學習javascript數據結構(二)——鏈表

前言

人生總是直向前行走,從不留下什麼。

原文地址:學習javascript數據結構(二)——鏈表

博主博客地址:Damonare的個人博客

正文

鏈表簡介

    上一篇博客-學習javascript數據結構(一)——棧和隊列說了棧和隊列在javascript中的實現,我們運用javascript提供的API很容易的實現了棧和隊列,但這種數據結構有一個很明顯的缺點,因爲數組大小是固定的所以我們在移除或是添加一項數據的時候成本很高,基本都需要吧數據重排一次。(javascript的Array類方法雖然很方便但背後的原理同樣是這樣的)

    相比數組我們今天主角——鏈表就要來的隨性的多,簡單的理解可以是這樣:在內存中,棧和隊列(數組)的存在就是一個整體,如果想要對她內部某一個元素進行移除或是添加一個新元素就要動她內部所有的元素,所謂牽一髮而動全身;而鏈表則不一樣,每一個元素都是由元素本身數據和指向下一個元素的指針構成,所以添加或是移除某一個元素不需要對鏈表整體進行操作,只需要改變相關元素的指針指向就可以了。

    鏈表在實際生活中的例子也有很多,比如自行車的鏈條,環環相扣,但添加或是移除某一個環節只需要對症下藥,對相關環節進行操作就OK。再比如:火車,火車就是一個鏈表,每一節車廂就是元素,想要移除或是添加某一節車廂,只需要把連接車廂的鏈條改變一下就好了。那麼,在javascript中又該怎麼去實現鏈表結構呢?

鏈表的創建

首先我們要創建一個鏈表類:

function LinkedList(){
    //各種屬性和方法的聲明
}

然後我們需要一種數據結構來保存鏈表裏面的數據:

var Node=function(element){
    this.element=element;
    this.next=null;
}
//Node類表示要添加的元素,他有兩個屬性,一個是element,表示添加到鏈表中的具體的值;另一個是next,表示要指向鏈表中下一個元素的指針。

接下來,我們需要給鏈表聲明一些方法:

  • append(element):向鏈表尾部添加一個新的元素;
  • insert(position,element):向鏈表特定位置插入元素;
  • remove(element):從鏈表移除一項;
  • indexOf(element):返回鏈表中某元素的索引,如果沒有返回-1;
  • removeAt(position):從特定位置移除一項;
  • isEmpty():判斷鏈表是否爲空,如果爲空返回true,否則返回false;
  • size():返回鏈表包含的元素個數;
  • toString():重寫繼承自Object類的toString()方法,因爲我們使用了Node類;

鏈表的完整代碼:

function LinkedList() {
    //Node類聲明
    let Node = function(element){
        this.element = element;
        this.next = null;
    };
    //初始化鏈表長度
    let length = 0;
    //初始化第一個元素
    let head = null;
    this.append = function(element){
        //初始化添加的Node實例
        let node = new Node(element),
            current;
        if (head === null){
            //第一個Node實例進入鏈表,之後在這個LinkedList實例中head就不再是null了
            head = node;
        } else {
            current = head;
            //循環鏈表知道找到最後一項,循環結束current指向鏈表最後一項元素
            while(current.next){
                current = current.next;
            }
            //找到最後一項元素後,將他的next屬性指向新元素node,j建立鏈接
            current.next = node;
        }
        //更新鏈表長度
        length++;
    };
    this.insert = function(position, element){
        //檢查是否越界,超過鏈表長度或是小於0肯定不符合邏輯的
        if (position >= 0 && position <= length){
            let node = new Node(element),
                current = head,
                previous,
                index = 0;
            if (position === 0){
                //在第一個位置添加
                node.next = current;
                head = node;
            } else {
                //循環鏈表,找到正確位置,循環完畢,previous,current分別是被添加元素的前一個和後一個元素
                while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
            }
            //更新鏈表長度
            length++;
            return true;
        } else {
            return false;
        }
    };
    this.removeAt = function(position){
        //檢查是否越界,超過鏈表長度或是小於0肯定不符合邏輯的
        if (position > -1 && position < length){
            let current = head,
                previous,
                index = 0;
            //移除第一個元素
            if (position === 0){
                //移除第一項,相當於head=null;
                head = current.next;
            } else {
                //循環鏈表,找到正確位置,循環完畢,previouscurrent分別是被添加元素的前一個和後一個元素
                while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                //鏈接previouscurrent的下一個元素,也就是把current移除了
                previous.next = current.next;
            }
            length--;
            return current.element;
        } else {
            return null;
        }
    };
    this.indexOf = function(element){
        let current = head,
            index = 0;
        //循環鏈表找到元素位置
        while (current) {
            if (element === current.element) {
                return index;
            }
            index++;
            current = current.next;
        }
        return -1;
    };
    this.remove = function(element){
        //調用已經聲明過的indexOfremoveAt方法
        let index = this.indexOf(element);
        return this.removeAt(index);
    };
    this.isEmpty = function() {
        return length === 0;
    };
    this.size = function() {
        return length;
    };
    this.getHead = function(){
        return head;
    };
    this.toString = function(){
        let current = head,
            string = '';
        while (current) {
            string += current.element + (current.next ? ', ' : '');
            current = current.next;
        }
        return string;
    };
    this.print = function(){
        console.log(this.toString());
    };
}
//一個實例化後的鏈表,裏面是添加的數個Node類的實例

ES6版本:

