Java 해시 맵이 실제로 O (1)입니까?


159

SO re Java 해시 맵 및 O(1)조회 시간 에 대한 흥미로운 주장을 보았습니다 . 누군가 왜 이것이 그렇게 설명 할 수 있습니까? 이러한 해시 맵이 내가 구입 한 해시 알고리즘과 크게 다르지 않으면 충돌이 포함 된 데이터 세트가 항상 존재해야합니다.

어떤 경우에는 조회가 O(n)아닌 것 O(1)입니다.

사람은인지 설명 할 수 있다 O (1)와, 그렇다면, 그들이이 어떻게 달성?


1
나는 이것이 답이 아닐 수도 있다는 것을 알고 있지만 Wikipedia 에 이것에 관한 아주 좋은 기사 가 있다는 것을 기억 합니다. 성능 분석 섹션을 놓치지 마십시오
victor hugo

28
Big O 표기법은 수행중인 특정 유형의 분석에 대한 상한을 제공합니다. 최악의 경우, 평균적인 경우 등에 관심이 있는지를 지정해야합니다.
Dan Homerick

답변:


127

HashMap의 특별한 특징은 균형 잡힌 나무와 달리 그 행동이 확률 적이라는 것입니다. 이러한 경우 최악의 상황이 발생할 가능성의 관점에서 복잡성에 대해 이야기하는 것이 가장 도움이됩니다. 해시 맵의 경우, 물론 맵의 전체 크기와 관련하여 충돌이 발생한 경우입니다. 충돌은 추정하기 매우 쉽습니다.

p 충돌 = n / 용량

따라서 요소 수가 적은 해시 맵은 적어도 하나의 충돌이 발생할 가능성이 큽니다. Big O 표기법을 사용하면 더욱 매력적인 작업을 수행 할 수 있습니다. 임의의 고정 상수 k에 대해 관찰하십시오.

O (n) = O (k * n)

이 기능을 사용하여 해시 맵의 성능을 향상시킬 수 있습니다. 대신 최대 2 번의 충돌 가능성을 생각할 수 있습니다.

p 충돌 x 2 = (n / 용량) 2

이것은 훨씬 낮습니다. 한 번의 추가 충돌 처리 비용은 Big O 성능과 관련이 없으므로 알고리즘을 실제로 변경하지 않고도 성능을 향상시킬 수있는 방법을 찾았습니다! 우리는 이것을 generalzie 할 수 있습니다

p 충돌 xk = (n / 용량) k

그리고 이제 우리는 임의의 수의 충돌을 무시하고 우리가 설명하는 것보다 더 많은 충돌이 거의 없을 가능성이 있습니다. 알고리즘의 실제 구현을 변경하지 않고 올바른 k를 선택하여 임의로 작은 수준으로 확률을 얻을 수 있습니다.

우리는 해시 맵 에 높은 확률로 O (1) 액세스 권한이 있다고 말함으로써 이것에 대해 이야기합니다.


HTML을 사용하더라도 여전히 분수에 만족하지 않습니다. 좋은 방법을 생각할 수 있다면 정리하십시오.
SingleNegationElimination 2016 년

4
실제로, 위에서 말한 것은 O (log N) 효과가 고정 된 오버 헤드에 의해 N의 극단 값에 대해 묻혀 있다는 것입니다.
Hot Licks

기술적으로, 그 숫자는 충돌 횟수의 예상 값이며, 이는 단일 충돌 확률과 같습니다.
Simon Kuang

1
이것은 상각 항문 분석과 비슷합니까?
lostsoul29

1
@ OleV.V. HashMap의 우수한 성능은 항상 해시 함수가 잘 분산되어 있어야합니다. 입력에서 암호화 해싱 기능을 사용하여 해시 속도에 대해 더 나은 해시 품질을 교환 할 수 있습니다.
SingleNegationElimination

38

최악의 동작을 평균 (예상) 런타임과 혼합 한 것 같습니다. 전자는 일반적으로 해시 테이블에 대해 실제로 O (n)입니다 (즉, 완벽한 해싱을 사용하지 않음). 실제로는 거의 관련이 없습니다.

