230 likes | 475 Views
アルゴリズムとデータ構造 1. 2006 年 7 月 4 日 酒居敬一 ( sakai.keiichi@kochi-tech.ac.jp ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2006/index.html. 比較に頼らない方法 ハッシュ法. データに関する情報を比較によらないで取得 データの性質が明らかであれば O(1) に近づくことができる ハッシュ関数が命 ハッシュ関数を設計するときに、データに関する情報を取得し盛り込む (連想記憶機構があれば … ).
E N D
アルゴリズムとデータ構造1 2006年7月4日 酒居敬一(sakai.keiichi@kochi-tech.ac.jp) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2006/index.html
比較に頼らない方法ハッシュ法 • データに関する情報を比較によらないで取得 • データの性質が明らかであればO(1)に近づくことができる • ハッシュ関数が命 • ハッシュ関数を設計するときに、データに関する情報を取得し盛り込む • (連想記憶機構があれば…)
ハッシュテーブル(123ページ以降で詳しく) • 並べたデータを「鍵」を用いてアクセス • 「鍵」からハッシュテーブル内のデータを特定するための関数 → ハッシュ関数 • ハッシュ関数 • 合同法(除算法) • H(k) = k mod n (k: 鍵, n:素数) • 平方抽出法 • 重ね合わせ法
データ データ データ データ データ データ データ ハッシュテーブルのイメージ
衝突 • 異なった鍵を用いてもハッシュ値が同じ • 先に格納されているデータ → ホーム • 後から格納するデータ → シノニム • 衝突を解決する方法 • 分離連鎖法 • 空き番地法 • 衝突を回避しない例(ハッシュとは呼ばない) • set associative cache • 衝突時、セット内から置き換えるラインを選択する
ハッシュ 2-way set associative cache アドレス ハッシュ値 ライン内アドレス ハッシュ関数 入力:アドレス値 セットあたりのway数は2
public class MyHashtable { private MyHashtable() { } public MyHashtable(int aMaxSize) { this.table = new AddressData[aMaxSize]; } public AddressData get(String aKey) { if(null == aKey){ throw new NullPointerException(); } return this.table[this.calculateHashCode(aKey)]; } public void printAll() { for(int count = 0; count < this.table.length; count++){ System.out.println(count+1 + "\t" + this.table[count]); } System.out.println(); } private AddressData[] table; }
public boolean put(AddressData anAddressData) { if(null == anAddressData){ return false; } this.table[this.calculateHashCode(anAddressData.getName())] = anAddressData; return true; } public boolean remove(String aKey) { if(null == aKey){ return false; } this.table[this.calculateHashCode(aKey)] = null; return true; } private int calculateHashCode(String aKey) { if(null == aKey){ throw new NullPointerException(); } int intkey = 0; for(int count = 0; count < aKey.length(); count++){ intkey += 0xFFFF & aKey.charAt(count); } return intkey % this.table.length; } [sakai@star]$ java MyHashtableTest 住所データを格納 1 null 2 null 3 null 4 杉山: 稲城,東京 208 5 null 6 null 7 ONGS Inc.: 渋谷,東京 151 8 後藤: 川崎,神奈川 214 9 null 10 null 11 佐々木: 座間,神奈川 228 12 小澤: 多摩,東京 206 13 null 14 null 15 null 16 null 17 null 18 null 19 null
ハッシュ テーブル 連結リスト 分離連鎖法(チェイン法) 図2.7.1 教科書125ページ
public class AddressData { public AddressData(String aName, String aMetropolice, String aCity, String aZipcode) { if((null == aName) || (null == aMetropolice) || (null == aCity)|| (null == aZipcode)){ throw new NullPointerException(); } this.name = aName; this.metropolice = aMetropolice; this.city = aCity; this.zipcode = aZipcode; } public String getName() { return this.name; } public String getAddress() { return this.city + "," + this.metropolice + " " + this.zipcode; } public String toString() { return this.name + ": " + this.city + "," + this.metropolice + “ " + this.zipcode; } private String city; private String metropolice; private String name; private String zipcode; } • 住所録として以下の項目を持つ • 名前 • 市 • 都道府県 • 郵便番号
public class ChainHashtable { private ChainHashtable() { } public ChainHashtable(int aMaxSize) { this.table = new MyLinkedList[aMaxSize]; } private MyLinkedList[] table; } public boolean put(AddressData anAddressData) { if(null == anAddressData){ return false; } int hashCode = this.calculateHashCode(anAddressData.getName()); if(null == this.table[hashCode]){ this.table[hashCode] = new MyLinkedList(); } this.table[hashCode].insert(anAddressData); return true; }
public AddressData get(String aKey) { if(null == aKey){ throw new NullPointerException(); } MyLinkedList list = this.table[this.calculateHashCode(aKey)]; if(null == list){ return null; } int limit = list.size(); int count = 1; AddressData address = null; while(count <= limit){ address = (AddressData)list.get(count); if(address.getName().equals(aKey)){ return address; } ++count; } return null; } private int calculateHashCode(String aKey) { if(null == aKey){ throw new NullPointerException(); } int intkey = 0; for(int count = 0; count < aKey.length(); count++){ intkey += 0xFFFF & aKey.charAt(count); } return intkey % this.table.length; }
public boolean remove(String aKey) { if(null == aKey){ return false; } MyLinkedList list = this.table[this.calculateHashCode(aKey)]; if(null == list){ return false; } int limit = list.size(); int count = 1; AddressData address = null; while(count <= limit){ address = (AddressData)list.get(count); if(address.getName().equals(aKey)){ return list.remove(count); } ++count; } return false; } public void printAll() { for(int count = 0; count < this.table.length; count++){ if(null == this.table[count]){ System.out.println(this.table[count]); }else{ this.table[count].printAll(); } } System.out.println(); }
[sakai@star]$ java ChainHashtableTest 住所データを格納 杉山: 稲城,東京 208 佐々木: 座間,神奈川 228 → 小澤: 多摩,東京 206 ONGS Inc.: 渋谷,東京 151 → 後藤: 川崎,神奈川 214 データの取得: 後藤 後藤: 川崎,神奈川 214 杉山: 稲城,東京 208 佐々木: 座間,神奈川 228 → 小澤: 多摩,東京 206 ONGS Inc.: 渋谷,東京 151 → 後藤: 川崎,神奈川 214 データの削除: ONGS Inc. 杉山: 稲城,東京 208 佐々木: 座間,神奈川 228 → 小澤: 多摩,東京 206 後藤: 川崎,神奈川 214 [sakai@star]$ ハッシュ表の大きさが3なので衝突が起きたときはリスト保持
ハッシュ 再ハッシュ 再ハッシュ 開番地法 既にデータが 格納されている キー • 再ハッシュの方法 • 線形走査法 • 二重ハッシュ法 • 均一ハッシュ法 ここも既にデータが 格納されている この場所は空なので ここに格納する 図2.7.4 教科書131ページ および教科書134ページ
キー ハッシュ 再ハッシュ 削除 削除フラグを格納 削除フラグ 再ハッシュ 空き番地法を用いた場合の削除 同じハッシュ値だけど、これじゃない。 データは消えてるけど、これでもない。 削除したい これだっ! このデータを 探索したい 教科書134ページ
public class OpenAddressHashtable { public OpenAddressHashtable(int aMaxSize) { this.table = new AddressData[aMaxSize]; } private final AddressData removedData = new AddressData("", "", "", ""); private AddressData[] table; } private int calculateHashCode(String aKey) { if(null == aKey){throw new NullPointerException();} int intkey = 0; for(int count = 0; count < aKey.length(); count++){ intkey += 0xFFFF & aKey.charAt(count); } return intkey % this.table.length; } 1回目のハッシュは剰余演算 2回目以降は一定間隔離す 距離は前の値から1~3 1固定の場合は線形走査法と同じ 固定値にしないときは二重ハッシュ法 private int calculateHashCodeAgain(String aKey, int aHashCode) { if(null == aKey){throw new NullPointerException();} int intkey = 0; for(int count = 0; count < aKey.length(); count++){ intkey += 0xFFFF & aKey.charAt(count); } int rehashCode = (aHashCode + 3 - (intkey % 3)) % this.table.length; System.err.println("再ハッシュ: " + aKey + " " + aHashCode + “ → " + rehashCode); return rehashCode; }
public boolean put(AddressData anAddressData) { if(null == anAddressData){ return false; } int hashCode = this.calculateHashCode(anAddressData.getName()); if((null == this.table[hashCode]) || (this.removedData == this.table[hashCode])){ this.table[hashCode] = anAddressData; return true; } int limit = this.table.length -1; String key = anAddressData.getName(); for(int count = 0; count < limit; count++){ hashCode = this.calculateHashCodeAgain(key, hashCode); if((null == this.table[hashCode]) || (this.removedData == this.table[hashCode])){ this.table[hashCode] = anAddressData; return true; } } return false; } 1回目のハッシュで衝突が起きたときは衝突が起きなくなるまで別のハッシュ関数でハッシュしなおす。
public AddressData get(String aKey) { if(null == aKey){ throw new NullPointerException(); } int hashCode = this.calculateHashCode(aKey); if(null == this.table[hashCode]){ return null; } if(this.removedData != this.table[hashCode]){ if(this.table[hashCode].getName().equals(aKey)){ return this.table[hashCode]; } } int limit = this.table.length -1; for(int count = 0; count < limit; count++){ hashCode = this.calculateHashCodeAgain(aKey, hashCode); if(null == this.table[hashCode]){ return null; } if(this.removedData != this.table[hashCode]){ if(this.table[hashCode].getName().equals(aKey)){ return this.table[hashCode]; } } } return null; }
public boolean remove(String aKey) { if(null == aKey){ throw new NullPointerException();} int hashCode = this.calculateHashCode(aKey); if(null == this.table[hashCode]){ return false; } if(this.removedData != this.table[hashCode]){ if(this.table[hashCode].getName().equals(aKey)){ this.table[hashCode] = removedData; return true; } } int limit = this.table.length -1; for(int count = 0; count < limit; count++){ hashCode = this.calculateHashCodeAgain(aKey, hashCode); if(null == this.table[hashCode]){ return false; } if(this.removedData != this.table[hashCode]){ if(this.table[hashCode].getName().equals(aKey)){ this.table[hashCode] = removedData; return true; } } } return false; }
[sakai@star]$ java OpenAddressHashtableTest null null null null null 住所データを格納 再ハッシュ: 後藤 1 → 2 再ハッシュ: 後藤 2 → 3 再ハッシュ: ONGS Inc. 1 → 2 再ハッシュ: ONGS Inc. 2 → 3 再ハッシュ: ONGS Inc. 3 → 4 佐々木: 座間,神奈川 228 杉山: 稲城,東京 208 小澤: 多摩,東京 206 後藤: 川崎,神奈川 214 ONGS Inc.: 渋谷,東京 151 データの削除: 杉山 データの削除: 小澤 データの削除: 後藤 再ハッシュ: 後藤 1 → 2 再ハッシュ: 後藤 2 → 3 データの削除: 佐々木 削除されました 削除されました 削除されました 削除されました ONGS Inc.: 渋谷,東京 151 データの取得: ONGS Inc. 再ハッシュ: ONGS Inc. 1 → 2 再ハッシュ: ONGS Inc. 2 → 3 再ハッシュ: ONGS Inc. 3 → 4 ONGS Inc.: 渋谷,東京 151 削除されました 削除されました 削除されました 削除されました ONGS Inc.: 渋谷,東京 151 ハッシュ表の大きさが5なので衝突が起きている
ハッシュ 再ハッシュ 再ハッシュ 既にデータが 格納されている キー 配列サイズ10 ステップ幅5 +5 mod 10 ここも既にデータが 格納されている よくない二重ハッシュ法
空きがあった! ハッシュ 再ハッシュ 再ハッシュ 既にデータが 格納されている キー 配列サイズ11 ステップ幅5 +5 mod 11 ここも既にデータが 格納されている 二重ハッシュ法