Karma tablo nasıl çalışır?


494

Karma tablonun nasıl çalıştığına dair bir açıklama arıyorum - benim gibi bir simpleton için düz İngilizce!

Örneğin, anahtarı alır biliyorum, hash hesaplar (nasıl bir açıklama arıyorum) ve sonra değerin saklandığı dizide nerede olduğunu bulmak için bir tür modulo gerçekleştirir, ama bu benim bilgim durur .

Herkes süreci açıklığa kavuşturabilir mi?

Düzenleme: Ben özellikle karma kodları nasıl hesaplanır sormuyorum, ama bir karma tablo nasıl çalışır genel bir bakış.


4
Son zamanlarda, karma tablolar ve stratejileri (ayrı zincirleme, doğrusal problama, çift karma) ile vurgu yaparak, verilerin nasıl saklanacağını ve aranacağını açıklayan birkaç yolu açıklayan bu ( en.algoritmy.net/article/50101/Hash-table ) makalesini yazdım. )
malejpavouk

1
Karma tabloyu, yalnızca ardışık tam sayı anahtarlarıyla sınırlı olmayan bir dizinin genişletilmiş bir sürümü olarak düşünebilirsiniz.
user253751

Yanıtlar:


913

İşte layman'ın terimleriyle ilgili bir açıklama.

Bir kütüphaneyi kitaplarla doldurmak istediğinizi varsayalım ve sadece orada doldurmakla kalmaz, aynı zamanda ihtiyacınız olduğunda onları tekrar kolayca bulmak istersiniz.

Yani, bir kitabı okumak isteyen kişi kitabın başlığını ve önyükleme yapmak için tam başlığı biliyorsa, o zaman alması gereken tek şey budur. Başlık ile kişi, kütüphanecinin yardımıyla, kitabı kolayca ve hızlı bir şekilde bulabilmelidir.

Peki, bunu nasıl yapabilirsin? Açıkçası, her kitabı nereye koyacağınıza dair bir tür liste tutabilirsiniz, ancak daha sonra kütüphanede arama yapmakla aynı sorun yaşarsınız, listeyi aramanız gerekir. Kabul edilirse, liste daha küçük ve aranması daha kolay olur, ancak yine de kütüphanenin (veya listenin) bir ucundan diğerine sıralı olarak aramak istemezsiniz.

Kitabın başlığıyla bir kerede size doğru noktayı verebilecek bir şey istiyorsunuz, bu yüzden tek yapmanız gereken sadece sağ rafa doğru ilerlemek ve kitabı almak.

Ama bu nasıl yapılabilir? Peki, kütüphaneyi doldurduğunuzda biraz önceden düşünülmüş ve kütüphaneyi doldurduğunuzda çok fazla iş var.

Kütüphaneyi bir uçtan diğer uca doldurmaya başlamak yerine, zeki küçük bir yöntem tasarlıyorsunuz. Kitabın başlığını alırsınız, bir raf numarası ve o rafa bir yuva numarası veren küçük bir bilgisayar programıyla çalıştırın. Kitabı buraya koyduğunuz yer burası.

Bu programın güzelliği, daha sonra, bir kişi kitabı okumak için geri geldiğinde, başlığı programdan bir kez daha besler ve başlangıçta verilen aynı raf numarasını ve yuva numarasını geri alırsınız ve bu kitabın bulunduğu yer.

Program, diğerlerinin de belirttiği gibi, karma algoritma veya karma hesaplama olarak adlandırılır ve genellikle içine beslenen verileri (bu durumda kitabın başlığı) alarak çalışır ve ondan bir sayı hesaplar.

Basitlik açısından, her harfi ve sembolü bir sayıya dönüştürdüğünü ve hepsini özetlediğini varsayalım. Gerçekte, bundan çok daha karmaşıktır, ama şimdilik bunu bırakalım.

Böyle bir algoritmanın güzelliği, aynı girişi tekrar tekrar beslerseniz, her seferinde aynı sayıyı tükürmeye devam etmesidir.

Tamam, temelde bir karma tablosu böyle çalışır.

Teknik şeyler aşağıdadır.

İlk olarak, numaranın boyutu var. Genellikle, böyle bir karma algoritmanın çıktısı, tipik olarak tablonuzdaki alandan çok daha büyük olan, çok sayıda bir aralık içinde bulunur. Diyelim ki kütüphanede tam bir milyon kitap için yerimiz var. Karma hesaplamanın çıktısı çok daha yüksek olan 0 ila bir milyar arasında olabilir.

Peki ne yapıyoruz? Modül hesaplaması adı verilen bir şey kullanıyoruz, temel olarak istediğiniz sayıya (yani bir milyar sayı) sayarsanız, ancak çok daha küçük bir aralıkta kalmak istiyorsanız, her daha küçük aralığın sınırına ulaştığınızda 0, ama büyük dizide ne kadar ilerlediğinizi takip etmeniz gerekiyor.

Karma algoritmanın çıktısının 0 ila 20 aralığında olduğunu ve belirli bir başlıktan 17 değerini aldığınızı varsayalım. Kütüphanenin boyutu sadece 7 kitapsa, 1, 2, 3, 4, 5, 6 sayarsınız ve 7'ye ulaştığınızda, 0'dan başlarsınız. 17 kez saymamız gerektiğinden, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3 ve son sayı 3'tür.

Tabii ki modül hesaplaması böyle yapılmaz, bölünme ve bir kalanla yapılır. 17'yi 7'ye bölmenin geri kalanı 3'tür (7, 14'te 14'e 2 kez gider ve 17 ile 14 arasındaki fark 3'tür).

Böylece kitabı 3 numaralı yuvaya koyarsınız.

Bu bir sonraki soruna yol açar. Çarpışmalar. Algoritma, kitapları boşluk bırakmanın bir yolu olmadığından, kitaplığı tam olarak doldurmaları (veya yapacaksanız karma tablo), her zaman daha önce kullanılmış bir sayıyı hesaplayacaktır. Kütüphane anlamında, bir kitap koymak istediğiniz rafa ve yuva numarasına ulaştığınızda, orada zaten bir kitap var.

