JavaScript數據結構與算法之 "鏈表"

鏈表數據結構

  • 鏈表是存儲有序元素的集合,但不同於數組,鏈表中的元素在內存中並不是連續放置的
  • 鏈表中的每個元素由一個存儲元素本身的節點和一個指向下一個元素的引用組成
  • 鏈表的優點:添加元素或移除元素的時候不需要移動其他元素,因此向鏈表中添加和刪除元素會比數組快
  • 鏈表的缺點:要想訪問鏈表中間的一個元素,需要從起點開始迭代鏈表直到找到所需要的元素,因此查詢鏈表中的元素會比數組慢
  • 鏈表結構在現實生活中的列子:火車,拉鍊

幫助函數或類

  • 下面的類或函數是公用的
  • 用於比較兩個元素相等的函數:const defaultEquals = (a, b) => a === b;
  • 判斷某個元素是否存在:
    const isExist = (element) => {
        return element !== undefined && element !== null;
    };
    
  • 基本鏈表的節點
    /*鏈表節點類*/
    class Node {
        constructor(element) {
            this.element = element;
            this.next = undefined;  // next存儲下一個節點的引用
        }
    }
    
  • 雙向鏈表的節點
    /*雙向鏈接節點類*/
    class DoublyNode extends Node {
        constructor(element, next, prev) {
            super(element, next);
            this.prev = prev;  // 指向上一個節點的鏈接
        }
    }
    

基本的鏈表

  • 基本鏈表的方法:
    • push(element):向鏈表尾部添加一個新元素
    • insert(element,index):向鏈表的特定位置插入一個新元素
    • getElementAt(index):返回鏈表中特定位置的元素。如果鏈表中不存在這個元素則返回undefined
    • remove(element):從鏈表中移除一個元素
    • indexOf(element):返回元素在鏈表中的索引。如果鏈表中沒有該元素則返回-1
    • removeAt(index):從鏈表中的特定位置移除一個元素
    • getHead():獲取鏈表的第一個元素
    • isEmpty(): 判斷鏈表中是否有元素,沒有返回true,有方法false
    • clear(): 清空鏈表
    • size(): 返回鏈表中元素的個數
    • toString()
  • 代碼
    /*創建鏈表的類*/
    class LinkedList {
        // equalsFn 比較相等的函數
        constructor(equalsFn = defaultEquals) {
            this.count = 0;  // 鏈表元素的計數器
            this.head = undefined; //鏈表的頭部
            this.equalsFn = equalsFn; //比較相等的函數
        }
    
        //  push(element):向鏈表尾部添加一個新元素
        push(element) {
            const node = new Node(element);
            let current;  //當前節點
    
            if (!isExist(this.head)) {
                this.head = node;
            } else {
                current = this.head;
                // 如果current的下一個元素存在則將current指向下一個元素
                while (isExist(current.next)) {
                    current = current.next;
                }
                // 循環結束或current會指向當前列表的最後一個元素,將其next賦爲新元素,建立鏈接
                current.next = node;
            }
    
            this.count += 1;
        }
    
        //  insert(element,index):向鏈表的特定位置插入一個新元素
        insert(element, index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return false;
            }
    
            // index 沒有越界
            const node = new Node(element);
            if (index === 0) {
                node.next = this.head;
                this.head = node;
            } else {
                const previous = this.getElementAt(index - 1);
                const current = previous.next;
                previous.next = node;
                node.next = current;
            }
    
            this.count += 1;
            return true;
        }
    
        //  getElementAt(index):返回鏈表中特定位置的元素。如果鏈表中不存在這個元素則返回undefined
        getElementAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 沒有越界
            let current = this.head;
    
            // 循環找到指定index位置的元素
            for (let i = 1; i <= index && isExist(current); i += 1) {
                current = current.next;
            }
    
            return current;
        }
    
        //  remove(element):從鏈表中移除一個元素
        remove(element) {
            const index = this.indexOf(element);
            return this.removeAt(index);
        }
    
        //  indexOf(element):返回元素在鏈表中的索引。如果鏈表中沒有該元素則返回-1
        indexOf(element) {
            let current = this.head;
    
            //循環找到鏈表中和傳入的元素相等的元素的索引
            for (let i = 0; i < this.count && isExist(current); i++) {
                if (this.equalsFn(element, current.element)) {
                    return i;
                }
                current = current.next;
            }
    
            // 沒有相等元素返回-1
            return -1;
        }
    
        //  removeAt(index):從鏈表中的特定位置移除一個元素
        removeAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 沒有越界
            let current = this.head;
    
            // 移除index位置的元素
            if (index === 0) {
                this.head = current.next;
            } else {
                const previous = this.getElementAt(index - 1); //找到index-1位置的元素
                current = previous.next; // 找到index位置的元素
                previous.next = current.next; // 移除index位置的元素
            }
    
            this.count -= 1;
            return current.element;
        }
    
        // getHead():獲取鏈表的第一個元素
        getHead() {
            return this.head;
        }
    
        //  isEmpty(): 判斷鏈表中是否有元素,沒有返回true,有方法false
        isEmpty() {
            return this.count === 0;
        }
    
        //  clear(): 清空鏈表
        clear() {
            this.count = 0;
            this.head = undefined;
        }
    
        //  size(): 返回鏈表中元素的個數
        size() {
            return this.count;
        }
    
        //  toString()
        toString() {
            if (!isExist(this.head)) {
                return '';
            }
    
            let str = `${this.head.element}`;
            let current = this.head.next;
            for (let i = 1; i < this.size() && isExist(current); i += 1) {
                str = `${str},${current.element}`;
                current = current.next;
            }
    
            return str;
        }
    }
    

