沖突的處理方法
阿新 • • 發佈:2018-05-20
while false max print 判斷 擴大 -- ptr 最小
- 處理沖突的方法
常用處理沖突的思路:
換個位置:開放地址法
同一個位置的沖突對象組織在一起:鏈地址法
- 開放定址法(Open Addressing)
一旦產生了沖突(該地址已有其他元素),就按某種規則去尋找另一空地址。
開放定址法
若發生了第i次沖突,試探的下一個地址將增加di,基本公式是:
hi(key) = (h(key)+dj) mod TableSize (1≤i<TableSize)
dj決定了不同的解決沖突方案:線性探測、平方探測、雙散列。
#define MAXTABLESIZE 100000 /* 允許開辟的最大散列表長度 */ typedef int ElementType; /*關鍵詞類型用整型 */ typedef int Index; /* 散列地址類型 */ typedef Index Position; /* 數據所在位置與散列地址是同一類型 */ /* 散列單元狀態類型,分別對應:有合法元素、空單元、有已刪除元素 */ typedef enum { Legitimate, Empty, Deleted } EntryType; typedef struct HashEntry Cell; /* 散列表單元類型 */ struct HashEntry{ ElementType Data; /* 存放元素 */ EntryType Info;/* 單元狀態 */ }; typedef struct TblNode *HashTable; /* 散列表類型 */ struct TblNode { /* 散列表結點定義 */ int TableSize; /* 表的最大長度 */ Cell *Cells; /* 存放散列單元數據的數組 */ }; int NextPrime( int N ) { /* 返回大於N且不超過MAXTABLESIZE的最小素數 */ int i, p = (N%2)? N+2 : N+1; /*從大於N的下一個奇數開始 */ while( p <= MAXTABLESIZE ) {for( i=(int)sqrt(p); i>2; i-- ) if ( !(p%i) ) break; /* p不是素數 */ if ( i==2 ) break; /* for正常結束,說明p是素數 */ else p += 2; /* 否則試探下一個奇數 */ } return p; } HashTable CreateTable( int TableSize ) { HashTable H; int i; H = (HashTable)malloc(sizeof(struct TblNode)); /* 保證散列表最大長度是素數 */ H->TableSize = NextPrime(TableSize); /* 聲明單元數組 */ H->Cells = (Cell *)malloc(H->TableSize*sizeof(Cell)); /* 初始化單元狀態為“空單元” */ for( i=0; i<H->TableSize; i++ ) H->Cells[i].Info = Empty; return H; }
1.線性探測法(Linear Probing)
- 線性探測法:以增量序列1,2,......,(TableSize-1)循環試探下一個存儲地址。
設關鍵詞序列為{47, 7, 29, 11, 9, 84, 54, 20, 30},
散列表表長TableSize = 13(裝填因子α = 9/13≈ 0.69);
散列函數為:h(key) = key mod 11。
用線性探測法處理沖突,列出一次插入後的散列表,並估算查找性能
散列表查找性能分析
- 成功平均查找長度(ASLs)
- 不成功平均查找長度(ASLu)
2.平方探測法(Quadratic Probing)--- 二次探測
平方探測法:以增量序列12,-12, 22, -22, ......, q2, -q2且q ≤ [ TableSize/2 ] 循環試探洗一個存儲地址。
是否有空間,平方探測就能找得到?
有定理顯示:如果散列表長度TableSize是某個4k+3(k是正整數)形式的素數時,平方探測法就可以探查到整個散列表空間。
Position Find( HashTable H, ElementType Key ) { Position CurrentPos, NewPos; int CNum = 0; /* 記錄沖突次數 */ NewPos = CurrentPos = Hash( Key, H->TableSize ); /* 初始散列位置 */ /* 當該位置的單元非空,並且不是要找的元素時,發生沖突 */ while( H->Cells[NewPos].Info!=Empty && H->Cells[NewPos].Data!=Key ) { /* 字符串類型的關鍵詞需要 strcmp 函數!! */ /* 統計1次沖突,並判斷奇偶次 */ if( ++CNum%2 ){ /* 奇數次沖突 */ NewPos = CurrentPos + (CNum+1)*(CNum+1)/4; /* 增量為+[(CNum+1)/2]^2 */ if ( NewPos >= H->TableSize ) NewPos = NewPos % H->TableSize; /* 調整為合法地址 */ } else { /* 偶數次沖突 */ NewPos = CurrentPos - CNum*CNum/4; /* 增量為-(CNum/2)^2 */ while( NewPos < 0 ) NewPos += H->TableSize; /* 調整為合法地址 */ } } return NewPos; /* 此時NewPos或者是Key的位置,或者是一個空單元的位置(表示找不到)*/ } bool Insert( HashTable H, ElementType Key ) { Position Pos = Find( H, Key ); /* 先檢查Key是否已經存在 */ if( H->Cells[Pos].Info != Legitimate ) { /* 如果這個單元沒有被占,說明Key可以插入在此 */ H->Cells[Pos].Info = Legitimate; H->Cells[Pos].Data = Key; /*字符串類型的關鍵詞需要 strcpy 函數!! */ return true; } else { printf("鍵值已存在"); return false; } }
3.雙散列探測法(Double Hashing)
雙散列探測法:di為i*h2(key),h2(key)是另一個散列函數探測序列成:h2(key), 2h2(key),3h2(key), ......
- 對任意的key,h2(key) ≠ 0!
- 探測序列還應該保證所有的散列存儲單元都應該能夠被探測到。選擇一下形式有良好的效果:
h2(key) = p - (key mod p)
其中:p < TableSize, p、TableSize都是素數。
4.再散列
- 當散列表元素太多(即裝填因子α太大)時,查找效率會下降;
實用最大裝填因子一般取0.5≤α≤0.85
- 當裝填因子過大時,解決的方法是加倍擴大散列表,這個過程叫做“再散列(Rehashing)”
分離鏈接法(Separate Chaining)
將響應位置上沖突的所有關鍵詞存儲在同一個單鏈表中
#define KEYLENGTH 15 /* 關鍵詞字符串的最大長度 */ typedef char ElementType[KEYLENGTH+1]; /* 關鍵詞類型用字符串 */ typedef int Index; /* 散列地址類型 */ /******** 以下是單鏈表的定義 ********/ typedef struct LNode *PtrToLNode; struct LNode { ElementType Data; PtrToLNode Next; }; typedef PtrToLNode Position; typedef PtrToLNode List; /******** 以上是單鏈表的定義 ********/ typedef struct TblNode *HashTable; /* 散列表類型 */ struct TblNode { /* 散列表結點定義 */ int TableSize; /* 表的最大長度 */ List Heads; /* 指向鏈表頭結點的數組 */ }; HashTable CreateTable( int TableSize ) { HashTable H; int i; H = (HashTable)malloc(sizeof(struct TblNode)); /* 保證散列表最大長度是素數,具體見代碼5.3 */ H->TableSize = NextPrime(TableSize); /* 以下分配鏈表頭結點數組 */ H->Heads = (List)malloc(H->TableSize*sizeof(struct LNode)); /* 初始化表頭結點 */ for( i=0; i<H->TableSize; i++ ) { H->Heads[i].Data[0] = ‘\0‘; H->Heads[i].Next = NULL; } return H; } Position Find( HashTable H, ElementType Key ) { Position P; Index Pos; Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */ P = H->Heads[Pos].Next; /* 從該鏈表的第1個結點開始 */ /* 當未到表尾,並且Key未找到時 */ while( P && strcmp(P->Data, Key) ) P = P->Next; return P; /* 此時P或者指向找到的結點,或者為NULL */ } bool Insert( HashTable H, ElementType Key ) { Position P, NewCell; Index Pos; P = Find( H, Key ); if ( !P ) { /* 關鍵詞未找到,可以插入 */ NewCell = (Position)malloc(sizeof(struct LNode)); strcpy(NewCell->Data, Key); Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */ /* 將NewCell插入為H->Heads[Pos]鏈表的第1個結點 */ NewCell->Next = H->Heads[Pos].Next; H->Heads[Pos].Next = NewCell; return true; } else { /* 關鍵詞已存在 */ printf("鍵值已存在"); return false; } } void DestroyTable( HashTable H ) { int i; Position P, Tmp; /* 釋放每個鏈表的結點 */ for( i=0; i<H->TableSize; i++ ) { P = H->Heads[i].Next; while( P ) { Tmp = P->Next; free( P ); P = Tmp; } } free( H->Heads ); /* 釋放頭結點數組 */ free( H ); /* 釋放散列表結點 */ }
沖突的處理方法