[data-structures] 해시 테이블은 어떻게 작동합니까?

해시 테이블의 작동 방식에 대한 설명을 찾고 있습니다.

예를 들어 키를 가져 와서 해시를 계산하고 (어떻게 설명을 찾고 있는지) 값을 저장하는 배열의 위치를 ​​알아 내기 위해 모듈로를 수행하지만 지식이 멈 춥니 다. .

누구나 프로세스를 명확히 할 수 있습니까?

편집 : 해시 코드 계산 방법에 대해 구체적으로 묻지 않고 해시 테이블 작동 방법에 대한 일반적인 개요를 제공합니다.



답변

평신도의 용어로 설명합니다.

도서관에 책을 채우고 책을 채우는 것이 아니라 필요할 때 쉽게 다시 찾을 수 있기를 원한다고 가정 해 봅시다.

따라서 책을 읽고 싶은 사람이 책의 제목과 부팅 할 정확한 제목을 알고 있으면 그게 전부라고 결정합니다. 제목을 통해 사서의 도움을 받아 그 사람이 책을 쉽고 빠르게 찾을 수 있어야합니다.

어떻게 그렇게 할 수 있습니까? 글쎄, 당신은 분명히 당신이 각 책을 넣는 곳의 목록을 유지할 수 있지만, 도서관을 검색하는 것과 같은 문제가 있습니다. 당신은 목록을 검색해야합니다. 물론, 목록은 더 작고 쉽게 검색 할 수 있지만 여전히 라이브러리 (또는 목록)의 한쪽 끝에서 다른 쪽 끝까지 순차적으로 검색하고 싶지는 않습니다.

책 제목으로 한 번에 올바른 지점을 제공 할 수있는 것을 원하므로 필요한 선반으로 걸어 가서 책을 집어 올리면됩니다.

그러나 어떻게 할 수 있습니까? 글쎄, 라이브러리를 채울 때 약간의 예측과 라이브러리를 채울 때 많은 작업이 있습니다.

라이브러리를 한쪽 끝에서 다른 쪽 끝까지 채우는 대신 영리한 작은 방법을 고안하십시오. 책 제목을 가져 와서 작은 컴퓨터 프로그램을 통해 실행하십시오.이 프로그램은 선반 번호와 해당 선반의 슬롯 번호를 표시합니다. 여기가 책을 놓는 곳입니다.

이 프로그램의 장점은 나중에 사람이 책을 읽기 위해 다시 들어올 때 프로그램을 통해 제목을 다시 한 번 피드하고 원래 제공된 선반 번호와 슬롯 번호를 다시 얻는다는 것입니다. 책이있는 곳.

다른 사람들이 이미 언급 했듯이이 프로그램은 해시 알고리즘 또는 해시 계산이라고하며 일반적으로 공급 된 데이터 (이 경우 책 제목)를 가져 와서 숫자를 계산하여 작동합니다.

간단히하기 위해 각 문자와 기호를 숫자로 변환하고 모두 합산한다고 가정 해 봅시다. 실제로는 그보다 훨씬 복잡하지만 지금은 그대로 두겠습니다.

이러한 알고리즘의 장점은 동일한 입력을 반복해서 입력하면 매번 같은 숫자를 뱉어내는 것입니다.

기본적으로 해시 테이블이 작동하는 방식입니다.

기술적 인 내용은 다음과 같습니다.

첫째, 숫자의 크기가 있습니다. 일반적으로 이러한 해시 알고리즘의 출력은 일반적으로 테이블에있는 공간보다 훨씬 큰 일부 범위 내에 있습니다. 예를 들어, 도서관에 정확히 백만 권의 책을 넣을 공간이 있다고 가정 해 봅시다. 해시 계산의 출력은 0에서 10 억 사이 일 수 있으며 훨씬 더 높습니다.

