js版數據結構_07散列表

js版數據結構_07散列表

ps:今天晚上寫之前沒考慮這一節代碼量的問題,爲保證時間沒有測試。但是主要都是邏輯問題,看邏輯便ok

知識點

  • 散列表是什麼
  • hash表的基本實現
  • hash表完善之使用分離鏈接
  • hash表完善之使用線性探查

1. 散列表(哈希表)

在這裏插入圖片描述
在這裏插入圖片描述
找的這兩個圖比較能體現散列表的特點,我們至今已經總結了不少的數據結構。但是回想一樣在他們的取值實現中,我們爲取到一個值往往是要對整個結構進行遍歷。這種操作效率是非常差的,我們希望可能有一種數據結構的數據獲取十分迅速。最好時間複雜度爲O(1)。於是散列表這種數據結構出現了。
散列表的實現,拿獲取值來說。它需要我們提供一個哈希哈數處理我們的key。使其能夠快速定位到我們存儲數據的存儲單元。
這裏我們會介紹兩種哈希函數,當然好理解的有缺陷,不好理解的最好用。

djb2HashCode(目前最好用的哈希函數)

djb2HashCode(key) {
const tableKey = this.toStrFn(key); 
let hash = 5381; 
for (let i = 0; i < tableKey.length; i++) { 
hash = (hash * 33) + tableKey.charCodeAt(i); 
}
return hash % 1013; 
}

loseloseHashCode(好理解,它只是將字符串的ascii碼與37取餘)

loseloseHashCode(key) {
if (typeof key === 'number') {
return key;
}
let hash = 0; 
for (let i = 0; i < key.length; i++) {
hash += tableKey.charCodeAt(i);
}
return hash % 37; 
}

2. hash表的基本實現

它的基本實現很簡單,不做解釋了。先說一個它的漏洞也是loseloseHashCode的不足,使用loseloseHashCode這種哈希函數,總會使得一些字符串的ascii碼值是相同的。這便會產生數據覆蓋的問題。顯然這不是我們希望遇見到。下面的兩節便是爲了解決這個問題。先來看它的基本實現

class HashTable {
            constructor() {
                    this.table = {};
                }
                // 先創建散列函數
            loseloseHashCode(key) {
                    if (typeof key === "number") {
                        return key
                    } else {
                        let hash = 0;
                        for (let i = 0; i < key.length; i++) {
                            hash = hash + key.charCodeAt(i);
                        }
                        return hash % 37;
                    }
                }
                // 新增一條到散列表
            put(key, val) {
                    if (key != null && val != null) {
                        const position = this.loseloseHashCode(key);
                        this.table[position] = val;
                        return true;
                    }
                    return false;
                }
                // 取值
            get(key) {
                    const position = this.loseloseHashCode(key);
                    if (this.table[position]) {
                        return this.table[position];
                    } else {
                        return undefined;
                    }
                }
                // 移出值
            remove(key) {
                const position = this.loseloseHashCode(key);
                if (this.table[position]) {
                    delete this.table[position];
                    return true;
                } else {
                    return false;
                }
            }
        }

3. hash表完善之使用分離鏈接

在這裏插入圖片描述
如上醜圖,鍵key1key2經過loseloseHashCode處理之後定位到的資源空間是同一塊,爲了防止數據覆蓋的問題,我們將使用鏈表存放它們。雖然這裏在鏈表中查找key1或key2時還是使用了線性查找。但是在這裏也是沒有法子的我們只能犧牲一下。不過注意的是我們這是在鏈表中保存的不止是val了。爲了方便定位之後的線性查詢我們要連key,val一塊存在鏈表中。

