HashMap get / put 복잡성


131

우리는 HashMap get/put연산이 O (1) 라고 말하는데 익숙합니다 . 그러나 해시 구현에 따라 다릅니다. 기본 오브젝트 해시는 실제로 JVM 힙의 내부 주소입니다. get/putare O (1) 이라고 주장하기에 충분하다고 확신 합니까?

사용 가능한 메모리는 또 다른 문제입니다. javadocs에서 알 수 있듯이 HashMap load factor0.75이어야합니다. JVM에 충분한 메모리가없고 load factor한계를 초과하면 어떻게됩니까?

따라서 O (1)이 보장되지 않는 것 같습니다. 말이 되나요, 아니면 뭔가 빠졌습니까?


1
상각 복잡성의 개념을 찾아 볼 수 있습니다. 예를 들어 여기를 참조하십시오 : stackoverflow.com/questions/3949217/time-complexity-of-hash-table 최악의 경우 복잡성은 해시 테이블에 대한 가장 중요한 척도가 아닙니다
Dr G

3
맞습니다 -O (1)로 상각되었습니다. 첫 부분을 잊지 마십시오. 이런 종류의 질문은 없습니다. :)
Engineer

내가 틀리지 않으면 Java 1.8부터 시간 복잡성 최악의 경우는 O (logN)입니다.
Tarun Kolla

답변:


216

그것은 많은 것들에 달려 있습니다. 그건 보통 자체가 일정 시간 괜찮은 해시, O (1) ...하지만 당신이 계산에 시간이 오래 걸립니다 해시를 가질 수 같은 해시 코드를 반환 해시 맵의 여러 항목이있는 경우, 일치하는 항목을 찾으려면 각 항목을 get호출하여 반복해야합니다 equals.

최악의 경우, a HashMap는 동일한 해시 버킷의 모든 항목을 통과하기 때문에 O (n) 조회를 갖습니다 (예 : 모두 동일한 해시 코드를 갖는 경우). 다행히도, 최악의 시나리오는 실제 경험에서 자주 나타나지 않습니다. 따라서 O (1)는 확실히 보장되지는 않지만 사용할 알고리즘과 데이터 구조를 고려할 때 일반적으로 가정해야합니다.

JDK 8에서는 HashMap키를 주문을 위해 비교할 수 있으면 밀도가 높은 버킷이 트리로 구현되어 동일한 해시 코드를 가진 항목이 많더라도 복잡성이 O (log) 엔). 물론 평등과 순서가 다른 키 유형이있는 경우 문제가 발생할 수 있습니다.

그리고 그렇습니다. 해시 맵에 충분한 메모리가 없다면 문제가 생길 것입니다 ...하지만 사용하는 데이터 구조에 관계없이 사실이 될 것입니다.


@marcog : 단일 조회를 위해 O (n log n)을 가정 합니까? 저에게 멍청한 소리가 들립니다. 물론 해시 및 등식 함수의 복잡성에 따라 달라 지지만 맵 크기에 따라 달라지지는 않습니다.
Jon Skeet

1
@marcog : O (n log n)라고 가정하고 있습니까? n 개의 항목을 삽입 하시겠습니까?
Jon Skeet

1
좋은 답변을 얻으려면 +1하십시오. 답변 에 해시 테이블대한 wikipedia 항목 과 같은 링크를 제공 하시겠습니까 ? 그렇게하면 관심이 많은 독자는 답을 했는지 이해하는 데 도움이 될 수 있습니다.
David Weiser