Tablodaki başka bir noktayı elde etmek ( çift ​​karma ) veya sadece size verilen alana yakın bir alan bulmak için (yani, yuvayı varsayarak önceki kitabın hemen yanında) verileri başka bir hesaplamaya çalıştırmak da dahil olmak üzere çeşitli çarpışma işleme yöntemleri vardır. lineer problama olarak da biliniyordu ). Bu, kitabı daha sonra bulmaya çalıştığınızda yapmanız gereken bazı kazmalarınız olduğu anlamına gelir, ancak yine de kütüphanenin bir ucundan başlamaktan daha iyidir.

Son olarak, bir noktada kütüphaneye kütüphanenin izin verdiğinden daha fazla kitap koymak isteyebilirsiniz. Başka bir deyişle, daha büyük bir kütüphane oluşturmanız gerekir. Kütüphanedeki kesin nokta kütüphanenin tam ve geçerli boyutu kullanılarak hesaplandığından, kütüphaneyi yeniden boyutlandırırsanız, hesaplamaların noktalarını bulmak için yapılan hesaplamalardan bu yana tüm kitaplar için yeni noktalar bulmak zorunda kalabilirsiniz. değişti.

Umarım bu açıklama kovalara ve fonksiyonlara göre biraz daha yeryüzündeydi :)


Böyle harika bir açıklama için teşekkürler. 4.x .Net çerçevesinde nasıl uygulandığına ilişkin daha fazla teknik bilgiyi nerede bulabileceğimi biliyor musunuz?
Johnny_D

Hayır, sadece bir sayı. Sadece her rafı ve yuvayı 0 veya 1'den başlayarak ve o raftaki her yuva için 1 artar, sonra bir sonraki rafta numaralandırmaya devam edersiniz.
Lasse V.Karlsen

2
'Verilerin tablodaki başka bir noktayı elde etmek için başka bir hesaplamaya çalıştırılması da dahil olmak üzere çeşitli çarpışma yönetimi yöntemleri mevcuttur - başka bir hesaplama ile ne demek istiyorsun? Bu sadece başka bir algoritma mı? Tamam, varsayalım ki kitap adına göre farklı bir sayı veren başka bir algoritma kullandık. Daha sonra, bu kitabı bulabilseydim hangi algoritmayı kullanacağımı nasıl bilebilirdim? Aradığım kitap olan kitabı bulana kadar ilk algoritmayı, ikinci algoritmayı vb. Kullanırdım.
user107986

1
@KyleDelaney: Kapalı karma için hayır (çarpışmaların alternatif bir kova bularak ele alındığı, bu da bellek kullanımının sabit olduğu, ancak kovalarda arama yapmak için daha fazla zaman harcadığınız anlamına gelir). İçin açık zincirleme aka karma (kasten bazı düşman / bilgisayar korsanı tarafından çarpmasıyla, için hazırlanmış korkunç hash fonksiyonu veya girişler) patolojik bir durumda karma kova boşaltmak En çok bitebileceğini ancak toplam bellek kullanımı hiçbir kötüdür - sadece daha fazla işaretçileri yerine NULL Verilere faydalı bir şekilde indeksleme.
Tony Delroy

3
@KyleDelaney: Yorumlarınızdan haberdar olmak için "@Tony" şeye ihtiyacınız var. Zincirleme hakkında merak ettiğiniz anlaşılıyor: diyelim ki üç değer düğümümüz A{ptrA, valueA}, B{ptrB, valueB}, C{ptrC, valueC}ve üç kova içeren bir karma tablo [ptr1, ptr2, ptr3]. Yerleştirirken çarpışma olup olmadığına bakılmaksızın, bellek kullanımı sabittir. Hiç çarpışma olabilir: A{NULL, valueA} B{NULL, valueB} C{NULL, valueC}ve [&A, &B, &C]ya tüm çarpışmalar A{&B, valueA} B{&C, valueB}, C{NULL, valueC}ve [NULL, &A, NULL]: NULL kovalar "harcanır"? Biraz değil. Aynı toplam bellek kullanılır.
Tony Delroy

104

Kullanım ve Lingo:

  1. Karma tablolar , verileri (veya kayıtları) hızlı bir şekilde depolamak ve almak için kullanılır.
  2. Kayıtlar karma tuşları kullanılarak kovalarda saklanır
  3. Karma anahtarlar , kayıtta bulunan seçilen bir değere ( anahtar değeri) bir karma algoritması uygulanarak hesaplanır . Seçilen bu değer, tüm kayıtlar için ortak bir değer olmalıdır.
  4. Her kova belirli bir sırada düzenlenir birden fazla kayıt olabilir.

Gerçek Dünya Örneği:

1803 yılında kurulan ve herhangi bir bilgisayar teknolojisine sahip olmayan Hash & Co. , yaklaşık 30.000 müşterisi için ayrıntılı bilgileri (kayıtları) tutmak için toplam 300 dosya dolabına sahipti. Her dosya klasörü, 0 ile 29,999 arasında benzersiz bir sayı olan istemci numarasıyla açıkça tanımlanmıştır.

O zamanın dosyalama memurları, çalışan personel için müşteri kayıtlarını hızlı bir şekilde almak ve saklamak zorunda kaldı. Personel, kayıtlarını saklamak ve almak için karma bir metodoloji kullanmanın daha verimli olacağına karar vermişti.

Bir istemci kaydını dosyalamak için, dosyalama memurları klasörde yazılan benzersiz müşteri numarasını kullanır. Bu istemci numarasını kullanarak , içerdiği dosya dolabını tanımlamak için karma anahtarını 300 ile değiştirirler. Dosya dolabını açtıklarında, müşteri numarasına göre sıralanmış birçok klasör içerdiğini keşfederler. Doğru yeri belirledikten sonra, kolayca yerleştirilirler.

Bir müşteri kaydını almak için, dosyalama memurlarına bir kağıt parçası üzerinde bir müşteri numarası verilecektir. Bu benzersiz istemci numarasını ( karma anahtarı ) kullanarak, hangi dosya dolabının istemciler klasörüne sahip olduğunu belirlemek için 300'e kadar modüle ederler. Dosya dolabını açtıklarında, müşteri numarasına göre sıralanmış birçok klasör içerdiklerini keşfedeceklerdi. Kayıtlar arasında arama yaparken, hızlı bir şekilde istemci klasörünü bulur ve alırlar.

Bizim gerçek dünya örneğinde, bizim kovalar olan dosya dolapları ve bizim kayıtları vardır dosya klasörleri .