看實現:

  // 創建一個節點類
        class LNode {
            constructor(e) {
                this.element = e;
                this.next = null;
            }
        }
        //鏈表類
        class LinkList {
            constructor() {
                this.count = 0;
                this.head = null;
            }
            getHead() {
                return this.head;
            }
            push(e) {
                const node = new LNode(e);
                let current = this.head;
                // 先看鏈表是不是空的
                if (current == null) {
                    this.head = node;
                } else {
                    // 想一下最後一個節點的特點,最後一個節點的next爲null
                    while (current.next) {
                        current = current.next;
                    }
                    current.next = node;
                }
                this.count++;
            }
            removeAt(index) {
                // 先判讀這個index是否越界
                if (index >= 0 && index < this.count) {
                    let current = this.head;
                    // 看index是不是head位置
                    if (index === 0) {
                        this.head = current.next;
                    } else {
                        let previous;
                        // 找位置
                        for (let i = 0; i < index; i++) {
                            previous = current;
                            current = current.next;
                        }
                        previous.next = current.next;
                    }
                    this.count--;
                    return current.element;

                } else {
                    return undefined;
                }
            }
            getElementAt(index) {
                    // 判斷是否越界
                    if (index >= 0 && index < this.count) {
                        let node = this.head;
                        for (let i = 0; i < index && node != null; i++) {
                            node = node.next;
                        }
                        return node;
                    } else {
                        return undefined;
                    }
                }
                // 在任意位置插入
            insert(e, index) {
                    // 還是先判斷是否越界
                    if (index >= 0 && index < this.count) {
                        let node = new LNode(e);

                        // 0號位置判斷
                        if (index == 0) {
                            let current = this.head;
                            node.next = current;
                            this.head = node;
                        } else {
                            // 先找到索引的前一號位置,一截兩瓣
                            let pre = this.getElementAt(index - 1);
                            let current = pre.next;
                            pre.next = node;
                            node.next = current;

                        }
                        this.count++;
                        return true;
                    } else {
                        return undefined;
                    }
                }
                // 返回一個元素的位置
            indexOf(e) {
                    // 到循環遍歷鏈表就ok
                    let current = this.head;
                    for (let i = 0; i < this.count && current != null; i++) {
                        if (current.element == e) {
                            return i;
                        } else {
                            current = current.next;
                        }
                    }
                    // 循環完鏈表仍然沒有找到
                    return -1;
                }
                // 刪除指定元素
            remove(e) {
                // 我們前面實現一個根據位置刪除
                // 還實現了一個根據指定元素返回位置
                let index = this.indexOf(e);
                return this.removeAt(index);
            }
            size() {
                return this.count;
            }
            isEmpty() {
                return this.count == 0;
            }
        }
        class LinkListNode {
            constructor(key, val) {
                this.key = key;
                this.val = val;
            }

        }
        class HashTable {
            constructor() {
                    this.table = {};
                }
                // 先創建散列函數
            loseloseHashCode(key) {
                    if (typeof key === "number") {
                        return key
                    } else {
                        let hash = 0;
                        for (let i = 0; i < key.length; i++) {
                            hash = hash + key.charCodeAt(i);
                        }
                        return hash % 37;
                    }
                }
                // 新增一條到散列表
            put(key, val) {
                    if (key != null && val != null) {
                        const position = this.loseloseHashCode(key);
                        if (!this.table[position]) {
                            this.table[position] = new LinkList();
                        }
                        this.table[position].push(new LinkListNode(key, val));
                        return true;
                    } else {
                        return false;
                    }
                }
                // 取值
            get(key) {
                    const position = this.loseloseHashCode(key);
                    if (this.table[position]) {
                        // 遍歷鏈表取值
                        if (!this.table[position].isEmpty()) {
                            let current = this.table[position].getHead();
                            while (current != null) {
                                if (current.element.key === key) {
                                    return current.element.val;
                                }
                                current = current.next;
                            }
                            return undefined;

                        }
                    } else {
                        return undefined;
                    }
                }
                // 移出值
            remove(key) {
                const position = this.loseloseHashCode(key);
                let linkList = this.table[position];
                if (linkList != null && !linkList.isEmpty()) {
                    let current = linkList.getHead();
                    // 鏈表我們實現兩種刪除元素1根據元素值2根據索引
                    // 顯然這裏用值好
                    while (current) {
                        if (current.element.key === key) {
                            linkList.remove(current.element);
                            // 刪完如鏈表爲空則刪除此位置
                            if (linkList.isEmpty()) {
                                delete this.table[position]
                            }
                            return true;
                        }
                        current = current.next;
                    }

                }
                return false;
            }
        }

