[algorithm] 해시 테이블이 실제로 O (1) 일 수 있습니까?
해시 테이블이 O (1)을 달성 할 수 있다는 것은 상식 인 것처럼 보이지만 그것은 결코 이해가되지 않습니다. 누군가 그것을 설명해 주시겠습니까? 떠오르는 두 가지 상황은 다음과 같습니다.
A. 값은 해시 테이블의 크기보다 작은 int입니다. 따라서 값은 자체 해시이므로 해시 테이블이 없습니다. 그러나 만약 있다면 그것은 O (1)이고 여전히 비효율적입니다.
B. 값의 해시를 계산해야합니다. 이 상황에서 조회되는 데이터 크기의 순서는 O (n)입니다. O (n) 작업을 수행 한 후 조회는 O (1) 일 수 있지만 여전히 내 눈에는 O (n)으로 나옵니다.
그리고 완벽한 해시 나 큰 해시 테이블이 없으면 버킷 당 여러 항목이있을 수 있습니다. 그래서 어쨌든 어느 시점에서 작은 선형 검색으로 발전합니다.
나는 해시 테이블이 굉장하다고 생각하지만 이론적이라고 가정하지 않는 한 O (1) 지정을 얻지 못합니다.
해시 테이블에 대한 Wikipedia의 기사는 일관되게 일정한 조회 시간을 참조하고 해시 함수의 비용을 완전히 무시합니다. 정말 공정한 조치입니까?
편집 : 내가 배운 내용을 요약하면 다음과 같습니다.
-
해시 함수가 키의 모든 정보를 사용하는 데 필요하지 않고 일정한 시간이 될 수 있고 충분히 큰 테이블이 충돌을 거의 일정한 시간으로 낮출 수 있기 때문에 기술적으로 사실입니다.
-
실제로는 시간이 지남에 따라 충돌을 최소화하기 위해 해시 함수와 테이블 크기가 선택되는 한 제대로 작동하기 때문에 사실입니다. 이는 종종 일정한 시간 해시 함수를 사용하지 않음을 의미합니다.
답변
여기에 두 개의 변수, m과 n이 있습니다. 여기서 m은 입력의 길이이고 n은 해시의 항목 수입니다.
O (1) 조회 성능 주장은 최소한 두 가지 가정을합니다.
- 객체는 O (1) 시간에 동등성을 비교할 수 있습니다.
- 해시 충돌이 거의 없습니다.
객체가 가변 크기이고 동등성 검사에서 모든 비트를 확인해야하는 경우 성능은 O (m)가됩니다. 그러나 해시 함수는 O (m) 일 필요는 없습니다. O (1) 일 수 있습니다. 암호화 해시와 달리 사전에서 사용하는 해시 함수는 해시를 계산하기 위해 입력의 모든 비트를 볼 필요가 없습니다. 구현은 고정 된 수의 비트 만 볼 수 있습니다.
항목 수가 충분히 많은 경우 항목 수가 가능한 해시 수보다 커지고 충돌이 발생하여 성능이 O (1) 이상으로 상승합니다. 예를 들어 간단한 연결 목록 순회 (또는 O (n) * m) 두 가정이 모두 거짓 인 경우).
실제로 O (1) 주장은 기술적으로 거짓이지만 많은 실제 상황, 특히 위의 가정이 적용되는 상황에서 거의 참입니다.
답변
해시를 계산해야하므로 조회되는 데이터 크기의 순서는 O (n)입니다. O (n) 작업을 수행 한 후 조회는 O (1) 일 수 있지만 여전히 내 눈에는 O (n)으로 나옵니다.
뭐? 단일 요소를 해시하려면 일정한 시간이 걸립니다. 다른 이유는 무엇입니까? n
요소를 삽입하는 경우 예, n
해시 를 계산 해야하며 선형 시간이 걸립니다. . 이미 해시 테이블에있는 모든 항목의 해시를 다시 계산하지 않습니다.
그리고 완벽한 해시 나 큰 해시 테이블이없는 한 버킷 당 여러 항목이있을 수 있으므로 어쨌든 어느 시점에서 작은 선형 검색으로 전환됩니다.
반드시 그런 것은 아닙니다. 버킷은 반드시 목록 또는 배열 일 필요는 없으며 균형 잡힌 BST와 같은 모든 컨테이너 유형일 수 있습니다. 그것은 O(log n)
최악의 경우를 의미 합니다. 그러나 이것이 하나의 버킷에 너무 많은 요소를 넣지 않도록 좋은 해싱 함수를 선택하는 것이 중요한 이유입니다. KennyTM가 지적했듯이, O(1)
가끔 양동이를 파헤쳐 야하는 경우에도 평균적으로 여전히 시간을 얻을 수 있습니다.
해시 테이블의 트레이드 오프는 물론 공간 복잡성입니다. 당신은 시간을 위해 공간을 거래하고 있는데, 이는 컴퓨팅 과학의 일반적인 경우 인 것 같습니다.
다른 주석 중 하나에서 문자열을 키로 사용한다고 언급했습니다. 여러 문자로 구성되어 있기 때문에 문자열의 해시를 계산하는 데 걸리는 시간에 대해 걱정하십니까? 다른 사람이 다시 지적했듯이 해시를 계산하기 위해 모든 문자를 볼 필요는 없지만 그렇게하면 더 나은 해시를 생성 할 수 있습니다. 이 경우 m
키 에 평균 문자가 있고 모든 문자를 사용하여 해시를 계산하면 옳다고 생각합니다 O(m)
. 그렇다면 m >> n
문제가있을 수 있습니다. 이 경우 BST를 사용하는 것이 더 나을 것입니다. 또는 더 저렴한 해싱 기능을 선택하십시오.
답변
해시는 고정 된 크기입니다. 적절한 해시 버킷을 찾는 것은 고정 비용 작업입니다. 이것은 O (1)임을 의미합니다.
해시 계산은 특별히 비용이 많이 드는 작업 일 필요는 없습니다. 여기서는 암호화 해시 함수에 대해 이야기하지 않습니다. 그러나 그것은 의해입니다. 해시 함수 계산 자체는 요소 수 n 에 의존하지 않습니다 . 요소의 데이터 크기에 따라 달라질 수 있지만 이것은 n이 나타내는 것이 아닙니다 . 따라서 해시 계산은 n에 의존하지 않고 O (1)이기도합니다.
답변
해싱은 테이블에 일정한 수의 키만 있고 다른 가정이 이루어진 경우에만 O (1)입니다. 그러나 그러한 경우에는 이점이 있습니다.
키에 n 비트 표현이있는 경우 해시 함수는 이러한 비트 중 1, 2, … n을 사용할 수 있습니다. 1 비트를 사용하는 해시 함수를 생각해보세요. 평가는 확실히 O (1)입니다. 그러나 키 공간을 2로만 분할합니다. 따라서 동일한 빈에 2 ^ (n-1) 키를 매핑합니다. BST 검색을 사용하면 거의 가득 찬 경우 특정 키를 찾는 데 최대 n-1 단계가 걸립니다.
이것을 확장하여 해시 함수가 K 비트를 사용하는 경우 bin 크기가 2 ^ (nk)임을 확인할 수 있습니다.
따라서 K- 비트 해시 함수 ==> 2 ^ K 개 이하의 유효 빈 ==> 최대 2 ^ (nK) 개의 빈당 n- 비트 키 ==> (nK) 단계 (BST)로 충돌을 해결합니다. 실제로 대부분의 해시 함수는 “효과적이지”않으며 2 ^ k 빈을 생성하기 위해 K 비트 이상을 필요로 / 사용합니다. 그래서 이것조차도 낙관적입니다.
이런 식으로 볼 수 있습니다. 최악의 경우 n 비트의 키 쌍을 고유하게 구별하려면 ~ n 단계가 필요합니다. 해시 테이블이든 아니든이 정보 이론 한계를 피할 수있는 방법은 없습니다.
그러나 이것은 해시 테이블을 사용하는 방법 /시기가 아닙니다!
복잡도 분석에서는 n 비트 키의 경우 테이블에 O (2 ^ n) 키가있을 수 있다고 가정합니다 (예 : 가능한 모든 키의 1/4). 그러나 대부분은 아니지만 해시 테이블을 사용하는 경우 테이블에는 n 비트 키의 상수 만 있습니다. 테이블에 일정한 수의 키만 원하는 경우 (예 : C가 최대 수라고 말하면) O (C) 빈의 해시 테이블을 형성하여 예상되는 상수 충돌을 보장 할 수 있습니다 (좋은 해시 함수 사용). 및 키에있는 n 비트의 ~ logC를 사용하는 해시 함수. 그러면 모든 쿼리는 O (logC) = O (1)입니다. 이것이 사람들이 “해시 테이블 액세스가 O (1)”라고 주장하는 방법입니다.
여기에는 몇 가지 문제가 있습니다. 첫째, 모든 비트가 필요하지 않다는 것은 결제 트릭 일뿐입니다. 먼저 해시 함수에 키 값을 전달할 수 없습니다. 그 이유는 O (n) 인 메모리에서 n 비트를 이동하기 때문입니다. 따라서 예를 들어 참조 전달을 수행해야합니다. 그러나 여전히 O (n) 연산이었던 어딘가에 저장해야합니다. 당신은 단지 그것을 해싱에 청구하지 않습니다; 전체적인 계산 작업은 이것을 피할 수 없습니다. 둘째, 해싱을 수행하고 빈을 찾고 둘 이상의 키를 찾았습니다. 비용은 해결 방법에 따라 다릅니다. 비교 기반 (BST 또는 목록)을 수행하면 O (n) 작업이 수행됩니다 (리콜 키는 n 비트 임). 두 번째 해시를 수행하면 두 번째 해시가 충돌하면 동일한 문제가 발생합니다.
이 경우 BST와 같은 대안을 고려하십시오. C 키가 있으므로 균형 잡힌 BST는 깊이가 O (logC)이므로 검색에는 O (logC) 단계가 필요합니다. 그러나이 경우의 비교는 O (n) 연산이 될 것입니다. 따라서이 경우에는 해싱이 더 나은 선택 인 것으로 보입니다.
답변
요약 : 해시 테이블 O(1)
은 범용 해시 함수 제품군에서 임의로 균일하게 해시 함수를 선택하는 경우 예상되는 최악의 경우 시간을 보장 합니다. 예상되는 최악의 경우는 평균 사례와 동일하지 않습니다.
면책 조항 : 저는 해시 테이블이라는 것을 공식적으로 증명하지 않습니다 . O(1)
이는 coursera [ 1 ] 의이 비디오를 봤기 때문 입니다. 또한 해시 테이블 의 상각 된 측면에 대해서도 논의하지 않습니다 . 이는 해싱 및 충돌에 대한 논의와 직교합니다.
나는 다른 답변과 댓글 에서이 주제에 대해 놀랍도록 많은 혼란을 겪고 있으며이 긴 답변에서 일부를 수정하려고 노력할 것입니다.
최악의 경우에 대한 추론
다양한 유형의 최악의 경우 분석이 있습니다. 지금까지 대부분의 답변이 만든 분석 은 최악의 경우가 아니라 평균적인 경우입니다 [ 2 ]. 평균 사례 분석이 더 실용적인 경향이 있습니다. 알고리즘에 최악의 경우 하나의 입력이 있지만 실제로는 다른 모든 입력에 대해 잘 작동합니다. 결론은 런타임 이 실행중인 데이터 세트에 따라 달라진다 는 것 입니다.
get
해시 테이블 메서드의 다음 의사 코드를 고려하십시오 . 여기서는 연결을 통해 충돌을 처리한다고 가정하므로 테이블의 각 항목은 연결된 (key,value)
쌍 목록입니다 . 우리는 또한 버킷의 수를 가정 m
고정되어 있지만 O(n)
, 여기서 n
입력 요소의 수입니다.
function get(a: Table with m buckets, k: Key being looked up)
bucket <- compute hash(k) modulo m
for each (key,value) in a[bucket]
return value if k == key
return not_found
다른 답변에서 지적했듯이 이것은 평균 O(1)
및 최악의 경우로 실행됩니다 O(n)
. 여기서 도전으로 증명의 작은 스케치를 만들 수 있습니다. 과제는 다음과 같습니다.
(1) 해시 테이블 알고리즘을 적에게 제공합니다.
(2) 적은 원하는만큼 공부하고 준비 할 수 있습니다.
(3) 마지막으로 공격자는 n
테이블에 삽입 할 크기 를 입력 합니다.
문제는 적의 입력에 대한 해시 테이블이 얼마나 빠르 냐는 것입니다.
(1) 단계에서 공격자는 해시 함수를 알고 있습니다. 단계 (2) 동안 공격자는 예를 들어 여러 요소의 해시를 무작위로 계산하여 n
동일한 요소 의 목록을 만들 수 있습니다 hash modulo m
. 그리고 (3)에서 그들은 당신에게 그 목록을 줄 수 있습니다. 그러나 모든 n
요소가 동일한 버킷으로 해시되므로 알고리즘이 O(n)
해당 버킷의 연결 목록을 순회하는 데 시간 이 걸립니다 . 우리가 챌린지를 몇 번 재 시도해도 적은 항상 이기고 알고리즘이 얼마나 나쁜지 최악의 경우 O(n)
입니다.
해싱은 어떻게 O (1)입니까?
이전 도전에서 우리를 좌절시킨 것은 적들이 우리의 해시 함수를 아주 잘 알고 있었고 그 지식을 사용하여 가능한 최악의 입력을 만들 수 있다는 것입니다. 항상 하나의 고정 된 해시 함수를 사용하는 대신 H
알고리즘이 런타임에 임의로 선택할 수있는 해시 함수 집합이 실제로 있다면 어떨까요? 궁금한 점이 있으면 해시 함수H
의 범용 제품군 [ 3 ]이라고합니다. 좋습니다. 여기에 임의성 을 추가해 보겠습니다 .
먼저 우리의 해시 테이블도 씨앗을 포함한다고 가정 r
하고, r
시공시 임의의 숫자에 할당됩니다. 한 번 할당하면 해시 테이블 인스턴스에 대해 수정됩니다. 이제 의사 코드를 다시 살펴 보겠습니다.
function get(a: Table with m buckets and seed r, k: Key being looked up)
rHash <- H[r]
bucket <- compute rHash(k) modulo m
for each (key,value) in a[bucket]
return value if k == key
return not_found
도전을 한 번 더 시도하면 (1) 단계에서 공격자는 우리가 가지고있는 모든 해시 함수를 알 수 H
있지만 이제 우리가 사용하는 특정 해시 함수는에 의존합니다 r
. 의 값은 r
우리 구조에 비공개이며, 공격자는이를 런타임에 검사하거나 미리 예측할 수 없으므로 항상 우리에게 나쁜 목록을 만들 수 없습니다. 의 단계에서 (2) 대적 하나 개의 기능을 선택한다고 가정하자 hash
에서 H
무작위로, 그는 다음 목록으로 만들어줍니다 n
에서 충돌 hash modulo m
, 그리고 보냅니다 런타임에 있음을 손가락을 교차 단계 (3)에 대해 H[r]
동일합니다 hash
그들이 선택합니다.
이것은 적들에게 심각한 내기이며 그가 만든 목록은.에서 충돌 hash
하지만의 다른 해시 함수 아래에서 무작위 입력이 될 것입니다 H
. 그가이 내기에서 이기면 우리의 실행 시간은 O(n)
이전과 같이 최악의 경우가 될 것이지만 그가지면 평균 O(1)
시간 이 걸리는 임의의 입력을 받게 됩니다. 그리고 실제로 대부분의 적이지는 경우, 그는 모든 |H|
도전에서 단 한 번만 이기며 우리는 |H|
매우 크게 만들 수 있습니다.
이 결과를 적이 항상 도전에서이긴 이전 알고리즘과 대조하십시오. 여기에 조금 Handwaving하지만 이후 대부분의 시간 대적이 실패하고 이것이 사탄이 시도 할 수있는 모든 전략에 대한 사실, 최악의 경우가 있지만 것을 다음 O(n)
의 예상되는 최악의 경우는 사실이다 O(1)
.
다시 말하지만 이것은 공식적인 증거가 아닙니다. 이 예상 최악의 경우 분석에서 얻을 수있는 보장은 런타임이 이제 특정 입력과 독립적이라는 것 입니다. 이것은 우리가 동기를 부여한 적이 나쁜 입력을 쉽게 만들 수 있음을 보여준 평균 사례 분석과는 달리 진정한 무작위 보장입니다.
답변
최악의 경우 O (1)를 얻을 수있는 두 가지 설정이 있습니다 .
- 설정이 정적 인 경우 FKS 해싱은 최악의 경우 O (1) 보장을 제공합니다. 그러나 귀하가 지적했듯이 귀하의 설정은 고정되어 있지 않습니다.
- Cuckoo 해싱을 사용하는 경우 쿼리 및 삭제는
최악의 경우 O (1) 이지만 삽입은 O (1) 만 예상됩니다. Cuckoo 해싱은 총 삽입 수에 상한이 있고 테이블 크기를 약 25 % 더 크게 설정하는 경우 매우 잘 작동합니다.
여기 에서 복사
답변
여기서 논의한 바에 따르면 X가 (테이블의 요소 수 / 빈 수)의 상한선이면 빈 조회의 효율적인 구현을 가정하면 더 나은 대답은 O (log (X))입니다.