【c#ハッシュテーブル】

C# でハッシュ テーブルのデータ構造を実装するコレクション クラスは次のとおりです。
(1) System.Collections.Hashtable
(2) System.Collections.Generic.Dictionary<TKey,TValue>
前者は一般的なタイプのハッシュ テーブル、後者はハッシュ テーブルの一般的なタイプです。ハッシュ テーブルの一般的な型のバージョンです。Dictionary と Hashtable の違いは、ジェネリックと非ジェネリックの単純な違いではなく、まったく異なるハッシュ競合解決方法を使用します。

8.3 ハッシュ競合の解決

ハッシュ関数の目的は競合を最小限に抑えることですが、実際のアプリケーションでは競合は避けられないため、競合が発生した場合には、対応する解決策が必要です。競合の可能性は、次の 2 つの要因に関連しています。

(1) 充填率 α: いわゆる充填率とは、ハッシュ アドレス空間のサイズ m に対するハッシュ テーブルに格納されるレコード数 n の比率、つまり α = n / m を指します。 αが小さいほど競合が発生する可能性が低く、αが大きいほど競合が発生する可能性が高くなります(最大値は1)。これは、α が小さいほどハッシュ テーブル内の空きユニットの割合が多くなり、挿入されるレコードと挿入されるレコードが競合する可能性が低くなり、逆に α が大きいほど、ハッシュテーブルの空きユニットの割合が小さくなるほど、挿入されるレコードと挿入されるレコードが競合する可能性が高くなり、一方、αが小さいほど、ストレージスペースの利用率が低いほど、ストレージスペースの利用率が低くなり、逆にストレージスペースの利用率が低くなります。競合の発生を減らし、記憶領域の使用率を向上させるために、α は通常 0.6 ~ 0.9 の範囲で制御されますが、C# の HashTable クラスでは α の最大値が 0.72 に設定されています。

(2) 使用するハッシュ関数に関係します。ハッシュ関数が適切に選択されていれば、ハッシュ アドレスがハッシュ アドレス空間内でできるだけ均等に分散されるため、競合の発生が減少しますが、そうでない場合は、ハッシュ アドレスが特定の領域に集中し、競合の発生が増加する可能性があります。可能性。

競合解決技術は、オープン ハッシュ法 (チェーン アドレス法とも呼ばれます) とクローズド ハッシュ法 (オープン アドレス法とも呼ばれます) の 2 つの主なカテゴリに分類できます。ハッシュ テーブルは、配列を使用して実装された連続的なアドレス空間です。2 つの競合解決手法の違いは、競合する要素が配列の空間の外に格納されるか、空間内に格納されるかです。

(1) ハッシュ法と競合する要素は配列空間の外に格納されます。「オープン」という言葉は、競合する要素を格納するために追加のスペースを「開く」必要があると理解できます。

(2) クローズドハッシュ法と競合する要素は配列空間に格納されます。「閉じた」という言葉は、競合するかどうかに関係なく、すべての要素が配列内で「閉じられている」ことを意味すると理解できます。オープン アドレス法とも呼ばれるクローズド ハッシュは、競合に関係なく、配列空間がすべての要素に対してオープンであることを意味します。

 

8.3.1 クローズドハッシュ方式(オープンアドレス方式)

クローズド ハッシュでは、すべての要素がハッシュ テーブル配列に保存されます。競合が発生すると、競合箇所の近くでレコードを保存できる空のユニットが検索されます。「次の」開口部を見つけるプロセスはサウンディングと呼ばれます。上記の方法は次の式で表すことができます。

hi=(h(キー)+di)%m i=1,2,...,k (k≤m-1)

このうち、h (key) はハッシュ関数、m はハッシュ テーブルの長さ、di はインクリメントのシーケンスです。di の値の違いに応じていくつかの検出方法に分けることができますが、ここでは Hashtable で使用されるダブルハッシュ方法のみを紹介します。

  • ダブルハッシュ