4. hash表完善之使用線性探查

在這裏插入圖片描述
這個比分離鏈表還要簡單一些,它是key定位到15的位置時發現爲空就把它的信息節點保存到這,key也是定位15的,但是它來的晚一看已經被人佔了。就向下找,找到一個空地就把自己的信息節點保存下來。
信息節點中的東西還是key和val。(方便查詢)

邏輯實現還是很順的,只不過有一個新的點要考慮。就是在節點刪除的時候,比如上圖我把key1刪掉了。那麼key2就會很尷尬。因爲我們定位都是定位到key1的位置的,一判斷key1的位置是空那我們下面實現的get和remove還怎麼玩。我們連key2的信息都拿不到。因此在數據移出的時候我們要對它下面的元素位置進行修復

實現:

 class Node {
            constructor(key, val) {
                this.key = key;
                this.val = val;
            }
        }
        class LineHashTable {
            constructor() {
                    this.table = {};
                }
                // 先創建散列函數
            loseloseHashCode(key) {
                if (typeof key === "number") {
                    return key
                } else {
                    let hash = 0;
                    for (let i = 0; i < key.length; i++) {
                        hash = hash + key.charCodeAt(i);
                    }
                    return hash % 37;
                }
            }

            verifyRemoveSideEffect(key, removedPosition) {
                    const hash = this.hashCode(key);
                    let index = removedPosition + 1;
                    while (this.table[index] != null) {
                        const posHash = this.hashCode(this.table[index].key);
                        if (posHash <= hash || posHash <= removedPosition) {
                            this.table[removedPosition] = this.table[index];
                            delete this.table[index];
                            removedPosition = index;
                        }
                        index++;
                    }
                }
                // 新增一條到散列表
            put(key, val) {
                    if (key != null && val != null) {
                        const position = this.loseloseHashCode(key);
                        if (this.table[position] == null) {
                            // 爲空放入節點
                            this.table[position] = new Node(key, val);
                        } else {
                            // 向下找空的儲存空間
                            let index = position + 1;
                            while (!this.table[index]) {
                                index++;
                            }
                            this.table[index] = new Node(key, val)
                        }
                        return true;
                    }
                    return false;
                }
                // 取值
            get(key) {
                    const position = this.loseloseHashCode(key);
                    //    先直接到lost函數所能快速定位的地方
                    if (this.table[position] != null) {
                        // 再判是不是我們要的值
                        if (this.table[position].key === key) {
                            return this.table[position].val;
                        }
                        // 不是則需要在此地的基礎上向下尋找了
                        let index = position + 1;
                        while (this.table[index] != null && this.table[index].key != key) {
                            index++;
                        }
                        // 上面出來了有兩種可能
                        // 1.找到目標
                        // 2.有一個空的地址
                        if (this.table[index] != null && this.table[index].key === key) {
                            return this.table[index].val;
                        }

                    }
                    return undefined;
                }
                // 移出值
            remove(key) {
                const position = this.loseloseHashCode(key);
                if (this.table[position] != null) {
                    // 判此時這個位置上的使我們要的嗎
                    if (this.table[position].key === key) {
                        delete this.table[position];
                        // 修復下面的位置
                        this.verifyRemoveSideEffect(key, position);
                        return true;
                    }
                    let index = position + 1;
                    while (this.table[index] != null && this.table[index].key !== key) {
                        index++;
                    }
                    if (this.table[index] != null && this.table[index].key === key) {
                        delete this.table[index];
                        // 修復下面的位置
                        this.verifyRemoveSideEffect(key, index);
                        return true;
                    }
                }
                return false;

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