Hatırlanması gereken önemli bir şey, bilgisayarların (ve algoritmalarının) sayılarla dizelerden daha iyi başa çıkmalarıdır. Dolayısıyla, bir dizin kullanarak büyük bir diziye erişmek, sıralı olarak erişmekten çok daha hızlıdır.

Simon'un çok önemli olduğuna inandığım gibi, karma kısmının büyük bir alanı (keyfi uzunluk, genellikle dizeler vb.) Dönüştürmek ve endeksleme için küçük bir alana (bilinen boyutta, genellikle sayılarla) eşlemektir. Bunu hatırlamak çok önemliyse!

Yukarıdaki örnekte, 30.000 olası müşteri daha küçük bir alana eşlenmiştir.


Buradaki ana fikir, genellikle zaman alan gerçek aramayı hızlandırmak için tüm veri kümenizi segmentlere ayırmaktır. Yukarıdaki örneğimizde, 300 dosya dolabının her biri (istatistiksel olarak) yaklaşık 100 kayıt içerecektir. 100 kayda göre arama yapmak (sıradan bağımsız olarak) 30.000 ile uğraşmaktan çok daha hızlıdır.

Bazılarının bunu zaten yaptığını fark etmiş olabilirsiniz. Ancak, bir karma anahtarı oluşturmak için bir karma metodoloji tasarlamak yerine, çoğu durumda soyadının ilk harfini kullanırlar. Her biri A'dan Z'ye bir harf içeren 26 dosya dolabınız varsa, teorik olarak verilerinizi segmentlere ayırdınız ve dosyalama ve alma işlemini geliştirdiniz.

Bu yardımcı olur umarım,

Jeach!


2
Değişken “açık adresleme” veya “kapalı adresleme” (evet, üzgün ama gerçek) veya “zincirleme” olarak adlandırılan belirli bir karma tablo çarpışma önleme stratejisini tanımlarsınız. Liste bölümleri kullanmayan, ancak öğeleri "satır içi" olarak depolayan başka bir tür daha vardır.
Konrad Rudolph

2
mükemmel açıklama. ancak her dosya dolabı ortalama olarak yaklaşık 100kayıtlar içerecektir (30 bin kayıt / 300 dolap = 100). Düzenlemeye değer olabilir.
Ryan Tuck

@TonyD, bu siteye çevrimiçi olarak sha-1 gidin TonyDve metin alanına yazdığınız için bir SHA-1 karması oluşturun . Sonuna benzeyen bir şeyin oluşturulmuş bir değeri ile sonuçlanacaksınız e5dc41578f88877b333c8b31634cf77e4911ed8c. Bu, çok sayıda onaltılık 160 bitlik (20 bayt) başka bir şey değildir. Daha sonra, kaydınızı saklamak için hangi kepçenin (sınırlı miktarda) kullanılacağını belirlemek için bunu kullanabilirsiniz.
16:20

@TonyD, "karma anahtar" teriminin çelişkili bir konuda nereye atıfta bulunduğundan emin değilim? Öyleyse, lütfen iki veya daha fazla yeri belirtin. Yoksa Wikipedia gibi diğer siteler "hash değerleri, hash kodları, hash toplamları veya sadece hashes" kullanırken "biz" hash anahtarı "terimini mi kullanıyorsunuz? Öyleyse, kullanılan terim bir grup veya kuruluş içinde tutarlı olduğu sürece kimin umurunda olur. Programcılar genellikle "anahtar" terimini kullanır. Şahsen başka bir iyi seçenek "karma değeri" olacağını iddia ediyorum. Ama "karma kodu, karma toplamı veya sadece karma" kullanarak kural dışı. Kelimelere değil algoritmaya odaklanın!
20'ye ulaşın

2
@TonyD, metni " hash anahtarını 300'e kadar modüllendirecekler" olarak değiştirdim, umarım herkes için daha temiz ve net olacak. Teşekkürler!
11'e ulaşın

64

Bu oldukça derin bir teori alanı olduğu ortaya çıkıyor, ancak temel taslak basit.

Temel olarak, karma işlevi yalnızca bir boşluktan (rasgele uzunluk dizeleri gibi) şeyleri alan ve bunları dizin oluşturma için yararlı olan bir alana (işaretsiz tam sayılar, örneğin) eşleyen bir işlevdir.

Eğer karma için sadece küçük bir alanınız varsa, bunları sadece tamsayı olarak yorumlamaktan kurtulabilirsiniz ve işiniz bitmiştir (örneğin 4 bayt dizeleri)