ダブル ハッシュ (2 次ハッシュとも呼ばれる) は、クローズド ハッシュ方式の中でも優れた方式であり、キーワードの別のハッシュ関数値を増分として使用します。2 つのハッシュ関数が h1 と h2 であると仮定すると、取得される検出シーケンスは次のようになります。

(h1(キー)+h2(キー))%m,(h1(キー)+2h2(キー))%m,(h1(キー)+3h2(キー))%m,…

このうち、mはハッシュテーブル長です。次のオープン アドレスを検出するためのダブル ハッシュの式は次のとおりであることがわかります。

(h1(キー) + i * h2(キー)) % m (1≤i≤m-1)

h2 を定義する方法はたくさんありますが、どのような方法を使用する場合でも、h2 (キー) の値は m と互いに素になる必要があります (共素とも呼ばれ、2 つの数値の最大公約数が 1 であることを意味します)。 2 つの数値には共通因数がありません。1 を除いて、競合するシノニム アドレスをハッシュ テーブル全体に均等に分散させることができます。そうでない場合は、シノニム アドレスの循環計算が発生する可能性があります。m が素数の場合、h2 が 1 から m-1 までの数値は m と互いに素であるため、h2 は次のように単純に定義できます。

h2(キー) = キー% (m - 2) + 1

The Mother of All Things オブジェクト クラスは GetHashCode() メソッドを定義します。このメソッドのデフォルトの実装では、オブジェクトの存続期間中に変更されないように、一意の整数値を返します。すべての型はオブジェクトから直接的または間接的に派生するため、すべてのオブジェクトがこのメソッドにアクセスできます。当然のことながら、文字列やその他の型は一意の数値で表すことができます。つまり、GetHashCode() メソッドは、すべてのオブジェクトのハッシュ関数の構築方法を統一します。もちろん、GetHashCode() メソッドは仮想メソッドであるため、このメソッドをオーバーライドして独自のハッシュ関数を構築することもできます。

 

8.4.1 ハッシュテーブルの実装原理

ハッシュ テーブルは、クローズド ハッシュ メソッドを使用して競合を解決します。構造バケットを通じてハッシュ テーブル内の 1 つの要素を表します。この構造には、次の 3 つのメンバーがあります。

(1) key: キー、つまりハッシュテーブル内のキーワードを示します。

(2)val:値、すなわちキーワードに対応する値を表す。

(3) hash_coll: キーに対応するハッシュコードを表すint型です。

int 型は 32 ビットの記憶領域を占有し、その最上位ビットは符号ビットで、「0」の場合は正の整数、「1」の場合は負の整数を意味します。Hash_coll は、現在位置で競合が発生しているかどうかを最上位ビットで表し、「0」、つまり正の数値の場合は競合なし、「1」の場合は競合なしを意味します。現在の位置に競合があることを示します。ハッシュ コードを格納し、競合が発生したかどうかをマークするためにビットが特別に使用される理由は、主にハッシュ テーブルの操作効率を向上させるためです。これについては後述します。

Hashtable は競合を解決するために二重ハッシュを使用しますが、上記の二重ハッシュ方法とは少し異なります。アドレスを検出する方法は次のとおりです。

h(キー, i) = h1(キー) + i * h2(キー)

ハッシュ関数 h1 と h2 の式は次のとおりです。

h1(キー) = キー.GetHashCode()

h2(キー) = 1 + (((h1(キー) >> 5) + 1) % (ハッシュサイズ - 1))

2 次元ハッシュを使用しているため、h(key, i) の最終値はハッシュサイズより大きくなる可能性があるため、h(key, i) に対してモジュロ演算を実行する必要があります。最終的に計算されたハッシュ アドレスは次のとおりです。

ハッシュアドレス = h(key, i) % ハッシュサイズ

[注意]: バケット構造体の hash_coll フィールドには、ハッシュアドレスの代わりに h(key, i) の値が格納されます。