雙向鏈表

  • 雙向鏈表和普通鏈表的區別在於,在普通鏈表中一個節點只有指向下一個節點的鏈接;而在雙向鏈表中,鏈接是雙向的:一個指向下一個元素,另一個指向前一個元素
  • 雙向鏈表提供了兩種迭代的方法:從頭到尾,或者從尾到頭。
  • 我們也可以訪問一個節點的前一個元素和下一個元素
  • 雙向鏈表的方法:
    • push(element):向雙向鏈表尾部添加一個新元素
    • insert(element,index):向雙向鏈表的任意位置插入一個新元素
    • getElementAt(index):返回雙向鏈表中特定位置的元素。如果雙向鏈表中不存在這個元素則返回undefined
    • remove(element):從雙向鏈表中移除一個元素
    • indexOf(element):返回元素在雙向鏈表中的索引。如果雙向鏈表中沒有該元素則返回-1
    • removeAt(index):從雙向鏈表中的特定位置移除一個元素
    • getHead():獲取雙向鏈表的第一個元素
    • getTail():獲取雙向鏈表的最後一個元素
    • isEmpty(): 判斷雙向鏈表中是否有元素,沒有返回true,有方法false
    • clear(): 清空雙向鏈表
    • size(): 返回雙向鏈表中元素的個數
    • toString()
  • 注意:代碼中沒有重寫的方法,將使用繼承自父類的方法
  • 代碼
    class DoublyLinkedList extends LinkedList {
       constructor(equalsFn = defaultEquals) {
           super(equalsFn);
           this.tail = undefined;  // 雙向鏈表的尾部元素
       }
    
       // push(element):向雙向鏈表尾部添加一個新元素
       push(element) {
           const node = new DoublyNode(element);
    
           if (!isExist(this.head)) {
               this.head = node;
               this.tail = node;
           } else {
               this.tail.next = node;
               node.prev = this.tail;
               this.tail = node;
           }
    
           this.count += 1;
       }
    
       //  insert(element,index):向雙向鏈表的任意位置插入一個新元素
       insert(element, index) {
           // 如果index越界
           if (!(index >= 0 && index <= this.count)) {
               return false;
           }
    
           // index 沒有越界
           const node = new DoublyNode(element);
           let current = this.head;
    
           if (index === 0) { // 插入到頭部
               if (!isExist(this.head)) {
                   this.head = node;
                   this.tail = node;
               } else {
                   node.next = current;
                   current.prev = node;
                   this.head = node;
               }
           } else if (index === this.count) { // 插入到尾部
               current = this.tail;
               current.next = node;
               node.prev = current;
               this.tail = node;
           } else { //插入到中間位置
               const previous = this.getElementAt(index - 1);
               current = previous.next;
               previous.next = node;
               node.next = current;
               current.prev = node;
               node.prev = previous;
           }
    
           this.count += 1;
           return true;
       }
    
       //  removeAt(index):從雙向鏈表中的特定位置移除一個元素
       removeAt(index) {
           // 如果index越界
           if (!(index >= 0 && index < this.count)) {
               return undefined;
           }
    
           // index沒有越界
           let current = this.head;
           if (index === 0) { //刪除頭部元素
               this.head = current.next;
    
               if (this.count === 1) {  //如果鏈表只有一個元素
                   this.tail = undefined;
               } else { //如果鏈表有多個元素
                   this.head.prev = undefined;
               }
           } else if (index === this.count - 1) { //刪除尾部元素
               current = this.tail;
               this.tail = current.prev;
               this.tail.next = undefined;
           } else { // 刪除中間元素
               const previous = this.getElementAt(index - 1);  // 找到鏈表中對應當前index位置元素的前一個元素
               current = previous.next;
               previous.next = current.next;
               current.next.prev = previous;
           }
    
           this.count -= 1;
           return current.element;
       }
    
       // getTail():獲取雙向鏈表的最後一個元素
       getTail() {
           return this.tail;
       }
    
       //  clear(): 清空雙向鏈表
       clear() {
           super.clear();
           this.tail = undefined;
       }
    }
    