let LinkedList2 = (function () {
    class Node {
        constructor(element){
            this.element = element;
            this.next = null;
        }
    }
    //這裏我們使用WeakMap對象來記錄長度狀態
    const length = new WeakMap();
    const head = new WeakMap();
    class LinkedList2 {
        constructor () {
            length.set(this, 0);
            head.set(this, null);
        }
        append(element) {
            let node = new Node(element),
                current;
            if (this.getHead() === null) {
                head.set(this, node);
            } else {
                current = this.getHead();
                while (current.next) {
                    current = current.next;
                }
                current.next = node;
            }
            let l = this.size();
            l++;
            length.set(this, l);
        }
        insert(position, element) {
            if (position >= 0 && position <= this.size()) {

                let node = new Node(element),
                    current = this.getHead(),
                    previous,
                    index = 0;
                if (position === 0) {
                    node.next = current;
                    head.set(this, node);
                } else {
                    while (index++ < position) {
                        previous = current;
                        current = current.next;
                    }
                    node.next = current;
                    previous.next = node;
                }
                let l = this.size();
                l++;
                length.set(this, l);
                return true;
            } else {
                return false;
            }
        }
        removeAt(position) {
            if (position > -1 && position < this.size()) {
                let current = this.getHead(),
                    previous,
                    index = 0;
                if (position === 0) {
                    head.set(this, current.next);
                } else {
                    while (index++ < position) {
                        previous = current;
                        current = current.next;
                    }
                    previous.next = current.next;
                }
                let l = this.size();
                l--;
                length.set(this, l);
                return current.element;
            } else {
                return null;
            }
        }
        remove(element) {
            let index = this.indexOf(element);
            return this.removeAt(index);
        }
        indexOf(element) {
            let current = this.getHead(),
                index = 0;
            while (current) {
                if (element === current.element) {
                    return index;
                }
                index++;
                current = current.next;
            }
            return -1;
        }
        isEmpty() {
            return this.size() === 0;
        }
        size() {
            return length.get(this);
        }
        getHead() {
            return head.get(this);
        }
        toString() {
            let current = this.getHead(),
                string = '';
            while (current) {
                string += current.element + (current.next ? ', ' : '');
                current = current.next;
            }
            return string;

        }
        print() {
            console.log(this.toString());
        }
    }
    return LinkedList2;
})();

雙向鏈表

function DoublyLinkedList() {
    let Node = function(element){
        this.element = element;
        this.next = null;
        this.prev = null; //NEW
    };
    let length = 0;
    let head = null;
    let tail = null; //NEW
    this.append = function(element){
        let node = new Node(element),
            current;
        if (head === null){
            head = node;
            tail = node; //NEW
        } else {
            //NEW
            tail.next = node;
            node.prev = tail;
            tail = node;
        }
        length++;
    };
    this.insert = function(position, element){
        if (position >= 0 && position <= length){
            let node = new Node(element),
                current = head,
                previous,
                index = 0;
            if (position === 0){
                if (!head){       //NEW
                    head = node;
                    tail = node;
                } else {
                    node.next = current;
                    current.prev = node; //NEW
                    head = node;
                }
            } else  if (position === length) { ////NEW
                current = tail;   
                current.next = node;
                node.prev = current;
                tail = node;
            } else {
                while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
                current.prev = node; //NEW
                node.prev = previous; //NEW
            }
            length++;
            return true;
        } else {
            return false;
        }
    };
    this.removeAt = function(position){
        if (position > -1 && position < length){
            let current = head,
                previous,
                index = 0;
            if (position === 0){ //NEW
                if (length === 1){ //
                    tail = null;
                } else {
                    head.prev = null;
                }
            } else if (position === length-1){  //NEW
                current = tail;
                tail = current.prev;
                tail.next = null;
            } else {
                while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                previous.next = current.next;
                current.next.prev = previous; //NEW
            }
            length--;
            return current.element;
        } else {
            return null;
        }
    };
    this.remove = function(element){
        let index = this.indexOf(element);
        return this.removeAt(index);
    };
    this.indexOf = function(element){
        let current = head,
            index = -1;
        if (element == current.element){
            return 0;
        }
        index++;
        while(current.next){
            if (element == current.element){
                return index;
            }
            current = current.next;
            index++;
        }
        //check last item
        if (element == current.element){
            return index;
        }
        return -1;
    };
    this.isEmpty = function() {
        return length === 0;
    };
    this. size = function() {
        return length;
    };
    this.toString = function(){
        let current = head,
            s = current ? current.element : '';
        while(current && current.next){
            current = current.next;
            s += ', ' + current.element;
        }
        return s;
    };
    this.inverseToString = function() {
        let current = tail,
            s = current ? current.element : '';
        while(current && current.prev){
            current = current.prev;
            s += ', ' + current.element;
        }
        return s;
    };
    this.print = function(){
        console.log(this.toString());
    };
    this.printInverse = function(){
        console.log(this.inverseToString());
    };
    this.getHead = function(){
        return head;
    };
    this.getTail = function(){
        return tail;
    }
}

    雙向鏈表和單項比起來就是Node類多了一個prev屬性,也就是每一個node不僅僅有一個指向它後面元素的指針也有一個指向它前面的指針。

循環鏈表

    明白了前面的基礎鏈表和雙向鏈表之後這個肯定不在話下了,循環,其實就是整個鏈表實例變成了一個圈,在單項鍊表中最後一個元素的next屬性爲null,現在讓它指向第一個元素也就是head,那麼他就成了單向循環鏈表。在雙向鏈表中最後一個元素的next屬性爲null,現在讓它指向第一個元素也就是head,那麼他就成了雙向循環鏈表。就那麼回事…

後記

說到現在一直都是線性表,就是順序數據結構,他們都是有順序的,數據都是一條繩子上的螞蚱。那麼,如果數據是沒有順序的呢?那又該使用哪種數據結構呢?這個放到[學習javascript數據結構(三)——集合]中學習。

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