散列 簡單實現(學習筆記)

散列簡介

散列是一種用於以常數平均時間進行插入、刪除、查找的技術;但是,那些需要元素間任何排序信息的操作將
不會得到有效的支持,比如FindMax/FindMin以及以線性時間按
排序順序將整個表進行打印的操作都是散列表所不支持的;
對散列表的理解:
    我們查找東西時一種做法是將東西標號,然後二分查找(二叉樹),另一種做法是將東西分類,即哈希。
一個理想的散列表數據結構只不過是一個包含有關鍵字的具有固定大小的數組。每個關鍵字被映射到數組0-N-1這
個範圍中的某一個數,並且被放到適當的單元裏。這個映射就叫做*散列函數*;最理想的情況下是保證任何兩個不
同關鍵字被映射到不同的單元裏。不過這是不可能的,因爲單元的
數目是有限的,而關鍵字是無限的。因此,需要一個散列函數,
他能在單元之間均勻的分配關鍵字;剩下的問題就是解決當亮哥關鍵字散列到同一個值的時候應該怎麼做了——
解決衝突;
一個較好的散列函數:
int Hash(const char* key, int TableSize)
{
    unsigned int HashVal = 0;
    while(*key != '\0')
        HashVal = (HashVal << 5) + *key++;
    return HashVal % TableSize;
}
解決衝突的方法:
有分離鏈接法,開發定址法等;我覺得分離鏈接法比較好理解,下面代碼都採用分離鏈接法;

分離鏈接散列表的類型聲明

// 鏈表節點聲明
struct ListNode;
typedef struct *ListNode Position;
struct ListNode
{
    int Element;
    Position Next;
};
// 哈希表結構定義
typedef Position List; // 這些typedef主要是爲了便於理解以及簡化書寫
struct HashTbl;
typedef struct *HashTbl HashTable;
struct HashTbl
{
    int TableSize;   // 哈希表的長度
    // 指向 “指向ListNode結構的指針” 的指針 ,
    // 有些實現裏直接指定了 TableList的大小 如List TableList[10]這種,這採用動態分配
    List *TableList; 
};

分離鏈接散列表初始化例程

HashTable InitTable(int TableSize)
{
    HashTable H = NULL;
    int i = 0;

    H = malloc(sizeof(struct HashTbl));
    if(NULL == H) return NULL;

    H->TableSize = TableSize;
    // 給散列表分配空間
    // 注意這裏 不是 sizeof(struct ListNode), 實際是申請了TableSize個數組,裏邊元素是List(指
    針)即指針數組
    H->TableList = malloc(sizeof(List) * H->TableSize);
    if(NULL == H->TableList) return NULL;

    // 這裏List的實現加了個表頭,所以要爲每個表頭分配內存
    for(i; i < H->TableSize; ++i)
    {
        // 注意,這裏纔是 sizeof(struct ListNode)
        H->TableList[i] = malloc(sizeof(struct ListNode));
        if(NULL == H->TableList[i])
            return NULL;
        else
            H->TableList[i]->Next = NULL;
    }
    return H;   
}

分離連接散列表的Find例程

Position Find(HashTable H, ElementType key)
{
    Position P = NULL;
    List L = NULL;
    if(NULL == H) return NULL;
    // 首先找到key對應的表頭,因爲一維是個數組,所以可以根據下標來找
    L = H->TableList[ Hash(key, H->TableSize) ];    
    if(NULL == L) return NULL;
    // 掠過表頭
    P= L->Next;
    // 遍歷鏈表
    while(P != NULL && P->Element != key)
        P = P->Next;
    reutrn P;
}

分離連接散列表的Insert例程

void Insert(HashTable H, ElementType key)
{
    if(NULL == H) return;

    // 首先查找,如果不存在則插入插入插入....哈哈
    Position Pos, NewCell;

    Pos = Find(H, key);
    if(NULL == Pos)
    {
        NewCell = malloc(sizeof(struct ListNode));
        if(NULL == NewCell) return;
        // 找到key對應的表頭
        List L = H->TableList[ Hash(key, H->TableSize) ];
        // 這裏採用頭部插入
        NewCell->Next = L->Next;
        NewCell->Element = key; // 可能需要拷貝
        L->Next = NewCell; 
    }
}

// 插入例程有要改進的地方,因爲每次插入都要查找一次,即兩次遍歷,暫時先這樣;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章