Bununla birlikte, genellikle, çok daha geniş bir alanınız vardır. Anahtar olarak izin verdiğiniz şeylerin alanı, dizin oluşturmak için kullandığınız şeylerin alanından daha büyükse (uint32'leriniz veya herhangi bir şey), her biri için benzersiz bir değere sahip olamazsınız. İki veya daha fazla şey aynı sonuca ulaştığında, fazlalığı uygun bir şekilde ele almanız gerekir (bu genellikle bir çarpışma olarak adlandırılır ve nasıl ele alıp almadığınız, biraz da ne olduğunuza bağlı olacaktır için karma kullanarak).

Bu, aynı sonuca sahip olma olasılığının düşük olmasını istediğiniz anlamına gelir ve muhtemelen karma işlevinin hızlı olmasını da istersiniz.

Bu iki mülkü (ve birkaçını) dengelemek birçok insanı meşgul etti!

Uygulamada genellikle uygulamanız için iyi çalıştığı bilinen bir işlev bulabilmeniz ve bunu kullanabilmeniz gerekir.

Şimdi bu işi karma bir hale getirmek için: Bellek kullanımını önemsemediğinizi düşünün. Ardından, dizin oluşturma kümeniz sürece bir dizi oluşturabilirsiniz (örneğin, tüm uint32'ler). Tabloya bir şey ekledikçe, anahtarını hash eder ve dizindeki diziye bakarsınız. Orada hiçbir şey yoksa, değerini oraya koyarsın. Zaten orada bir şey varsa, bu girdiyi gerçekte hangi girdinin hangi tuşa ait olduğunu bulmak için yeterli bilgi (orijinal anahtarınız veya akıllı bir şey) ile birlikte bu adresteki bir şeyler listesine eklersiniz.

Bu nedenle, uzun bir süre boyunca, hashtable'ınızdaki (dizi) her giriş boştur veya bir giriş veya bir giriş listesi içerir. Geri alma, dizinin dizinlenmesi ve değerin döndürülmesi ya da değerlerin listesinin kaldırılması ve doğru olanın döndürülmesi gibi basit bir işlemdir.

Tabii ki pratikte bunu genellikle yapamazsınız, çok fazla bellek harcar. Böylece her şeyi seyrek bir diziye göre yaparsınız (sadece girişler gerçekte kullandığınız girişlerdir, diğer her şey örtük olarak boştur).

Bu işi daha iyi hale getirmek için çok sayıda şema ve püf noktası var, ancak bu temel bilgiler.


1
Üzgünüm, bunun eski bir soru / cevap olduğunu biliyorum, ama bu son noktayı anlamaya çalışıyorum. Bir karma tablosu O (1) zaman karmaşıklığına sahiptir. Ancak, seyrek bir dizi kullandığınızda, değerinizi bulmak için ikili bir arama yapmanız gerekmiyor mu? Bu noktada zaman karmaşıklığı O (log n) olmaz mı?
herbrandson

@herbrandson: no ... seyrek bir dizi, değerlerle nispeten az sayıda endeksin doldurulduğu anlamına gelir - yine de anahtarınızdan hesapladığınız karma değeri için doğrudan belirli dizi öğesine endeksleyebilirsiniz; yine de, Simon'un açıkladığı seyrek dizi uygulaması sadece çok sınırlı durumlarda aklı başındadır : kova boyutları bellek sayfası boyutları sıralamasındaysa (örneğin int1000'i 1 arada bir kısırlıktaki anahtarlar ve 4k sayfalar = çoğu sayfa dokundu) ve OS, adres alanı bol olduğunda tüm 0 sayfalara verimli davranır (bu nedenle tüm kullanılmayan grup sayfalarının yedek belleğe ihtiyacı yoktur)
Tony Delroy

@TonyDelroy - bu aşırı basitleştirme olduğu doğrudur, ancak fikir, pratik bir uygulama değil, ne olduklarına ve nedenlerine bir genel bakış sunmaktı. İkincisinin ayrıntıları, genişlemenizde başını sallarken daha nüanslıdır.
simon

48

Birçok cevap, ancak hiçbiri çok görsel değildir ve hash tabloları görselleştirildiğinde kolayca "tıklayabilir".

Karma tablolar genellikle bağlantılı listelerin dizisi olarak uygulanır. İnsanların adlarını saklayan bir tablo hayal edersek, birkaç eklemeden sonra, aşağıdaki gibi hafızaya yerleştirilebilir, burada ()kapalı sayılar metnin / adın hash değerleridir.

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

Birkaç nokta:

  • dizi girişlerinin her biri (indeksler [0], [1]...) bir kova olarak bilinir ve - muhtemelen boş - bağlantılı bir değerler listesi başlatır ( bu örnekte aka öğeler - insanların adları )
  • her bir değer (örneğin, "fred"karma ile 42) kepçe bağlantılı olan [hash % number_of_buckets], örneğin 42 % 10 == [2]; %bir modülo operatör kova sayısına bölünmesiyle elde edilen kalan -
  • birden çok veri değeri aynı kovada çarpışabilir ve bunlardan bağlanabilir, en çok hash değerleri modulo işleminden sonra çarpıştığından (örn 42 % 10 == [2]. ve 9282 % 10 == [2]), ancak zaman zaman hash değerleri aynı olduğundan (örn. "fred"ve "jane"her ikisi de hash ile gösterilir 42)
    • çoğu hash tablosu, biraz daha düşük performansla, ancak işlevsel karmaşa olmadan - çarpışmaları ele alır veya hashed-demetindeki bağlantılı listede zaten bulunan her bir değerle aranan veya eklenen bir değerin tam değerini (burada metin) karşılaştırarak

Bağlantılı liste uzunlukları, değerlerin sayısı ile değil yük faktörü ile ilgilidir

Tablo boyutu büyürse, yukarıdaki gibi uygulanan karma tablolar kendilerini yeniden boyutlandırma eğilimindedir (yani daha büyük bir kova dizisi oluşturma, oradan yeni / güncellenmiş bağlantılı listeler oluşturma, eski diziyi silme) değerlerin kovalara oranını korumak için (aka load faktör ) 0,5 ila 1,0 aralığında bir yerde.

Hans, aşağıdaki açıklamada diğer yük faktörleri için gerçek formülü verir, ancak gösterge değerleri için: yük faktörü 1 ve kriptografik güç karma fonksiyonu ile, 1 / e (~% 36,8) kova boş olma eğilimi gösterir, başka bir 1 / e (~% 36.8) bir eleman, 1 / (2e) veya ~% 18.4 iki eleman, 1 / (3! E) yaklaşık% 6.1 üç eleman, 1 / (4! E) veya ~% 1.5 dört eleman, 1 / (5! E) ~ .3'ün beşi vb. Vardır - boş olmayan kovalardan ortalama zincir uzunluğu ~ 1.58 olup, tabloda kaç eleman olursa olsun (100 eleman ve 100 kova veya 100 milyon olsun elemanları ve 100 milyon kova), bu nedenle arama / ekleme / silme işleminin O (1) sabit zamanlı işlemler olduğunu söylüyoruz .

Bir karma tablosu anahtarları değerlerle nasıl ilişkilendirebilir?

Yukarıda açıklandığı gibi bir karma tablo uygulaması göz önüne alındığında struct Value { string name; int age; };, yalnızca namealana bakan (yaş göz ardı ediliyor) ve eşitlik karşılaştırması ve karma işlevleri gibi bir değer türü oluşturmayı hayal edebiliriz ve sonra harika bir şey olur: tablodaki Valuegibi kayıtları saklayabiliriz {"sue", 63}, daha sonra yaşını bilmeden "dava etmek" için arama yapın, depolanan değeri bulun ve yaşını kurtarın, hatta güncelleyin
- mutlu yıllar Sue - bu karma değerini ilginç bir şekilde değiştirmez, bu nedenle Sue kaydını başka bir yere taşımamızı gerektirmez Kova.

Bunu yaptığımızda, karma tablosunu ilişkilendirilebilir bir kapsayıcı aka harita olarak kullanıyoruz ve depoladığı değerlerin bir anahtardan (ad) oluştuğu kabul edilebilir ve bir veya daha fazla alan hala - kafa karıştırıcı olarak - değer ( benim örneğimde, sadece yaş). Harita olarak kullanılan bir karma tablo uygulaması, karma harita olarak bilinir .

Bu, kendi anahtarı olarak düşünebileceğiniz "dava" gibi ayrık değerleri sakladığımız bu yanıtın önceki örnekleriyle çelişmektedir: bu tür bir kullanım karma kümesi olarak bilinir .

Karma tabloyu uygulamanın başka yolları da vardır

Tüm karma tablolar bağlantılı listeler kullanmaz ( ayrı zincirleme olarak bilinir ), ancak ana alternatif kapalı karma ( açık adresleme olarak da bilinir ) - özellikle desteklenen silme işlemleri ile - çarpışma eğilimli tuşlar / hash fonksiyonları.


Karma işlevlerle ilgili birkaç kelime

Güçlü hash ...

Genel bir amaç, en kötü durumdaki çarpışmayı en aza indirgeyen karma işlevinin işi, her zaman aynı anahtar için aynı karma değerini oluştururken, karma tablo kovalarının etrafındaki anahtarlara etkin biçimde rasgele püskürtmektir. Anahtarın herhangi bir yerinde değişen bir bit bile ideal olarak - rastgele - sonuçtaki hash değerindeki bitlerin yaklaşık yarısını çevirir.

Bu normalde benim için çok karmaşık matematiklerle düzenlenir. Anlaşılması kolay bir yoldan bahsedeceğim - en ölçeklenebilir veya önbellek dostu değil, doğası gereği zarif (bir kerelik ped ile şifreleme gibi!) - Bence yukarıda belirtilen arzu edilen nitelikleri eve götürmeye yardımcı oluyor. Diyelim ki 64 bit doubles hash ediyorsun - 256 rasgele sayının her birini 8 tablo oluşturabilirsin (aşağıdaki kod), daha sonra doublefarklı bir tabloya indekslemek için bellek temsilinin her 8 bit / 1 baytlık dilimini kullanabilirsiniz. rastgele sayılar ararsınız. Bu yaklaşımla, doubletablolardan birinde farklı bir rasgele sayı arandığında sonuçların herhangi bir yerinde (ikili rakam anlamında) biraz değiştiğini ve tamamen ilişkisiz bir nihai değer olduğunu görmek kolaydır .

// 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]];

Zayıf ama çok hızlı hash ...

Birçok kütüphanenin hash fonksiyonu tamsayıları değişmeden geçirir ( önemsiz veya kimlik hash fonksiyonu olarak bilinir ); yukarıda açıklanan güçlü karmadan diğer bir uçtur. Bir kimlik karması son dereceEn kötü durumlarda çarpışma eğilimli, ancak umut, artma eğilimi gösteren (belki de bazı boşluklarla) oldukça yaygın tamsayı anahtarları durumunda, rasgele hash yapraklarından daha az boş bırakarak ardışık kovalara eşleneceklerdir (~ 36.8 daha önce bahsedilen yük faktörü 1'de%), bu nedenle rasgele eşlemelerle elde edilenden daha az çarpışma ve daha az bağlantılı çarpışma elemanı listesine sahiptir. Güçlü bir karma oluşturmak için gereken süreyi kaydetmek de harika ve eğer anahtarlar sırayla aranırsa, hafızadaki yakındaki kovalarda bulunarak önbellek isabetlerini iyileştirir. Tuşları zaman yok güzel artırır, umut onlar tamamen kovalar içine kendi yerleştirme randomize etmek için onlar güçlü karma işlevi gerekmez rastgele yeterli olacak olduğunu.


6
Sadece söylememe izin ver: harika cevap.
CRThaze

@Tony Delroy Şaşırtıcı cevap için teşekkürler. Yine de aklımda hala bir açık nokta var. 100 milyon kova olsa bile, arama süresinin yük faktörü 1 ve kriptografik güç sağlama fonksiyonu ile O (1) olacağını söylüyorsunuz. Peki ya 100 milyonda doğru kovayı bulmaya ne dersiniz? Tüm kovalar sıralansa bile, O değil mi (log100.000.000)? Kovayı bulmak O (1) nasıl olabilir?
selman

@selman: sorunuz neden O (log100.000.000) olduğunu düşündüğünüzü açıklamak için pek çok ayrıntı sağlamaz, ancak "tüm kovalar sıralanmış olsa bile" diyorsunuz - karma tablo kovalarındaki değerlerin vardır asla hangi kepçe anahtarına hash fonksiyonu uygulanarak belirlenir değer görünür: zamanki anlamda "sıralanmış". Karmaşıklığı O (log100.000.000) olarak düşünmek, sıralanan kovalar aracılığıyla ikili bir arama yaptığınızı hayal ettiğiniz anlamına gelir, ancak karma işlem böyle çalışmaz. Belki diğer cevaplardan birkaçını okuyun ve daha anlamlı olmaya başlayıp başlamadığına bakın.
Tony Delroy

@TonyDelroy Gerçekten de, "sıralanmış kovalar" hayal ettiğim en iyi senaryo. Dolayısıyla O (log100.000.000). Ancak durum böyle değilse, uygulama milyonlar arasında ilgili grubu nasıl bulabilir? Karma işlevi bir şekilde bir bellek konumu oluşturur mu?
selman

1
@selman: bilgisayar belleği sabit zaman "rastgele erişime" izin verdiği için: bir bellek adresi hesaplayabiliyorsanız, dizinin diğer bölümlerindeki belleğe erişmeye gerek kalmadan bellek içeriğini alabilirsiniz. Yani, ister birinci kovaya, ister son kovaya veya aradaki herhangi bir kovaya erişirseniz, CPU L1 / L2 / L3 bellek önbellekleme etkilerine rağmen, aynı performans özelliklerine (gevşek, aynı miktarda zaman ayırın) yalnızca son erişilen veya tesadüfen yakındaki kovalara hızlı bir şekilde yeniden erişmenize yardımcı olmak için çalışırlar ve big-O analizi için göz ardı edilebilir).
Tony Delroy

24

Siz bunu tam olarak açıklamaya çok yakınsınız, ancak birkaç şeyi kaçırıyorsunuz. Hashtable sadece bir dizidir. Dizinin kendisi her yuvada bir şey içerir. En azından karmayı veya değerin kendisini bu yuvada saklayacaksınız. Buna ek olarak, bu yuvada çarpışan bağlantılı / zincirlenmiş bir değerler listesini de saklayabilir veya açık adresleme yöntemini kullanabilirsiniz. Ayrıca, bu yuvadan almak istediğiniz diğer verilere bir işaretçi veya işaretçi saklayabilirsiniz.

Hashvalue değerinin genellikle değerin yerleştirileceği yuvayı göstermediğine dikkat etmek önemlidir. Örneğin, bir karma değer negatif bir tamsayı değeri olabilir. Negatif bir sayı bir dizi konumuna işaret edemez. Ek olarak, karma değerleri, kullanılabilir yuvalardan birçok kez daha büyük sayılara sahip olma eğilimindedir. Bu nedenle, değerin hangi yuvaya girmesi gerektiğini anlamak için hashtable tarafından başka bir hesaplama yapılması gerekir. Bu, aşağıdaki gibi bir modül matematik işlemi ile yapılır:

uint slotIndex = hashValue % hashTableSize;

Bu değer, değerin girileceği yuvadır. Açık adreslemede, yuva zaten başka bir hashvalue ve / veya diğer verilerle doluysa, modül sonraki işlemi bulmak için bir kez daha çalıştırılır:

slotIndex = (remainder + 1) % hashTableSize;

Yuva dizinini belirlemek için daha gelişmiş yöntemler olabilir, ancak bu gördüğüm yaygın olanı ... daha iyi performans gösteren diğerleriyle ilgilenir.

Modulus yöntemiyle, 1000 büyüklüğünde bir tablonuz varsa, 1 ile 1000 arasındaki herhangi bir karma değer ilgili yuvaya gider. Negatif değerler ve 1000'den büyük değerler yuva değerleriyle çarpışabilir. Bunun gerçekleşme şansı, hem karma yönteminize hem de karma tablosuna toplam kaç öğe eklediğinize bağlıdır. Genel olarak, hashtable'ın boyutunu, kendisine eklenen toplam değer sayısı, boyutunun sadece yaklaşık% 70'ine eşit olacak şekilde yapmak en iyi uygulamadır. Karma işleviniz eşit dağılımlı iyi bir iş çıkarırsa, genellikle çok az veya hiç kova / yuva çarpışmasıyla karşılaşırsınız ve hem arama hem de yazma işlemleri için çok hızlı bir şekilde çalışır. Eklenecek toplam değer sayısı önceden bilinmiyorsa, ne anlama geliyorsa iyi bir tahmin yapın,

Umarım bu yardımcı olmuştur.

PS - C # GetHashCode()yöntem oldukça yavaş ve test birçok koşul altında gerçek değer çarpışmaları ile sonuçlanır. Biraz eğlenmek için, kendi hashunction'unuzu oluşturun ve ASLA hash ettiğiniz belirli verilerle çarpışmaya, GetHashCode'dan daha hızlı çalışmaya ve oldukça eşit bir dağılıma sahip olmaya çalışmayın. Bunu int boyut hashcode değerleri yerine uzun kullanarak yaptım ve 0 çarpışma ile hashtable içinde 32 milyon giriş hashvalues ​​oldukça iyi çalıştı. Maalesef işverenime ait olduğu için kodu paylaşamıyorum ... ancak belirli veri alanları için mümkün olduğunu açıklayabilirim. Bunu başarabildiğinizde, hashtable ÇOK hızlı. :)


yazı oldukça eski biliyorum ama birisi (kalan + 1) burada ne anlama geldiğini açıklayabilir
Hari

3
@Hari remainder, orijinal modulo hesaplamasının sonucunu ifade eder ve bir sonraki kullanılabilir yuvayı bulmak için buna 1 ekleriz.
x4nd3r

"Dizinin kendisi her yuvada bir şey içerecektir. En azından karmayı veya değerin kendisini bu yuvada saklayacaksınız." - "yuvaların" (kovaların) hiç değer depolamaması yaygındır; açık adresleme uygulamaları genellikle NULL'u veya bir işaretçiyi bağlı listedeki ilk düğüme depolar - doğrudan yuva / kovada hiçbir değer içermez. "başkaları ile ilgilenir" - gösterdiğiniz "+1" doğrusal problama , daha iyi performans gösterir: ikinci dereceli problama . "genellikle kova / yuva çarpışması olmadan çok az karşılaşır" - @% 70 kapasite, ~% 12 yuva w / 2 değer, ~% 3 ​​3 ....
Tony Delroy

"Bunu int boyutlu hashcode değerleri yerine uzun kullanarak yaptım ve 0 çarpışma ile hashtable içinde 32 milyon giriş hashvalues ​​üzerinde oldukça iyi çalıştı." - Bu , anahtarların değerlerinin, kova sayısından çok daha geniş bir aralıkta etkili bir şekilde rasgele olduğu genel durumda mümkün değildir . Farklı hash değerlerine sahip olmanın genellikle yeterince kolay longolduğuna dikkat edin (ve hash değerleri konuşmanız, başardıklarınızı gösterir), ancak mod /% işlemi olmadığında (genel durumda) hash tablosunda çarpışmadığından emin olun. ).
Tony Delroy