2
@ SleimanJneidi : 키가 Comparable <T>`을 구현하지 않으면 여전히 그렇습니다.하지만 시간이 더 있으면 답변을 업데이트하겠습니다.
Jon Skeet

1
@ ip696 : 그렇습니다. put"amortized O (1)"입니다 – 보통 O (1), 가끔 O (n)입니다.
Jon Skeet

9

기본 해시 코드가 주소인지 확실하지 않습니다. 얼마 전에 해시 코드 생성을위한 OpenJDK 소스를 읽었으며 조금 더 복잡한 것을 기억합니다. 여전히 좋은 배포를 보장하는 것은 아닙니다. 그러나 해시 맵에서 키로 사용하는 클래스가 기본 해시 코드를 사용하는 클래스가 적기 때문에 자체 구현을 제공하므로 좋을 것입니다.

게다가, 당신이 알지 못할 수도 있습니다 (다시 말하면, 이것은 읽기 소스를 기반으로합니다-보장되지 않습니다). 가장 큰 해시 맵을 제외하고 모두 필요했습니다. 그것은 당신이 그것을 볼 수있는 일반적인 경우를 생각할 수는 없지만 구체적으로 스스로하지 않는 해시를 처리하는 데 도움이됩니다.

마지막으로, 테이블이 오버로드 될 때 발생하는 것은 테이블이 병렬 연결된 목록 세트로 퇴화되어 성능이 O (n)이되는 것입니다. 특히, 통과되는 링크 수는 평균적으로 부하율의 절반입니다.


6
젠장. 나는 뒤집기 휴대 전화 터치 스크린에 이것을 입력하지 않았다면 Jon Sheet를 때릴 수 있다고 믿습니다. 그 배지가 있어요?
Tom Anderson

8

HashMap 작업은 hashCode 구현의 종속 요소입니다. 이상적인 시나리오의 경우 모든 객체에 고유 한 해시 코드를 제공하는 해시 구현이 우수하다고 가정하면 (해시 충돌 없음) 가장 우수하고 최악의 평균 시나리오는 O (1)입니다. 해시 코드의 잘못된 구현이 항상 1 또는 해시 충돌을 갖는 해시를 반환하는 시나리오를 생각해 봅시다. 이 경우 시간 복잡도는 O (n)입니다.

이제 메모리에 대한 질문의 두 번째 부분에 도달하면 JVM이 메모리 제약 조건을 처리합니다.


8

항목 수이고 크기 O(n/m)인 경우 해시 맵이 평균 이라고 이미 언급했습니다 . 원칙적으로 모든 것이 쿼리 시간 과 함께 단일 연결 목록으로 축소 될 수 있다고 언급되었습니다 . (이 모든 것은 해시 계산이 일정한 시간이라고 가정합니다).nmO(n)

그러나 자주 언급되지 않는 것은 확률이 적어도 1-1/n(따라서 99.9 % 확률 인 1000 개 항목의 경우) 가장 큰 버킷이 더 이상 채워지지 않는다는 것입니다 O(logn). 따라서 이진 검색 트리의 평균 복잡도를 일치시킵니다. (그리고 상수가 좋을수록 더 꽉 찬 경계는(log n)*(m/n) + O(1) ).

이 이론적 한계에 필요한 것은 합리적으로 좋은 해시 함수를 사용하는 것입니다 (Wikipedia : Universal Hashing 참조). .a*x>>m ). 물론 해시 값을 제공하는 사람은 무작위 상수를 어떻게 선택했는지 알지 못합니다.

TL; DR : 매우 높은 확률로 해시 맵의 최악의 경우 입 / 출력 복잡도는 O(logn)입니다.


(그리고이 중 어느 것도 무작위 데이터를 가정하지 않습니다. 확률은 순전히 해시 함수의 선택에서 발생합니다)
Thomas Ahle

또한 해시 맵에서 조회의 런타임 복잡성에 관한 동일한 질문이 있습니다. 상수 요인을 제거해야하므로 O (n) 인 것 같습니다. 1 / m은 상수 요소이므로 O (n)을 남기고 삭제됩니다.
nickdu

4

동의합니다 :

  • O (1)의 일반적인 상각 복잡성
  • hashCode()구현이 잘못 되면 여러 번의 충돌이 발생할 수 있습니다. 이는 최악의 경우 모든 객체가 동일한 버킷으로 이동하므로 각 버킷이 a로 백업되는 경우 O ( N )List 입니다.
  • Java 8부터는 HashMap각 버킷에 사용 된 노드 (연결된 목록)를 TreeNodes (목록이 8 개보다 큰 요소가있는 경우 빨강-검정 트리)로 동적으로 대체하여 O ( logN ) 의 성능이 저하 됩니다.

그러나 우리가 100 % 정확하기를 원한다면 이것은 진실이 아닙니다. 키의 구현 hashCode()및 유형Object (불변 / 캐시 또는 컬렉션)도 엄격한 용어로 실제 복잡성에 영향을 줄 수 있습니다.

다음 세 가지 경우를 가정 해 봅시다.

  1. HashMap<Integer, V>
  2. HashMap<String, V>
  3. HashMap<List<E>, V>

그들은 같은 복잡성을 가지고 있습니까? 글쎄, 첫 번째의 상각 복잡도는 예상대로 O (1)입니다. 그러나 나머지 hashCode()의 경우 조회 요소도 계산해야하므로 알고리즘에서 배열과 목록을 탐색해야 할 수도 있습니다.

위의 모든 배열 / 목록의 크기가 k 라고 가정합니다 . 이어서, HashMap<String, V>HashMap<List<E>, V>O (k)는 복잡성을 상각 마찬가지로, O (것이다 K + logN Java8에서) 최악의 경우.

* String키 를 사용하는 것은 변경이 불가능하고 Java hashCode()는 개인 변수 의 결과를 캐시 hash하므로 한 번만 계산되므로 키 를 사용하는 것이 더 복잡한 경우 입니다.

/** Cache the hash code for the string */
    private int hash; // Default to 0

그러나 Java의 String.hashCode()구현이 hash == 0컴퓨팅 전에 여부 를 확인 하기 때문에 위의 내용은 최악의 경우도 있습니다 hashCode. 그러나 hashcode"f5a5a608"과 같이 0 을 출력하는 비어 있지 않은 문자열이 있습니다. 여기를 참조 하십시오 .이 경우 메모가 도움이되지 않을 수 있습니다.


2

실제로, 그것은 O (1)이지만, 이것은 실제로 끔찍하고 수학적으로 말도 안되는 단순화입니다. O () 표기법은 문제의 크기가 무한대 인 경향이있을 때 알고리즘의 작동 방식을 나타냅니다. 해시 맵 get / put은 제한된 크기의 O (1) 알고리즘처럼 작동합니다. 한계는 컴퓨터 메모리와 어드레싱 관점에서 상당히 크지 만 무한대는 아닙니다.

해시 맵 get / put이 O (1)라고 말하면 실제로 get / put에 필요한 시간은 다소 일정하며 해시 맵이 될 수있는 한 해시 맵의 요소 수에 의존하지 않는다고 말해야합니다. 실제 컴퓨팅 시스템에 표시됩니다. 문제가 그 크기를 넘어서고 더 큰 해시 맵이 필요하다면, 잠시 후, 한 가지 요소를 설명하는 비트의 수는 설명 가능한 다른 요소가 없어 질수록 증가 할 것입니다. 예를 들어, 해시 맵을 사용하여 32 비트 숫자를 저장 한 후 해시 맵에 2 ^ 32 비트 이상의 요소를 갖도록 문제 크기를 늘리면 개별 요소는 32 비트 이상으로 설명됩니다.

개별 요소를 설명하는 데 필요한 비트 수는 log (N)입니다. 여기서 N은 최대 요소 수이므로 get 및 put은 실제로 O (log N)입니다.

트리 세트 (O (log n))와 비교하면 해시 세트는 O (long (max (n))이며 특정 구현에서는 max (n)이기 때문에 O (1)이라고 생각합니다. 고정되어 있고 변경되지 않으며 (비트 단위로 저장된 객체의 크기) 해시 코드를 계산하는 알고리즘이 빠릅니다.

마지막으로, 어떤 데이터 구조에서 요소를 찾는 것이 O (1)이라면 우리는 얇은 공기에서 정보를 생성 할 것입니다. n 요소의 데이터 구조가 있으면 n 가지 방법으로 하나의 요소를 선택할 수 있습니다. 이를 통해 log (n) 비트 정보를 인코딩 할 수 있습니다. 0 비트 (즉, O (1) 의미)로 인코딩 할 수 있다면 무한 압축 ZIP 알고리즘을 만들었습니다.


그렇다면 트리 세트의 복잡성이되어야합니까 O(log(n) * log(max(n)))? 모든 노드에서의 비교는 더 똑똑 할 수 있지만 최악의 경우 모든 O(log(max(n))비트 를 검사해야합니다 .
maaartinus
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.