반 괜찮은 해시와 결합 된 모든 신뢰할 수있는 해시 테이블 구현은 예상되는 경우 매우 작은 차이 범위 내에서 매우 작은 요소 (실제로 2)로 O (1)의 검색 성능을 갖습니다.


6
나는 항상 상한이 최악의 경우라고 생각했지만 오해 된 것 같습니다-평균 경우 상한을 가질 수 있습니다. 따라서 O (1)를 주장하는 사람들은 평균적인 경우라고 분명히 밝힌 것 같습니다. 최악의 경우는 충돌이 많고 O (n)이되는 데이터 세트입니다. 이제 말이 되네요.
paxdiablo 2016 년

2
평균 사례에 큰 O 표기법을 사용할 때 예상되는 런타임 함수의 상한에 대해 명확하게 정의 된 수학적 함수라는 것을 분명히해야합니다. 그렇지 않으면 대답이 의미가 없습니다.
ldog 2016 년

1
gmatt : 귀하의 이의 제기를 이해하고 있는지 잘 모르겠습니다. big-O 표기법은 정의 에 따라 함수의 상한 입니다. 그러므로 다른 의미가 있습니까?
Konrad Rudolph

3
일반적으로 컴퓨터 문헌에서는 알고리즘의 런타임 또는 공간 복잡도 함수의 상한을 나타내는 큰 O 표기법을 볼 수 있습니다. 이 경우 상한은 실제로 함수가 아니라 함수에 대한 연산자 (임의 변수)이며 실제로는 필수입니다 (레베 그). 그런 것을 묶을 수 있다는 사실을 취해서는 안됩니다 당연하고 사소한 것이 아닙니다.
ldog 2016 년

31

Java에서 HashMap은 hashCode를 사용하여 버킷을 찾습니다. 각 버킷은 해당 버킷에있는 항목의 목록입니다. 비교를 위해 등호를 사용하여 항목을 스캔합니다. 항목을 추가 할 때 특정로드 백분율에 도달하면 HashMap의 크기가 조정됩니다.

따라서 때로는 몇 가지 항목과 비교해야하지만 일반적으로 O (n)보다 O (1)에 훨씬 가깝습니다. 실용적인 목적으로, 당신이 알아야 할 전부입니다.


11
big-O는 한계를 지정해야하므로 O (1)에 가까운 지 여부에 차이가 없습니다. O (n / 10 ^ 100)조차도 여전히 O (n)입니다. 나는 효율성을 낮추고 비율을 낮추는 것에 대해 당신의 요지를 얻었지만 여전히 알고리즘을 O (n)에 둡니다.
paxdiablo 2016 년

4
해시 맵 분석은 일반적으로 평균 경우에 O (1)입니다 (공모 포함) 최악의 경우 O (n)을 가질 수 있지만 일반적으로는 그렇지 않습니다. 차이에 대해-O (1)은 차트의 항목 수에 관계없이 동일한 액세스 시간을 얻음을 의미하며 일반적으로 테이블 크기와 'n ')
Liran Orevi 2016 년

4
버킷에 이미 일부 요소가 있기 때문에 버킷을 검색하는 데 시간이 걸리더라도 여전히 정확히 O (1)입니다. 버킷의 최대 크기가 고정되어있는 한 O () 분류와 관련이없는 상수입니다. 그러나 "유사한"키가 추가 된 요소가 더 많아서 이러한 버킷이 오버플로되어 더 이상 상수를 보장 할 수 없습니다.
sth 2016 년

@sth 버킷의 크기가 고정 된 이유는 무엇입니까?
Navin

31

o (1)은 각 룩업이 단일 항목 만 검사한다는 것을 의미하지는 않습니다. 이는 검사 된 평균 항목 수가 컨테이너의 항목 수에 따라 일정하게 유지됨을 의미합니다. 따라서 100 개의 항목이있는 컨테이너에서 항목을 찾기 위해 평균 4 번의 비교를 수행하는 경우 10000 개의 항목이있는 컨테이너에서 항목을 찾기 위해 평균 4 번의 비교를 수행해야합니다. 특히 해시 테이블이 다시 해시되는 지점과 매우 적은 수의 항목이있는 지점 주변의 분산).

따라서 버킷 당 평균 키 수가 고정 범위 내에있는 한 충돌로 인해 컨테이너에서 o (1) 작업을 수행 할 수 없습니다.