그래서 우리는 무엇을합니까? 우리는 모듈러스 계산 (modulus calculation)이라는 것을 사용합니다. 이것은 기본적으로 당신이 원하는 수 (예 : 10 억 숫자)를 세었지만 훨씬 더 작은 범위 안에 머무르기를 원한다면, 당신이 다시 시작한 작은 범위의 한계에 도달 할 때마다 0이지만 큰 순서로 얼마나 멀리 있는지 추적해야합니다.

해시 알고리즘의 출력이 0에서 20 사이이고 특정 제목에서 값 17을 얻습니다. 도서관의 크기가 7 권인 경우 1, 2, 3, 4, 5, 6으로 계산하고 7에 도달하면 0부터 다시 시작합니다. 17 번 계산해야하므로 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3이며 최종 숫자는 3입니다.

물론 모듈러스 계산은 그런 식으로 수행되지 않고 나누기와 나머지로 수행됩니다. 17을 7로 나누는 나머지는 3입니다 (7은 14에서 17에 2 번 들어가고 17과 14의 차이는 3입니다).

따라서 책을 슬롯 번호 3에 넣습니다.

이것은 다음 문제로 이어집니다. 충돌. 알고리즘은 책을 간격을 두어 라이브러리를 정확하게 채울 수있는 방법이 없기 때문에 (또는 원하는 경우 해시 테이블) 항상 이전에 사용 된 숫자를 계산하게됩니다. 도서관의 의미에서 책장에 넣고 싶은 선반 번호와 슬롯 번호에 도달하면 이미 책이 있습니다.

데이터를 또 다른 계산으로 실행하여 테이블의 다른 지점을 가져 오거나 ( 이중 해싱 ) 주어진 공간에 가까운 공간을 찾는 것 (예 : 슬롯을 가정 한 이전 책 바로 옆)을 포함한 다양한 충돌 처리 방법이 있습니다. 선형 프로빙 이라고도 함 ). 이것은 나중에 책을 찾으려고 할 때 약간의 파고가 있다는 것을 의미하지만, 도서관의 한쪽 끝에서 시작하는 것보다 여전히 낫습니다.

마지막으로 라이브러리가 허용하는 것보다 많은 책을 라이브러리에 넣을 수 있습니다. 다시 말해, 더 큰 라이브러리를 만들어야합니다. 라이브러리의 정확한 지점은 라이브러리의 정확한 현재 크기를 사용하여 계산되었으므로 라이브러리의 크기를 조정하면 계산을 수행 한 후 해당 지점을 찾기 위해 모든 책에 대해 새로운 지점을 찾아야 할 수도 있습니다. 변경되었습니다.

이 설명이 버킷과 함수보다 지구에 약간 더 가깝기를 바랍니다. 🙂


답변

사용법 및 Lingo :

  1. 해시 테이블 은 데이터 (또는 레코드)를 빠르게 저장하고 검색하는 데 사용됩니다.
  2. 레코드는 해시 키를 사용하여 버킷 에 저장됩니다
  3. 해시 키 는 레코드에 포함 된 선택한 값 ( 값)에 해싱 알고리즘을 적용하여 계산됩니다 . 이 선택된 값은 모든 레코드에 공통 값이어야합니다.
  4. 버킷 에는 특정 순서로 구성된 여러 레코드가있을 수 있습니다.

실제 예 :

1803 년에 설립되었으며 컴퓨터 기술이 부족한 Hash & Co. 는 약 30,000 명의 고객에 대한 상세 정보 (기록)를 보관하기 위해 총 300 개의 파일 캐비닛을 보유하고있었습니다. 각 파일 폴더는 클라이언트 번호 (0-29,999의 고유 번호)로 명확하게 식별되었습니다.

당시의 사무원은 근무하는 직원의 고객 기록을 신속하게 가져 와서 저장해야했습니다. 직원들은 해싱 방법을 사용하여 레코드를 저장하고 검색하는 것이 더 효율적이라고 결정했습니다.