ハッシュ テーブルのすべての要素は、buckets という名前のバケット配列 (データ バケットとも呼ばれます) に格納されます。以下は、データ要素が (キー、値、ハッシュ コード) を使用する、ハッシュ テーブルへのデータの挿入および削除のプロセスを示しています。表現します。この例では、ハッシュテーブルの長さが 11、つまり hashsize = 11 であると仮定していることに注意してください。ここでは最初の 5 要素のみが表示されます。

                (1) 要素(k1, v1, 1)と(k2, v2, 2)を挿入します。

挿入された 2 つの要素間に競合がないため、h1(key) % hashsize の値がハッシュ コードとして直接使用され、h2(key) は無視されます。効果を図 8.6 に示します。

                (2) 要素の挿入(k3、v3、12)

     新しく挿入された要素のハッシュ コードは 12 です。ハッシュ テーブルの長さは 11、12 % 11 = 1 であるため、新しい要素はインデックス 1 に挿入される必要がありますが、インデックス 1 はすでに k1 によって占有されているため、h2 を使用する必要があります。 ( キー) ハッシュ コードを再計算します。

h2(キー) = 1 + (((h1(キー) >> 5) + 1) % (ハッシュサイズ - 1))

h2(キー) = 1 + ((12 >> 5) + 1) % (11 - 1)) = 2

  • 新しいハッシュ アドレスは h1(key) + i * h2(key) = 1 + 1 * 2 = 3 なので、k3 はインデックス 3 に挿入されます。インデックス 1 で競合があるため、最上位ビットを「1」に設定する必要があります。

(10000000000000000000000000000001)2 = (-2147483647)10

最終的な効果を図 8.7 に示します。

( 3) 要素の挿入 (k4、v4、14)

k4 のハッシュ コードは 14 (14 % 11 = 3) で、インデックス 3 はすでに k3 によって占有されているため、アドレスは 2 次ハッシュを使用して再計算され、新しいアドレスは 14 になります。インデックス 3 に競合があるため、上位ビットを「1」に設定する必要があります。

(12)10 = (00000000000000000000000000001100)2 上位「1」以降

(10000000000000000000000000001100)2 = (-2147483636)10

最終的な効果を図 8.8 に示します。

(4) 要素k1、k2を削除する

Hashtable が競合する要素 (hash_coll は負の数) を削除すると、要素のキーが配列バケットを指すようにし、同時に要素の hash_coll の下位 31 ビットをすべて「0」に設定し、最上位を保持します。元の hash_coll が負の数であるため、最上位ビットは「1」になります。

(1000000000000000000000000000000)2 = (-2147483648)10

hash_coll 値が -2147483648 であるかどうかを単純に判断するだけでは、インデックスが空であるかどうかを判断することはできません。インデックス 0 で競合がある場合、その hash_coll 値も -2147483648 となり、キーがバケットを指すことになるためです。ここで、キーがバケットを指し、hash_coll 値が -2147483648 である欠員は、「競合欠員」と呼ばれます。図 8.8 に示すように、k1 が削除されると、インデックス 1 のギャップは競合ギャップになります。

Hashtable が競合せずに要素を削除すると (hash_coll は正の数)、キーと値の両方が null に設定され、hash_coll の値は 0 に設定されます。このような競合のない空きを「競合のない空き」と呼びます。図 8.9 に示すように、k2 が削除された後、インデックス 2 は競合のない空きです。ハッシュテーブルが初期化されると、バケット配列内のすべての位置が競合します。 -無料、空席あり。

ハッシュ テーブルがキーワードで要素を検索する場合、まずキーのハッシュ アドレスを計算し、次にこのハッシュ アドレスを通じて配列の対応する位置に直接アクセスし、2 つのキーの値を比較し、それらが同じであれば検索します。成功して返され、それらが異なる場合、次のステップは hash_coll の値に基づいて決定されます。hash_coll が 0 または正の数の場合は、競合がなくこの時点では検索が失敗することを示し、hash_coll が負の数の場合は、競合があることを示します。このとき、計算を続ける必要があります。ハッシュ アドレスから 2 次ハッシュを介して検索するなど、対応するものが見つかるまで続きます。キーの値は、検索が成功したことを示します。検索プロセス中に hash_coll が正の場合、または 2 次ハッシュの回数が正の場合計算された値がハッシュ テーブルの長さと等しい場合、検索は失敗します。hash_coll の上位ビットを競合ビットとして設定する主な目的は、検索速度を向上させ、無意味な 2 次ハッシュの複数回の計算を避けることであることがわかります。