16

나는 이것이 오래된 질문이라는 것을 알고 있지만 실제로 그것에 대한 새로운 대답이 있습니다.

해시 맵은 실제로 O(1)엄밀히 말하면 정확하지 않습니다. 요소 수는 임의로 커질수록 일정한 시간에 검색 할 수 없으므로 O 표기법은 숫자로 정의됩니다. 임의로 커짐).

그러나 O(n)버킷을 선형 목록으로 구현해야한다는 규칙이 없기 때문에 실시간 복잡성이 따르는 것은 아닙니다 .

실제로 Java 8은 버킷 TreeMaps이 임계 값을 초과 하면 버킷을 구현 하여 실제 시간을 만듭니다 O(log n).


4

버킷 수 (b라고 함)가 일정하게 유지되면 (일반적인 경우) 조회는 실제로 O (n)입니다.
n이 커짐에 따라 각 버킷의 요소 수는 평균 n / b입니다. 충돌 해결이 일반적인 방법 중 하나 (예 : 링크 된 목록) 중 하나로 수행되면 조회는 O (n / b) = O (n)입니다.

O 표기법은 n이 커지면 어떻게되는지에 관한 것입니다. 특정 알고리즘에 적용하면 오해의 소지가 있으며 해시 테이블이 그 예입니다. 처리 할 요소 수에 따라 버킷 수를 선택합니다. n이 b와 거의 같은 크기 인 경우 조회는 대략 일정한 시간이지만 O는 n → ∞로 한계로 정의되므로 O (1)이라고 부를 수 없습니다.



2

우리는 O (1) 인 해시 테이블 조회의 표준 설명이 엄격한 최악의 성능이 아니라 평균 예상 시간을 참조 함을 확립했습니다. 체인과의 충돌을 해결하는 해시 테이블 (예 : Java 해시 맵)의 경우 해시 함수가 좋은 O (1 + α)입니다 . . 여기서 α는 테이블의로드 계수입니다. 저장하는 객체의 수가 테이블 크기보다 큰 상수보다 크지 않은 한 여전히 일정합니다.

엄밀히 말하면 결정적 해시 함수에 대해 O ( n ) 조회 가 필요한 입력을 구성 할 수 있다고 설명했습니다 . 그러나 평균 검색 시간과 다른 최악의 예상 시간 을 고려하는 것도 흥미 롭습니다 . 체인을 사용 하면 α = 1 일 때 O (1 + 가장 긴 체인의 길이)입니다 (예 : Θ (log n / log log n )).

예상되는 최악의 경우 조회를 일정하게 유지하는 이론적 인 방법에 관심이 있다면 다른 해시 테이블과의 충돌을 재귀 적으로 해결하는 동적 완벽한 해싱 에 대해 읽을 수 있습니다 !


2

해싱 함수가 매우 좋은 경우에만 O (1)입니다. Java 해시 테이블 구현은 잘못된 해시 함수로부터 보호하지 않습니다.

항목을 추가 할 때 테이블을 확장해야하는지 여부는 조회 시간에 관한 것이므로 질문과 관련이 없습니다.


2

HashMap 내부의 요소는 연결된 목록 (노드)의 배열로 저장되며, 배열의 각 연결된 목록은 하나 이상의 키의 고유 한 해시 값에 대한 버킷을 나타냅니다.
HashMap에 항목을 추가하는 동안 키의 해시 코드는 배열에서 버킷의 위치를 ​​결정하는 데 사용됩니다.

location = (arraylength - 1) & keyhashcode

여기서 &는 비트 AND 연산자를 나타냅니다.

예를 들면 다음과 같습니다. 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")

get 작업 중에 동일한 방법을 사용하여 키의 버킷 위치를 결정합니다. 최상의 경우 각 키에는 고유 한 해시 코드가 있고 각 키에 대해 고유 한 버킷이 발생합니다.이 경우 get 메소드는 버킷 위치를 결정하고 상수 O (1) 인 값을 검색하는 데만 시간을 소비합니다.

최악의 경우 모든 키에 동일한 해시 코드가 있고 동일한 버킷에 저장되므로 전체 목록을 순회하여 O (n)이됩니다.