고객 기록을 제출하기 위해 사무원은 폴더에 기록 된 고유 고객 번호를 사용합니다. 이 클라이언트 번호를 사용하면 포함 된 파일 캐비닛을 식별하기 위해 해시 키 를 300 씩 변조합니다 . 파일 캐비닛을 열면 클라이언트 번호별로 많은 폴더가 포함되어 있음을 알 수 있습니다. 올바른 위치를 식별 한 후에는 간단히 해당 위치로 이동합니다.

고객 기록을 검색하기 위해 서류 제출시 사무원에게 고객 번호가 부여됩니다. 이 고유 한 클라이언트 번호 ( 해시 키 )를 사용하여 클라이언트 폴더가있는 파일 캐비넷을 판별하기 위해 300으로 변조합니다. 그들이 파일 캐비닛을 열었을 때 클라이언트 번호별로 많은 폴더가 포함되어 있음을 발견했습니다. 레코드를 검색하면 클라이언트 폴더를 빠르게 찾아서 검색합니다.

실제 예에서 버킷파일 캐비닛 이며 레코드파일 폴더 입니다.


기억해야 할 중요한 점은 컴퓨터 (및 해당 알고리즘)가 문자열보다 숫자를 더 잘 처리한다는 것입니다. 따라서 인덱스를 사용하여 큰 배열에 액세스하는 것이 순차적으로 액세스하는 것보다 훨씬 빠릅니다.

Simon이 내가 매우 중요하다고 생각한 것처럼 해싱 부분은 큰 공간 (임의의 길이, 일반적으로 문자열 등)을 변환하고 색인을 위해 작은 크기의 공간 (일반적으로 숫자, 알려진 숫자)으로 매핑하는 것입니다. 기억해야 할 것이 매우 중요합니다!

따라서 위의 예에서 30,000 개의 가능한 클라이언트가 더 작은 공간에 매핑되었습니다.


이것의 주요 아이디어는 일반적으로 시간이 많이 걸리는 실제 검색 속도를 높이기 위해 전체 데이터 세트를 세그먼트로 나누는 것입니다. 위의 예에서 300 개의 파일 캐비닛 각각은 (통계적으로) 약 100 개의 레코드를 포함합니다. 100 개의 레코드를 통해 (주문에 관계없이) 검색하는 것이 30,000을 처리하는 것보다 훨씬 빠릅니다.

일부는 실제로 이미이 작업을 수행 한 것을 알 수 있습니다. 그러나 해시 키를 생성하기 위해 해싱 방법을 고안하는 대신 대부분의 경우 단순히 성의 첫 글자를 사용합니다. 따라서 각각 A부터 Z까지의 문자를 포함하는 26 개의 파일 캐비닛이있는 경우 이론적으로 데이터를 세그먼트 화하고 파일 정리 및 검색 프로세스를 향상 시켰습니다.

도움이 되었기를 바랍니다,

복숭아!


답변

이것은 이론의 매우 깊은 영역으로 밝혀졌지만 기본 개요는 간단합니다.

본질적으로 해시 함수는 하나의 공백 (임의의 길이의 문자열)에서 물건을 가져 와서 인덱싱에 유용한 공간 (예 : 부호없는 정수)에 매핑하는 함수 일뿐입니다.

해시해야 할 공간이 적다면 정수로 해석하는 것만으로도 벗어날 수 있습니다 (예 : 4 바이트 문자열)

그러나 일반적으로 더 큰 공간이 있습니다. 키로 허용하는 것의 공간이 uint32 또는 기타 색인을 생성하는 데 사용하는 것의 공간보다 크면 각각에 대해 고유 한 값을 가질 수 없습니다. 둘 이상의 것들이 동일한 결과로 해시되면 중복을 적절한 방식으로 처리해야합니다 (일반적으로 충돌이라고하며 처리 방법 또는 처리하지 않는 방식은 현재 상태에 따라 조금씩 다름) 해시 사용).

이것은 동일한 결과를 얻지 않기를 원하며 해시 함수가 빠르기를 원할 수도 있습니다.