循環鏈表

  • 循環鏈表可以像鏈表一樣只有單向引用,也可以像雙向鏈表一樣有雙向引用
  • 循環鏈表和鏈表之間唯一的區別在於,最後一個元素指向下一個元素引用不是undefined,而是鏈表的第一個元素‘
  • 雙向循環鏈表有指向head元素的tail.next 和指向 tail 元素的head.prev
  • 循環鏈表繼承自鏈表
  • 方法
    • push(element):向循環鏈表尾部添加一個新元素
    • insert(element,index):向循環鏈表的任意位置插入一個新元素
    • getElementAt(index):返回循環鏈表中特定位置的元素。如果循環鏈表中不存在這個元素則返回undefined
    • remove(element):從循環鏈表中移除一個元素
    • indexOf(element):返回元素在循環鏈表中的索引。如果循環鏈表中沒有該元素則返回-1
    • removeAt(index):從循環鏈表中的特定位置移除一個元素
    • getHead():獲取循環鏈表的第一個元素
    • isEmpty(): 判斷循環鏈表中是否有元素,沒有返回true,有方法false
    • clear(): 清空循環鏈表
    • size(): 返回循環鏈表中元素的個數
    • toString()
      注意:代碼中沒有重寫的方法,將使用繼承自父類的方法
  • 代碼
    /*創建循環鏈表的類*/
    class CircularLinkedList extends LinkedList {
        constructor(equalsFn = defaultEquals) {
            super(equalsFn);
        }
    
        // push(element):向循環鏈表尾部添加一個新元素
        push(element) {
            const node = new Node(element);
    
            if (this.isEmpty()) {
                this.head = node;
            } else {
                const current = this.getElementAt(this.size() - 1);
                current.next = node;
            }
    
            node.next = this.head;
            this.count += 1;
        }
    
        // insert(element,index):向循環鏈表的任意位置插入一個新元素
        insert(element, index) {
            // 如果index越界
            if (!(index >= 0 && index <= this.count)) {
                return false;
            }
    
            // index沒有越界
            const node = new Node(element);
            if (index === 0) { // 插入到頭部
                if (!isExist(this.head)) { // 不存在this.head
                    this.head = node;
                    node.next = this.head;
                } else { //存在this.head
                    const current = this.getElementAt(this.size() - 1);
                    node.next = this.head;
                    this.head = node;
                    current.next = this.head;
                }
            } else {
                const previous = this.getElementAt(index - 1);
                node.next = previous.next;
                previous.next = node;
            }
    
            this.count += 1;
            return true;
        }
    
        // removeAt(index):從循環鏈表中的特定位置移除一個元素
        removeAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 沒有越界
            let current = this.head;
            if (index === 0) { //刪除第一個元素
                if (this.size() === 1) { // 只有一個元素
                    this.head = undefined;
                } else { //有多個元素
                    const last = this.getElementAt(this.size() - 1); // 最後一個元素
                    this.head = current.next;
                    last.next = this.head;
                }
            } else { //刪除其他元素
                const previous = this.getElementAt(index - 1);
                current = previous.next;
                previous.next = current.next;
            }
    
            this.count -= 1;
            return current.element;
        }
    }
    
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章