Java 8의 경우, 크기가 8보다 커지면 Linked List 버킷이 TreeMap으로 바뀌어 최악의 검색 효율이 O (log n)로 줄어 듭니다.


1

이것은 알고리즘 자체가 실제로 변경되지 않기 때문에 기본적으로 대부분의 프로그래밍 언어에서 대부분의 해시 테이블 구현에 적용됩니다.

테이블에 충돌이 없으면 단일 조회 만 수행하면되므로 실행 시간은 O (1)입니다. 충돌이있는 경우 둘 이상의 조회를 수행해야하므로 성능이 O (n)으로 낮아집니다.


1
실행 시간이 조회 시간에 의해 제한된다고 가정합니다. 해시 함수는 경계 (String)을 제공하는 경우 실제로 당신은 상황을 많이 찾을 수 있습니다
스테판 EGGERMONT

1

충돌을 피하기 위해 선택한 알고리즘에 따라 다릅니다. 구현에서 별도의 체인을 사용하는 경우 모든 데이터 요소가 동일한 값으로 해시되는 최악의 시나리오가 발생합니다 (예 : 해시 함수의 선택 불량). 이 경우 데이터 조회는 연결된 목록, 즉 O (n)의 선형 검색과 다르지 않습니다. 그러나 그 발생 확률은 무시할 만하고 조회가 가장 좋고 평균 사례는 일정하게 유지됩니다. 즉 O (1).


1

실질적인 관점에서 학문을 제외하고 HashMaps는 성능에 영향을 미치지 않는 것으로 인정되어야합니다 (프로필러가 달리 지시하지 않는 한).


4
실용적이지 않습니다. 문자열을 키로 사용하자마자 모든 해시 함수가 이상적인 것은 아니며 일부는 실제로 느리다는 것을 알 수 있습니다.
Stephan Eggermont

1

이론적 인 경우에만 해시 코드가 항상 다르고 모든 해시 코드에 대한 버킷도 다른 경우 O (1)이 존재합니다. 그렇지 않으면, 그것은 해시 맵의 증분에서 일정한 순서이며, 검색 순서는 일정하게 유지됩니다.


0

물론 해시 맵의 성능은 주어진 객체에 대한 hashCode () 함수의 품질에 따라 달라집니다. 그러나 충돌 가능성이 매우 낮도록 함수를 구현하면 성능이 매우 우수합니다 ( 가능한 모든 경우 에 O (1)는 아니지만 대부분의 경우).

예를 들어, Oracle JRE의 기본 구현은 난수 (오브젝트 인스턴스에 저장되어 변경되지 않도록하지만 바이어스 된 잠금을 비활성화하지만 다른 논의 임)를 사용하므로 충돌 가능성이 있습니다. 매우 낮은.


"대부분의 경우"입니다. 보다 구체적으로, N이 무한대에 가까워 질수록 총 시간은 K 곱하기 N (K가 일정한)을 향하는 경향이있다.
ChrisW 2016 년

7
이것은 잘못이다. 해시 테이블의 인덱스는 hashCode % tableSize충돌이 발생할 수 있음을 통해 결정됩니다 . 32 비트를 완전히 사용하지 못합니다. 이것은 해시 테이블의 요점입니다. 큰 인덱싱 공간을 작은 것으로 줄입니다.
FogleBird

1
"충돌이 없음을 보장합니다"아니오지도의 크기가 해시 크기보다 작기 때문이 아닙니다. 예를 들어지도의 크기가 2 인 경우 충돌이 보장됩니다 (문제는 중요하지 않음) 세 가지 요소를 삽입하려고하면 해시가 무엇인지).
ChrisW 2016 년

그러나 O (1)에서 키에서 메모리 주소로 어떻게 변환합니까? x = array [ "key"]와 같습니다. 키는 메모리 주소가 아니므로 여전히 O (n) 조회 여야합니다.
paxdiablo

1
"해시 코드를 구현하지 않으면 객체의 메모리 주소를 사용한다고 생각합니다." 그것을 사용할 수는 있지만 표준 Oracle Java의 기본 hashCode는 실제로 객체 헤더에 저장된 25 비트 난수이므로 64/32 비트는 아무런 영향을 미치지 않습니다.
Boann
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.