이 두 속성 (및 다른 속성)의 균형을 유지하면 많은 사람들이 바쁘게 지 냈습니다!

실제로는 일반적으로 응용 프로그램에 잘 작동하는 것으로 알려진 기능을 찾아서 사용할 수 있어야합니다.

이제이 작업을 해시 테이블로 만들려면 메모리 사용에 신경 쓰지 않았다고 상상해보십시오. 그런 다음 인덱싱 세트 (예 : 모든 uint32)만큼 배열을 만들 수 있습니다. 테이블에 무언가를 추가하면 키를 해시하고 해당 인덱스의 배열을 봅니다. 거기에 아무것도 없다면, 거기에 가치를 두십시오. 이미 무언가가 있다면,이 새로운 항목을 해당 주소에있는 것들의 목록에 추가하여 어떤 항목이 실제로 어떤 키에 속하는지를 찾기에 충분한 정보 (원래 키 또는 영리한 것)와 함께 추가하십시오.

오래 갈수록 해시 테이블 (배열)의 모든 항목이 비어 있거나 하나의 항목 또는 항목 목록을 포함합니다. 검색은 배열에 색인을 생성하고 값을 반환하거나 값 목록을 걷고 올바른 값을 반환하는 것처럼 간단합니다.

물론 실제로는 이렇게 할 수 없으며 너무 많은 메모리를 낭비합니다. 따라서 희소 배열을 기반으로 모든 작업을 수행합니다 (실제로 사용되는 항목은 다른 항목은 암시 적으로 null입니다).

이 작업을 개선하기위한 많은 구성표와 요령이 있지만 이것이 기본입니다.


답변

많은 답변이 있지만 그중 어느 것도 시각적이지 않으며 해시 테이블은 시각화 할 때 쉽게 “클릭”할 수 있습니다.

해시 테이블은 종종 연결된 목록의 배열로 구현됩니다. 사람들의 이름을 저장하는 테이블을 상상하면 몇 번의 삽입 후에 아래와 같이 메모리에 배치 될 수 있습니다. 여기서 ()-닫힌 숫자는 텍스트 / 이름의 해시 값입니다.

bucket#  bucket content / linked list

[0]      --> "sue"(780) --> null
[1]      null
[2]      --> "fred"(42) --> "bill"(9282) --> "jane"(42) --> null
[3]      --> "mary"(73) --> null
[4]      null
[5]      --> "masayuki"(75) --> "sarwar"(105) --> null
[6]      --> "margaret"(2626) --> null
[7]      null
[8]      --> "bob"(308) --> null
[9]      null

몇 가지 사항 :

  • 배열 항목 (인덱스는 각각 [0], [1]…)를로 알려져 버킷 및 시작 – 비어 -의 연결리스트 (일명 요소 ,이 예에서 – 사람의 이름 )
  • 각각의 값은 (예를 들어, "fred"해시가 42)에서 연결된 통 [hash % number_of_buckets]42 % 10 == [2]; %는 IS 나머지 연산자 버킷의 개수로 나눈 나머지는 –
  • 복수의 데이터 값이있다 충돌 에서와 같은 통에서 연결되어, 주로 이들의 해시 값은 모듈로 연산 한 후 충돌 (예 있으므로 42 % 10 == [2], 및 9282 % 10 == [2]) 만, 해시 값이 동일 (예를 들어, 때때로 때문에 "fred""jane"모두 해시 도시 42이상)
    • 대부분의 해시 테이블은 해시 대상 버킷의 링크 된 목록에서 이미 찾거나 삽입되는 값의 전체 값 (여기서는 텍스트)을 비교하여 성능이 약간 저하되었지만 기능적 혼란이없는 충돌을 처리합니다.

연결된 목록 길이는 값 수가 아닌로드 팩터와 관련이 있습니다.