(Tüm çarpışmalardan kaçınmak mükemmel karma olarak bilinir . Genel olarak önceden bilinen birkaç yüz bin anahtar için pratiktir - gperf böyle bir karma işlevini hesaplamak için bir araç örneğidir. koşullar - örneğin, anahtarlarınız kendi bellek havuzunuzdaki oldukça dolu tutulan nesnelere işaretçi ise, her bir işaretçi sabit bir mesafe ile, işaretçileri bu mesafeye bölebilir ve bir dizini hafifçe seyrek bir diziye ayırarak Çarpışmalar.)
Tony Delroy

17

Benim anlayışımda şu şekilde çalışır:

İşte bir örnek: tüm tabloyu bir dizi kova olarak resmedin. Alfasayısal karma kodları olan bir uygulamanız olduğunu ve alfabenin her harfi için bir kova bulunduğunu varsayalım. Bu uygulama, karma kodu belirli bir harfle başlayan her öğeyi ilgili grupta koyar.

Diyelim ki 200 nesneniz var, ancak sadece 15 tanesinde 'B' harfiyle başlayan karma kodlar var. Karma tablonun 200 nesnenin tamamı yerine, yalnızca 'B' grubundaki 15 nesneyi bulup araması gerekir.

Hash kodunu hesaplarken, bu konuda büyülü bir şey yok. Amaç sadece farklı nesnelerin farklı kodlar döndürmesini ve eşit nesnelerin eşit kodlar döndürmesini sağlamaktır. Tüm örnekler için her zaman bir karma kodu ile aynı tamsayıyı döndüren bir sınıf yazabilirsiniz, ancak sadece bir dev kova haline geleceği için, bir karma tablonun kullanışlılığını yok edersiniz.


