1. 程式人生 > >沖突的處理方法

沖突的處理方法

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 );        /* 釋放散列表結點 */
}

沖突的處理方法