테이블 크기가 커지면 위와 같이 구현 된 해시 테이블은 자체적으로 크기를 조정하는 경향이 있습니다 (즉, 더 큰 버킷 배열을 생성하고 그로부터 새로운 / 업데이트 된 링크 목록을 생성하고 기존 배열을 삭제) 버킷에 대한 값의 비율을 유지합니다 (일명 로드 계수 0.5 내지 1.0의 범위에서) 대체로.

Hans는 아래 주석에서 다른 부하 계수에 대한 실제 공식을 제공하지만, 표시 값에 대해서는 부하 계수 1 및 암호화 강도 해시 함수를 사용하여 버킷의 1 / e (~ 36.8 %)가 비어 있고 다른 1 / e (~ 36.8 %)는 1 / (2e) 또는 ~ 18.4 % 두 요소, 1 / (3! e) 약 6.1 % 세 요소, 1 / (4! e) 또는 ~ 1.5 % 네 요소, 1 / (5! e) ~ .3 %에는 5 개 등이 있습니다.-비어 있지 않은 버킷의 평균 체인 길이는 테이블에 몇 개의 요소가 있는지에 관계없이 ~ 1.58입니다 (즉, 100 개의 요소와 100 개의 버킷이 있는지 또는 1 억 개가 있는지) 조회 및 삽입 / 삭제가 O (1) 일정 시간 작업 이라고 말하는 이유 입니다.

해시 테이블이 키를 값과 연관시키는 방법

위에서 설명한 해시 테이블 구현을 고려할 때 필드 struct Value { string name; int age; };만 보며 name(연령 무시) 등호 비교 및 ​​해시 함수와 같은 값 유형을 작성하고 멋진 일이 발생 한다고 상상할 수 있습니다 . 테이블에서 Value와 같이 레코드 를 저장할 수 있습니다 {"sue", 63}그런 다음 나중에 나이를 모른 채 “고소”를 검색하고 저장된 값을 찾고 나이를 복구하거나 업데이트합니다.
생일 축하합니다-해시 값을 흥미롭게 변경하지 않으므로 Sue의 레코드를 다른 레코드로 옮길 필요가 없습니다. 버킷.