13

Kısa ve güzel:

Karma tablo bir diziyi tamamlar, diyelim internalArray. Öğeler diziye şu şekilde eklenir:

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

Bazen iki anahtar dizideki aynı dizine hash olur ve her iki değeri de saklamak istersiniz. internalArrayBağlantılı listeler bir dizi yaparak kodlamak için basit olan aynı dizinde her iki değeri depolamak istiyorum :

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

Yani, karma tablomdan bir öğe almak istersem, yazabilirim:

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

Silme işlemleri yazmak kadar basittir. Anlayacağınız gibi, bağlantılı listeler dizimizden ekleme, arama ve kaldırma neredeyse O (1).

İnternalArray'ımız çok dolu olduğunda, belki de yaklaşık% 85 kapasitede olduğunda, dahili diziyi yeniden boyutlandırabilir ve eski dizideki tüm öğeleri yeni diziye taşıyabiliriz.


11

Bundan daha da basit.

Bir hashtable bir diziden başka bir şey değildir (genellikle seyrek anahtar / değer çiftlerini içeren bir vektör olan) . Bu dizinin maksimum boyutu, genellikle, hashtable'ta depolanan veri türü için olası değerler kümesindeki öğe sayısından daha küçüktür.

Karma algoritması, dizide saklanacak öğenin değerlerine dayalı olarak bu diziye bir dizin oluşturmak için kullanılır.