8.4.2 ハッシュテーブルのコード実装

ハッシュテーブルの実装は比較的複雑ですが、コードを簡略化するため、この例では一部のエラー判定を無視していますので、テスト時にキー値を空に設定しないでください。

1    using  System  ;
2    public  class  Hashtable
3    {
4        private  structbucket 5        { 6 public  Object key;  //キー7 public  Object val;  //8 public int  hash_coll;  //ハッシュ コード9        } 10 privatebucket  []buckets ;  //ハッシュテーブルのデータを格納する配列(データバケット)11 private int  count;  //要素数12 private int

           
           
            

      
       
        loadsize;  //現在保存できる要素の数は
13 です      private  float  loadFactor;  //フィルファクタ
14       //デフォルトの構築メソッド
15       public  Hashtable() :  this ( 0 1.0f ) { }
16       //構築メソッド容量を指定する
17       public  Hashtable( int  Capacity,  floatloadFactor  )
18       {
19           if  ( ! (loadFactor  >=  0.1f  &&  loadFactor  <=  1.0f ))
20               throw new  ArgumentOutOfRangeException(
21                   "フィル ファクターは 0.1 から 1 の間でなければなりません" );
22           this .loadFactor  =  loadFactor  >  0.72f  ?  0.72f  :loadFactor;
23           //容量に応じてテーブルの長さを計算します
24           double  rawsize  =  Capacity  /  this .loadFactor;
25           int  hashsize  =  (rawsize  >  11 ?  //テーブルの長さは 11 より大きい素数です
26               HashHelpers.GetPrime(( int )rawsize) :  11 ;
27buckets =           newbucket   [hashsize];  //コンテナを初期化する28loadsize =  ( int )( this .loadFactor  *  hashsize); 29 }       30           public  virtual void Add  (Object key, Object value)  // Add 31 {       32    Insert           ( key , value,  true ); 33       } 34 //ハッシュコードの初期化35 private uint  InitHash(Object key, int  hashsize, 36 


        



      
       
          out  uint  seed, out  uint  incr)
37       {
38           uint  hashcode  =  ( uint )GetHash(key)  &  0x7FFFFFFF //絶対値を取得します
39           seed  =  ( uint )hashcode;  // h1
40           incr  =  ( uint )( 1  +  ( ((シード >>  5 ) + 1 %  (( uint )hashsize - 1 )));// h2
41           return  hashcode;  //ハッシュ コードを返す
42       }
43       public  virtual  Object  this [オブジェクト キー]  //インデクサー
44       {
45           get
46           {
47               uint  seed;  // h1
48               uint  incr;  // h2
49               uint  hashcode  =  InitHash(key,buckets.Length, 
50                   out シード、  out  incr);
51               int  ntry  = 0 // h(key,i) 52bucket               b; 53 int  bn  =  ( int )(seed  %  ( uint )buckets.Length);  // h(key,0) 54 do 55の i 値を表すために使用されます              { 56                   b  =  buckets[bn];  57 if  (b.key  == null // b は競合のないスロット58                   {   //対応するキーが見つからない、return empty 59 return null ; 60                   }

              
              


                   

                       

61                   if  (((b.hash_coll  &  0x7FFFFFFF == ハッシュコード)  &&
62                       KeyEquals(b.key, key))
63                   {    //查找成功
64                       return  b.val;
65                   }
66                   bn  =  ( int )((( long )bn  +  incr)  %  
67                       ( uint )buckets.Length); // h(key+i)
68               }  while  (b.hash_coll  < 0  &&  ++ ntry  <  buckets.Length);
69               return  null ;
70           }
71           set
72           {
73               Insert(key, value,  false );
74           }
75       }
76       private  void  Expand()  //展開
77       {    //新規作成容量
以前           容量  約 2倍 です           。

80       }
81       private  void  rehash( int  newsize)  //按新容量扩容
82       {
83           Bucket[] newBuckets  = 新しい バケット[newsize];
84           for  ( int  nb  =  0 ; nb  <  buckets.Length; nb ++ )
85           {
86              バケット oldb  = バケット[nb];
87               if  ((oldb.key  !=  null &&  (oldb.key  != バケット))
88               {
89                   putEntry(newBuckets, oldb.key, oldb.val, 
90                       oldb.hash_coll  &  0x7FFFFFFF );
91               }
92           }
93          バケット =  newBuckets;
94          ロードサイズ =  ( int )(loadFactor  *  newsize);
95           return ;
96       }
97       //古い配列の要素を新しい配列に追加します
98       private  void  putEntry(bucket[] newBuckets, Object key, 
99          オブジェクト nvalue,  int ハッシュコード)
100      {
101          uint シード =  ( uint ) ハッシュコード; // h1
102          uint  incr  =  ( uint )( 1  +  (((seed  >>  5 +  1 %  
103              (( uint )newBuckets.Length  -  1 ))); // h2
104          int  bn  =  ( int )(seed  %  (uint )newBuckets.Length); //ハッシュ アドレス
105          do
106          {    //現在の位置が競合空席または競合のない空席の場合、新しい要素
107を追加できます             if  ((newBuckets[bn].key  ==  null ||  
108                  (newBuckets[bn].key  == バケット))
109              {    //値を割り当てる
110                  newBuckets[bn].val  =  nvalue;
111                  newBuckets[bn].key  =  key;
112                  newBuckets[bn].hash_coll  |= ハッシュコード;
113                 リターン;
114              }
115              //現在の位置に他の要素が既に存在する場合
116              if  (newBuckets[bn].hash_coll  >=  0 )
117              {    // hash_coll の上位ビットを 1 に設定
118                  newBuckets[bn].hash_coll  |=  
119                      unchecked (( int ) 0x80000000 );
120              }
121              //セカンダリ ハッシュ h1(key)+h2(key)
122              bn  =  ( int )((( long )bn  +  incr)  %  ( uint)newBuckets.Length);
123          }  while  ( true );
124      }
125      protected  virtual  int  GetHash(Object key)
126      {    //ハッシュコードを取得する
127          return  key.GetHashCode();
128      }
129      protected  virtual  bool  KeyEquals(Object item , オブジェクト key)
130      {    // 2 つのキーが等しいかどうかを判断するために使用されます
131          return  item  ==  null  ?  false  : item.Equals(key);
132      }
133     // add が true の場合は要素の追加に使用され、add が false の場合は要素の値の変更に使用されます
134      private  void  Insert(Object key, Object nvalue,  bool  add)
135      {    //上限の場合保存できる要素数を超えた場合、拡張
136          if  (count  >=  loadsize)
137          {   
138              Expand();
139          }
140          uint  seed;  // h1
141          uint  incr;  // h2
142          uint  hashcode  =  InitHash (キー、バケット.長さ、アウト シード、 アウト 増分) ;
143          int  ntry  =  0 // h(key,i) の i 値を表すために使用されます
144          int  emptySlotNumber  =  - 1 //空のスロットを記録するために使用されます
145          int  bn  =  ( int )(seed  %  ( uint )buckets. Length );  //インデックス番号
146          do
147          {    //競合する空きがある場合は、同じキーが存在するかどうかを判断するために後方検索を続ける必要があります
148              if  (emptySlotNumber  ==  - 1  &&  (buckets[bn].key  = = バケット)  &&
149                  (buckets[bn].hash_coll  <  0 ))
150              {
151                  emptySlotNumber  =  bn;
152              }
153              if  (buckets[bn].key  ==  null //追加する前に重複キーがないことを確認してください
154              {
155                  if  ( emptySlotNumber  !=  - 1 //前の空のスロットを使用
156                      bn  =  emptySlotNumber;
157buckets                  [bn].val  =  nvalue;
158                 Buckets[bn].key  =  key;
159                  Buckets[bn].hash_coll  |=  ( int )hashcode;
160                  count ++ ;
161                  return ;
162              }
163              //重複キーが見つかりました
164              if  (((buckets[bn].hash_coll  &  0x7FFFFFFF ) == hashcode)  &&
165                  KeyEquals(buckets[bn].key, key))
166              {    //要素を追加している状態の場合、キーが重複しているためエラー167が報告されますif  (add) 168
                 
                 {
169                      throw  new  ArgumentException( "重複したキー値が追加されました! " );
170                  }
171buckets                  [bn].val  =  nvalue;  //バッチキーの要素を変更します
172                  return ;
173              }
174              //競合がある場合、 set hash_coll 最上位ビットは 1
175              if  (emptySlotNumber  ==  - 1 )
176              {
177                  if  (buckets[bn].hash_coll  >=  0 )
178                  {
179buckets                      [bn].hash_coll  |=  unchecked (( int ) 0x80000000 );
180                  }
181              }
182              bn  =  ( int )((( long )bn  +  incr)  %  ( uint )buckets.Length); //第 2 度 ha 183          }  while  ( ++ ntry  <  Buckets.Length); 184 throw
new InvalidOperationException  ( "追加に失敗しました! "
          );
185      }
186      public  virtual  void  Remove(Object key)  //要素を削除します
187      {
188          uint  seed;  // h1
189          uint  incr;  // h2
190          uint  hashcode  =  InitHash(key,buckets.Length, out  seed,  out  incr);
191          int  ntry  =  0 // i in h(key,i)
192         バケット b;
193          int  bn  =  ( int)(seed  %  ( uint )buckets.Length);  //ハッシュアドレス
194          do
195          {
196              b  =  buckets[bn];
197              if  (((b.hash_coll  &  0x7FFFFFFF == ハッシュコード)  &&
198                  KeyEquals(b. key , key))  //対応するキー値が見つかった場合
199              {    //最上位ビットを保持し、残りを 0 にクリア
200buckets                  [bn].hash_coll  &=  unchecked (( int ) 0x80000000 );
201                 if  (buckets[bn].hash_coll  !=  0 //もともと競合があった場合
202                  {    //キーポイントをバケットに設定する
203                      Buckets[bn].key  =  buckets;
204                  }
205                  else  //もともと競合はなかった
206                  {    //キーを空に設定します
207                      Buckets[bn].key  =  null ;
208                  }
209                  Buckets[bn].val  =  null ;   //対応する「値」を解放します。
210                 カウント-- ;
211                 リターン;
212              }  //二度哈希
213              bn  =  ( int )((( long )bn  +  incr)  %  ( uint )buckets.Length);
214          }  while  (b.hash_coll  <  0  &&  ++ ntry  <  buckets.Length);
215      }
216     パブリック オーバーライド 文字列 ToString()
217     ​​ {
文字         219 for  ( int  i  = 0 ; i  <  buckets.Length; i ++ ) 220 { 221 if  ( buckets [          i ] .key !  =   null  && buckets [  i].key  != バケット) 222              {    //空でない場合はインデックス、キー、値、hash_coll を出力します223                  s  += string .Format( " {0,-5}{1,-8}{2,-8}{3,-8}\r \ n " 224                      i.ToString()、buckets[i].key.ToString()、
          

               

 

225buckets                      [i].val.ToString(), 
226buckets                      [i].hash_coll.ToString());
227              }
228              else
229              {    //空の場合、インデックスと hash_coll を出力します
230                  s  +=  string .Format ( " {0,-21}{1,-8}\r\n " , i.ToString(),
231                      Buckets[i].hash_coll.ToString());
232              }
233          }
234          return  s;
235      }
236     パブリック 仮想 整数の 数 //属性
237      {    //要素数を取得
238          get  {  return  count; }
239      }
240  }

HashtableArrayListの実装は似ており、たとえば、どちらも配列に基づいてさらに抽象化されており、容量を自動的に指数関数的に拡張できます。

おすすめ

転載: blog.csdn.net/qq_40097668/article/details/124383878