이 작업을 수행 할 때 해시 테이블을 연관 컨테이너 ( 일명 map )로 사용하며, 저장하는 값은 (이름)와 여전히 혼란스러워하는 하나 이상의 다른 필드 ( ( 내 예에서는 나이 만). 맵으로 사용되는 해시 테이블 구현을 해시 맵이라고 합니다.

이것은 “sue”와 같은 불연속 값을 저장 한이 답변의 앞부분의 예와 대조됩니다.이 값은 자체 키로 생각할 수 있습니다. 이러한 종류의 사용법을 해시 세트라고 합니다.

해시 테이블을 구현하는 다른 방법이 있습니다

모든 해시 테이블이 별도의 체인으로 알려진 링크 된 목록을 사용 하는 것은 아니지만 주로 대체 주소 해싱 (일명 개방 주소 지정 ) , 특히 지우기 작업이 지원되는 경우 충돌이 발생하기 쉬운 키 / 해시 함수.


해시 함수에 대한 몇 마디

강력한 해싱 …

일반적으로 최악의 충돌을 최소화하는 해시 함수의 작업은 항상 동일한 키에 대해 동일한 해시 값을 생성하면서 해시 테이블 버킷 주위에 키를 무작위로 효과적으로 분사하는 것입니다. 키의 어느 곳에서든 1 비트 만 변경해도 결과 해시 값에서 비트의 절반 이상이 이상적으로 무작위로 플립됩니다.

이것은 일반적으로 수학에 너무 복잡하여 조정하기가 어렵습니다. 가장 확장 가능하거나 캐시 친화적이지 않지만 본질적으로 우아함 (일회용 패드를 사용한 암호화와 같은)을 이해하기 쉬운 방법에 대해 언급하겠습니다. 64 비트를 해싱한다고 가정 해보십시오. double각 256 개의 난수 (아래 코드) 각각에 8 개의 테이블을 만든 다음 각 double메모리 의 8 비트 / 1 바이트 슬라이스 를 사용하여 다른 테이블에 색인을 생성 할 수 있습니다. 당신이 찾는 임의의 숫자. 이 방법 double을 사용하면 테이블의 하나에서 다른 임의의 숫자가 조회되고 완전히 상관되지 않은 최종 값 이 결과의 임의의 위치에서 변경되는 이진 숫자 의미로 쉽게 볼 수 있습니다.

// note caveats above: cache unfriendly (SLOW) but strong hashing...
size_t random[8][256] = { ...random data... };
const char* p = (const char*)&my_double;
size_t hash = random[0][p[0]] ^ random[1][p[1]] ^ ... ^ random[7][p[7]];

약하지만 종종 빠른 해싱 …

많은 라이브러리의 해싱 함수는 변경되지 않은 ( 사소한 또는 신원 해시 함수 라고도 함) 정수를 전달합니다 . 위에서 설명한 강력한 해싱과는 다른 극단입니다. 신원 해시는 매우최악의 경우 충돌이 발생하기 쉽지만, 아마도 약간의 공백이있는 경향이있는 정수 키의 경우에는 임의의 해싱 잎보다 비어있는 채로 연속 버킷에 매핑됩니다 (우리의 ~ 36.8 앞서 언급 한로드 팩터 1에서의 %)에 의해, 랜덤 매핑에 의해 달성되는 것보다 충돌이 적고 충돌 요소의 링크 된리스트가 적어진다. 또한 강력한 해시를 생성하는 데 걸리는 시간을 절약하고 키를 순서대로 조회하면 메모리 근처의 버킷에서 발견되어 캐시 적중률이 향상됩니다. 키 가 잘 증가 하지 않으면 무작위로 배치되어 버킷에 배치를 완전히 무작위 화하기 위해 강력한 해시 기능이 필요하지 않을 것이라는 희망이 있습니다.


답변

여러분은 이것을 완전히 설명하는 데 매우 가깝지만 몇 가지 사항이 누락되었습니다. 해시 테이블은 단지 배열입니다. 배열 자체는 각 슬롯에 무언가를 포함합니다. 최소한이 슬롯에 해시 값 또는 값 자체를 저장합니다. 이 외에도이 슬롯에 충돌 한 연결 / 체인 값 목록을 저장하거나 공개 주소 지정 방법을 사용할 수 있습니다. 이 슬롯에서 검색하려는 다른 데이터에 대한 포인터를 저장할 수도 있습니다.

해시 값 자체는 일반적으로 값을 배치 할 슬롯을 나타내지 않습니다. 예를 들어, 해시 값은 음의 정수 값일 수 있습니다. 분명히 음수는 배열 위치를 가리킬 수 없습니다. 또한 해시 값은 사용 가능한 슬롯보다 여러 번 큰 경향이 있습니다. 따라서 값을 입력해야하는 슬롯을 파악하기 위해 해시 테이블 자체에서 다른 계산을 수행해야합니다. 이것은 다음과 같은 계수 수학 연산으로 수행됩니다.

uint slotIndex = hashValue % hashTableSize;

이 값은 값이 들어갈 슬롯입니다. 열린 주소 지정에서 슬롯에 다른 해시 값 및 / 또는 다른 데이터가 이미 채워져 있으면 다음 슬롯을 찾기 위해 계수 작업이 다시 한 번 실행됩니다.

slotIndex = (remainder + 1) % hashTableSize;

슬롯 인덱스를 결정하는 다른 고급 방법이있을 수 있지만 이것이 내가 본 일반적인 방법입니다 … 더 나은 성능을 가진 다른 사람들에게 관심이 있습니다.

모듈러스 방법을 사용하면 크기가 1000 인 테이블이 있으면 1과 1000 사이의 해시 값이 해당 슬롯으로 이동합니다. 음수 값과 1000보다 큰 값은 잠재적으로 충돌하는 슬롯 값입니다. 이러한 일이 발생할 가능성은 해시 방법과 해시 테이블에 추가하는 총 항목 수에 따라 다릅니다. 일반적으로 해시 테이블의 크기를 추가하여 총 값 수는 크기의 약 70 %에 불과하도록하는 것이 가장 좋습니다. 해시 함수가 균일 한 분산 작업을 수행하는 경우 일반적으로 버킷 / 슬롯 충돌이 거의 발생하지 않으며 조회 및 쓰기 작업 모두에서 매우 빠르게 수행됩니다. 더할 총 값의 수를 미리 알 수없는 경우, 어떤 방법 으로든 추측 해보십시오.

이것이 도움이 되었기를 바랍니다.

PS-C #에서이 GetHashCode()방법은 속도가 느리고 테스트 한 많은 조건에서 실제 값 충돌이 발생합니다. 정말 재미있게, 자신의 해시 함수를 작성하고 해시하는 특정 데이터에 충돌하지 않도록 해보십시오. GetHashCode보다 빠르게 실행되며 상당히 균일하게 배포됩니다. int 크기의 해시 코드 값 대신 long을 사용 하여이 작업을 수행했으며 충돌이없는 해시 테이블의 최대 3,200 만 개의 전체 해시 값에서 꽤 잘 작동했습니다. 불행히도 코드가 고용주에게 속한 코드를 공유 할 수는 없지만 특정 데이터 도메인에서 코드가 가능하다는 것을 알 수 있습니다. 이것을 달성 할 수 있으면 해시 테이블은 매우 빠릅니다. 🙂


답변

이것이 내 이해에서 작동하는 방식입니다.

다음은 예입니다. 전체 테이블을 일련의 버킷으로 묘사하십시오. 영숫자 해시 코드로 구현하고 알파벳의 각 문자에 대해 하나의 버킷이 있다고 가정하십시오. 이 구현은 해시 코드가있는 각 항목을 해당 버킷에 특정 문자로 시작합니다.

200 개의 객체가 있지만 그 중 15 개만 문자 ‘B’로 시작하는 해시 코드를 가지고 있다고 가정 해 봅시다. 해시 테이블은 200 개의 객체가 아니라 ‘B’버킷에서 15 개의 객체 만 검색하고 검색하면됩니다.

해시 코드를 계산하는 한 마법은 없습니다. 목표는 다른 객체가 다른 코드를 반환하고 동일한 객체가 동일한 코드를 반환하도록하는 것입니다. 모든 인스턴스에 대해 항상 해시 코드와 동일한 정수를 반환하는 클래스를 작성할 수 있지만 하나의 거대한 버킷이되기 때문에 해시 테이블의 유용성을 본질적으로 파괴합니다.


답변

짧고 달다:

해시 테이블은 배열을 마무리하고 호출 할 수 있습니다 internalArray. 이런 식으로 항목이 배열에 삽입됩니다.

let insert key value =
    internalArray[hash(key) % internalArray.Length] <- (key, value)
    //oversimplified for educational purposes

때로는 두 개의 키가 배열의 동일한 인덱스로 해시되어 두 값을 모두 유지하려고합니다. 두 개의 값을 동일한 인덱스에 저장하고 싶습니다 internalArray. 연결된 목록의 배열을 만들어 코딩하기가 쉽습니다.

let insert key value =
    internalArray[hash(key) % internalArray.Length].AddLast(key, value)

따라서 해시 테이블에서 항목을 검색하려면 다음과 같이 쓸 수 있습니다.

let get key =
    let linkedList = internalArray[hash(key) % internalArray.Length]
    for (testKey, value) in linkedList
        if (testKey = key) then return value
    return null

삭제 작업은 작성하는 것만 큼 간단합니다. 알다시피, 링크 된 목록의 배열에서 삽입, 조회 및 제거는 거의 O (1)입니다.

internalArray의 용량이 약 85 %에 도달하면 내부 배열의 크기를 조정하고 모든 항목을 기존 배열에서 새 배열로 옮길 수 있습니다.