Bu, dizideki anahtar / değer çiftlerinin vektörlerinin saklandığı yerdir. Dizideki dizinler olabilen değer kümesi, türün sahip olabileceği tüm olası değerlerin sayısından daha küçük olduğundan, karmanızın olması mümkündür. algoritması iki ayrı anahtar için aynı değeri üretecektir. Bir iyi karma algoritması (bir genel karma algoritması muhtemelen bilemeyiz belirli bilgileri olduğundan genellikle tip küme neden olan) mümkün olduğunca bu engeller, ancak önlemek mümkün değildir.

Bu nedenle, aynı karma kodu üretecek birden fazla anahtarınız olabilir. Bu olduğunda, vektördeki öğeler yinelenir ve vektördeki anahtar ile aranan anahtar arasında doğrudan bir karşılaştırma yapılır. Eğer bulunursa, büyük ve anahtarla ilişkili değer döndürülür, aksi takdirde hiçbir şey döndürülmez.


10

Bir sürü şey ve bir dizi alırsınız.

Her şey için, bunun için bir karma adı verilen bir dizin oluşturursunuz. Karma ile ilgili önemli olan şey çok fazla dağılmasıdır; iki benzer şeyin benzer karmaları olmasını istemezsiniz.

Eşyalarınızı karma ile belirtilen konuma diziniz. Birden fazla şey belirli bir karmada sarılabilir, bu nedenle şeyleri dizilerde veya genellikle bir kova olarak adlandırdığımız uygun bir şeyde saklarsınız.

Karmadaki şeyleri ararken, aynı değeri uygular, karma değerini hesaplar, sonra o konumdaki kovada ne olduğunu görür ve aradığınızı kontrol edersiniz.

Karma işleminiz iyi çalışıyorsa ve diziniz yeterince büyük olduğunda, dizideki herhangi bir dizinde en fazla birkaç şey olacaktır, bu yüzden çok fazla bakmak zorunda kalmazsınız.

Bonus puanları için, hash tablonuza erişildiğinde, (varsa) bulunan şeyi kepçenin başlangıcına taşır, böylece bir dahaki sefere kontrol edilen ilk şey olur.


1
herkesin bahsetmeyi kaçırdığı son nokta için teşekkürler
Sandeep Raju Prabhakar

4

Şimdiye kadar tüm cevaplar iyi ve hashtable'ın nasıl çalıştığının farklı yönlerine ulaşıyor. İşte size yardımcı olabilecek basit bir örnek. Diyelim ki küçük harfli alfabetik dizeleri olan bazı öğeleri anahtar olarak saklamak istiyoruz.

Simon'un açıkladığı gibi, karma işlevi geniş bir alandan küçük bir alana eşlemek için kullanılır. Örneğimiz için bir karma işlevinin basit, saf bir şekilde uygulanması, dizenin ilk harfini alıp bir tamsayıya eşleyebilir, bu nedenle "timsah", 0 karma koduna sahiptir, "arı", 1 karma koduna sahiptir. " zebra "25, vb. olurdu.

Sonra 26 kovadan oluşan bir dizimiz var (Java'da ArrayLists olabilir) ve öğeyi anahtarımızın hash koduyla eşleşen gruba koyduk. Aynı harfle başlayan bir anahtara sahip birden fazla öğemiz varsa, bunlar aynı karma koduna sahip olacaktır, bu nedenle hepsi bu karma kod için gruba gider, böylece grupta doğrusal bir arama yapılması gerekir belirli bir öğeyi bul.

Örneğimizde, alfabeyi kapsayan anahtarları olan birkaç düzine öğemiz olsaydı, çok iyi çalışırdı. Bununla birlikte, bir milyon öğemiz varsa veya tüm anahtarlar 'a' veya 'b' ile başlasaydı, hash masamız ideal olmazdı. Daha iyi performans elde etmek için farklı bir hash fonksiyonu ve / veya daha fazla kovaya ihtiyacımız var.


3

İşte ona bakmanın başka bir yolu.

A dizisi kavramını anladığınızı varsayıyorum. Bu, A'nın ne kadar büyük olursa olsun, I adımına, A [I] 'ye ulaşabileceğiniz indeksleme işlemini destekleyen bir şey.

Bu nedenle, örneğin, farklı yaşlara sahip bir grup insan hakkında bilgi depolamak istiyorsanız, basit bir yol, yeterince büyük bir diziye sahip olmak ve her kişinin yaşını dizinin bir dizini olarak kullanmak olacaktır. Bu şekilde, herhangi bir kişinin bilgilerine tek adımda erişebilirsiniz.

Ama elbette aynı yaşta birden fazla kişi olabilir, bu yüzden her girişte diziye koyduğunuz şey, o yaştaki tüm insanların bir listesidir. Böylece tek bir kişinin bilgilerine tek bir adımda ve bu listede biraz arama ("kova" olarak adlandırılır) elde edebilirsiniz. Sadece kovalar büyüyecek kadar çok insan varsa yavaşlar. Daha sonra, daha büyük bir diziye ve yaş hakkında kullanmak yerine soyadının ilk birkaç harfi gibi kişi hakkında daha tanımlayıcı bilgiler almanın başka bir yoluna ihtiyacınız vardır.

Temel fikir budur. Yaşı kullanmak yerine, değerlerin iyi yayılmasını sağlayan kişinin herhangi bir işlevi kullanılabilir. Bu karma işlevdir. Kişinin adının ASCII gösteriminin her üç bitini bir dereceye kadar karıştırmış gibi. Önemli olan tek şey, çok fazla kişinin aynı kepçeye hash istememesidir, çünkü hız küçük kalan kovalara bağlıdır.


2

Hash'in nasıl hesaplandığı genellikle hashtable'a değil, ona eklenen öğelere bağlıdır. .Net ve Java gibi çerçeveler / temel sınıf kitaplıklarında, her nesnenin bu nesne için bir karma kodu döndüren bir GetHashCode () (veya benzeri) yöntemi vardır. İdeal karma kod algoritması ve kesin uygulama, nesnede temsil edilen verilere bağlıdır.


2

Bir karma tablosu tamamen pratik hesaplamanın rasgele erişimli makine modelini izlemesi, yani bellekteki herhangi bir adresteki değerin O (1) veya sabit zamanda erişilebilmesi üzerinde çalışır.

Yani, bir anahtarlar evrenim varsa (bir uygulamada kullanabileceğim tüm olası anahtarlar kümesi, örneğin öğrenci için rulo no. onları sistemimde bellek ayırabileceğim boyutların sonlu bir sayı kümesine eşlemenin bir yolu, teorik olarak karma tablom hazır.

Genel olarak, uygulamalarda anahtar evreninin boyutu, karma tablosuna eklemek istediğim öğe sayısından çok büyüktür (örneğin, 10000 veya 100000 tamsayı değerlerine 1 GB bellek harcamak istemiyorum, çünkü bunlar 32 ikili reprsentaion biraz uzun). Yani, bu karmaşayı kullanıyoruz. Bu, büyük evrenimi hafızada kullanabileceğim küçük bir değer kümesiyle eşleştiren bir çeşit "matematiksel" işlemdir. Pratik durumlarda, genellikle bir karma tablosunun alanı (her öğenin öğe sayısı * boyutu) ile aynı "düzen" (big-O) 'dir, Yani, çok fazla bellek harcamıyoruz.

Şimdi, küçük bir kümeye eşlenen büyük bir küme, eşlemenin birebir olması gerekir. Böylece, farklı anahtarlar aynı alana tahsis edilecektir (?? adil değil). Bununla başa çıkmanın birkaç yolu var, sadece popüler iki tanesini biliyorum:

  • Bağlantılı bir listeye başvuru olarak değere ayrılacak alanı kullanın. Bu bağlantılı liste, birden çok bir eşlemede aynı yuvada bulunan bir veya daha fazla değeri depolar. Bağlantılı listede arama yapmaya gelen birine yardımcı olacak anahtarlar da bulunur. Aynı dairedeki birçok insan gibi, bir teslimatçı geldiğinde, odaya gider ve özellikle adamdan ister.
  • Tek bir değer yerine her seferinde aynı değer dizisini veren bir dizide çift karma işlevi kullanın. Bir değeri depolamaya gittiğimde, gerekli bellek konumunun boş veya dolu olduğunu görüyorum. Ücretsizse, değerimi orada saklayabilirim, eğer işgal edilirse, sıradan bir sonraki değeri alırım ve boş bir konum bulana kadar değerimi orada saklayana kadar devam ederim. Değeri ararken veya geri alırken, dizinin verdiği ile aynı yola geri dönerim ve her konumda, onu bulana veya dizideki tüm olası konumları arayana kadar vaue olup olmadığını sorarım.

CLRS ile Algoritmalara giriş, konuyla ilgili çok iyi bir fikir verir.


0

Programlama seyrini arayan herkes için, işte böyle çalışır. Gelişmiş karmaların dahili uygulaması, depolama alanı tahsisi / dağıtma ve arama için birçok karmaşıklığa ve optimizasyona sahiptir, ancak üst düzey fikir hemen hemen aynı olacaktır.

(void) addValue : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   if (bucket) 
   {
       //do nothing, just overwrite
   }
   else   //create bucket
   {
      create_extra_space_for_bucket();
   }
   put_value_into_bucket(bucket,value);
}

(bool) exists : (object) value
{
   int bucket = calculate_bucket_from_val(value);
   return bucket;
}

nerede calculate_bucket_from_val()tüm benzersizliği sihirli gerçekleşmesi gerekir karma işlevidir.

Temel kural şudur: Belirli bir değerin eklenmesi için, Kepçe, MAĞAZA OLMASI gereken DEĞERDEN EŞSİZ ve ÇALIŞABİLİR olmalıdır.

Kova, değerlerin saklandığı herhangi bir alandır - burada bir dizi dizini olarak int tuttum, ancak belki de bir bellek konumu.


1
"temel kural şudur: Belirli bir değerin eklenmesi için, Kepçe, MAĞAZA OLMASI gereken DEĞERDEN EŞSİZ ve ÇALIŞABİLİR olmalıdır." - Bu , derleme zamanında bilinen yalnızca birkaç yüz veya bin değer için mümkün olan mükemmel bir karma işlevini tanımlar . Çoğu karma tablo çarpışmalarla başa çıkmak zorundadır . Ayrıca, karma tablolar boş olsun veya olmasın tüm kovalar için yer ayırma eğilimindeyken, sahte kodunuz create_extra_space_for_bucket()yeni anahtarların eklenmesi sırasında bir adımı belgeler . Kovalar işaretçiler olabilir.
Tony Delroy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.