鏈表與LinkedList

真的膩害

鏈表

概念

說到鏈表,coder們都不會陌生,在日常開發(fā)中或多或少都會用到它。
它是鏈?zhǔn)酱鎯Φ木€性表,簡稱鏈表。鏈表由多個鏈表元素組成,這些元素稱為節(jié)點。結(jié)點之間通過邏輯連接,形成鏈?zhǔn)酱鎯Y(jié)構(gòu)。存儲結(jié)點的內(nèi)存單元,可以是連續(xù)的也可以是不連續(xù)的。邏輯連接與物理存儲次序沒有關(guān)系。
還是多說說概念上的東西,來說說鏈表的分類,從內(nèi)存角度出發(fā):鏈表可以分為靜態(tài)鏈表動態(tài)鏈表從鏈表存儲方式的角度出發(fā):鏈表可以分為單鏈表雙鏈表以及循環(huán)鏈表

  • 靜態(tài)鏈表:把線性表的元素放在數(shù)組中,不管物理上是不是連續(xù)存放的,它們之間的邏輯關(guān)系來連接,數(shù)組單位存放鏈表結(jié)點,結(jié)點的鏈域指向下一個元素的位置,也就是下一個元素所在的數(shù)組單元的下標(biāo)。既然需要數(shù)組來實現(xiàn),那么數(shù)組的長度是不能預(yù)支的。
  • 動態(tài)鏈表:克服了靜態(tài)鏈表的缺點。它動態(tài)地為節(jié)點分配存儲單元。當(dāng)有節(jié)點插入時,系統(tǒng)動態(tài)地為結(jié)點分配空間。在結(jié)點刪除時,應(yīng)該及時釋放相應(yīng)存儲單元,以防止內(nèi)存泄露。

  • 單鏈表: 單鏈表是一種順序存儲的結(jié)構(gòu)。
    有一個頭結(jié)點,沒有值域,只有連域,專門存放第一個結(jié)點的地址。
    有一個尾結(jié)點,有值域,也有鏈域,鏈域始終為NULL.
    所以,在單鏈表中為找第i個結(jié)點或數(shù)據(jù)元素,必須先找到第i-1結(jié)點或數(shù)據(jù)元素,而且必須知道頭結(jié)點,否則整個鏈表無法訪問。
單鏈表示意圖
  • 雙鏈表
    雙鏈表也是基于單鏈表的,單鏈表是單向的。而雙鏈表則在單鏈表的基礎(chǔ)上添加了一個鏈域。通過兩個鏈域,分別指向結(jié)點的前結(jié)點和后結(jié)點。這樣的話可以通過雙鏈表的任何結(jié)點訪問到它的前結(jié)點和后結(jié)點。但是雙鏈表還是不夠靈活,在實際編程中比較常用的是循環(huán)雙鏈表,但是循環(huán)雙鏈表比較麻煩。
雙向鏈表示意圖
  • 循環(huán)鏈接表
    循環(huán)鏈表由單鏈表演化而來。單鏈表的最后一個結(jié)點的鏈域指向NULL,而循環(huán)鏈表的建立,不要專門的頭結(jié)點,讓最后一個結(jié)點的鏈域指向鏈表結(jié)點。它與單鏈表的區(qū)別:
    區(qū)別一:鏈表的建立。單鏈表需要創(chuàng)建一個頭結(jié)點,專門存放第一個結(jié)點的地址。單鏈表的鏈域指向NULL。而循環(huán)鏈表的建立,不要專門的頭結(jié)點,讓最后一個結(jié)點的鏈域指向鏈表的頭結(jié)點。
    區(qū)別二:鏈表表尾的判斷。單鏈表判斷結(jié)點是否為表尾結(jié)點,只需判斷結(jié)點的鏈域值是否是NULL。如果是,則為尾結(jié)點;否則不是。而循環(huán)鏈表盤判斷是否為尾結(jié)點,則是判斷該節(jié)點的鏈域是不是指向鏈表的頭結(jié)點。

鏈表的實現(xiàn)

單鏈表(Single-Linked List)

public class SingleLinkedList<E> {
    transient int size = 0;
    private Node<E> first;

    private class Node<E> {
        E item;
        Node<E> next;
    }
}

插入節(jié)點

由于我們SIngleLinkedList類中維護(hù)了一個指向FirstNode的引用,所以在表頭插入節(jié)點是很容易的。

 public void insert(E item) {
        Node<E> oldFirst = first;
        first = new Node<>();
        first.item = item;
        first.next = oldFirst;
        size++;
    }

刪除節(jié)點

 /**
     * 從頭結(jié)點開始刪除
     * @return
     */
    public E delete() {
        if (first != null) {
            E item=first.item;
            //讓頭結(jié)點變成原來頭結(jié)點的下一個結(jié)點
            first=first.next;
            return item;
        } else {
            throw new NullPointerException("This SingleLinkedList is empty!");
        }
    }

雙向鏈表(DoubleLinkedList)

雙向鏈表相比與單鏈表的優(yōu)勢在于它同時支持高效的正向及反向遍歷,并且可以方便的在鏈表尾部刪除結(jié)點(單鏈表可以方便的在尾部插入結(jié)點,但不支持高效的表尾刪除操作)。

public class DoubleLinkedList<E> {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}

添加節(jié)點

 public void addFirst(E e) {
        Node<E> f = first;
        Node<E> newNode = new Node<E>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
    }

    public void addLast(E e) {
        Node<E> l = last;
        Node<E> newNode = new Node<E>(l, e, null);
        last = newNode;
        if(l==null){
            first=newNode;
        }else{
            l.next=newNode;
        }
        size++;
    }

在指定位置之前添加節(jié)點:

  public void add(int index, E element) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("index is illegal");
        }
        if (index == size) {
            addLast(element);
        }
        addBefore(element, node(index));
    }

    private void addBefore(E element, Node<E> node) {
        Node<E> prev = node.prev;
        Node<E> newNode = new Node<E>(prev, element, node);
        node.prev = newNode;
        if (prev == null)
            first = newNode;
        else
            prev.next = newNode;
        size++;
    }

    private Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;

        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i++)
                x = x.prev;
            return x;
        }
    }

刪除操作

 private E deleteFirst(Node<E> f) {//刪除第一個
        E element = f.item;
        Node<E> next = f.next;
        f.item = null;
        f.next = null;
        first = next;
        if (next == null) {
            last = null;
        } else {
            next.prev = null;
        }
        size--;
        return element;
    }

    private E deleteLast(Node<E> node) {//刪除最后一個
        E element = node.item;
        Node<E> prev = node.prev;
        node.item = null;
        node.prev = null;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        return element;
    }

    E delete(Node<E> x) {//刪除指定的Node
        E element = x.item;
        Node<E> next = x.next;
        Node<E> prev = x.prev;
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev=prev;
            x.next=null;
        }
        x.item=null;
        size--;
        return element;
    }

它們使用的地方

 public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    delete(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    delete(x);
                    return true;
                }
            }
        }
        return false;
    }

直接看代碼就知道所謂鏈表到底是怎么一回事了。

鏈表的特性

1 優(yōu)點

鏈表的主要優(yōu)勢有兩點:1,插入的時間復(fù)雜度為o(1);2,可以動態(tài)的改變大小。

2 缺點

由于其鏈?zhǔn)酱鎯Φ奶匦裕湵聿痪邆淞己玫目臻g局部性,也就是說,鏈表是一種緩存不友好的數(shù)據(jù)結(jié)構(gòu)。

LinkedList

java 中LinkedList中的源碼中添加刪除方法如我在上面寫的雙鏈表中的添加刪除方法基本一致。
LinkedList是基于雙向循環(huán)鏈表實現(xiàn)的(last.next=first,first.prev=last)。
LinkedList是非線程安全的,只在單線程下適合使用。
LinkedList實現(xiàn)了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現(xiàn)了Cloneable接口,能被克隆。
除了可以當(dāng)作鏈表來操作外,它還可以當(dāng)作棧,隊列和雙端隊列來使用。

 public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

public boolean offer(E e) {
        return add(e);
    }

public void push(E e) {
        addFirst(e);
    }
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
 public E pop() {
        return removeFirst();
    }
....

從源碼可以看出LinkedList不僅有鏈表的特征方法還是棧和隊列的特性方法。
對于LinkedList還有幾點要說的。
* 在查找和刪除某元素時,源碼中都劃分為該元素為null和不為null兩種情況來處理,LinkedList中允許元素為null。
* LinkedList是基于鏈表實現(xiàn)的,因此不存在容量不足的問題,所以這里沒有擴容的方法。
* 注意源碼中的Entry entry(int index)方法。該方法返回雙向鏈表中指定位置處的節(jié)點,而鏈表中是沒有下標(biāo)索引的,要指定位置出的元素,就要遍歷該鏈表,從源碼的實現(xiàn)中,我們看到這里有一個加速動作。源碼中先將index與長度size的一半比較,如果indexsize/2,就只從位置size往前遍歷到位置index處。這樣可以減少一部分不必要的遍歷,從而提高一定的效率(實際上效率還是很低)。
* LinkedList是基于鏈表實現(xiàn)的,因此插入刪除效率高,查找效率低(雖然有一個加速動作)。
* 要注意源碼中還實現(xiàn)了棧和隊列的操作方法,因此也可以作為棧、隊列和雙端隊列來使用。
## 一道鏈表的測試題
這道題來自[劍指offer](https://www.nowcoder.com/ta/coding-interviews?page=)上的一道題:
在一個排序的鏈表中,存在重復(fù)的結(jié)點,請刪除該鏈表中重復(fù)的結(jié)點,重復(fù)的結(jié)點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理后為 1->2->5

public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
    this.val = val;
}

}

public class Solution {

public ListNode deleteDuplication(ListNode pHead)
{
int maxNum = 0;
//int minNum = pHead.val;
//int length = 0;
for (ListNode x = pHead; x != null; x = x.next) {
// length++;
if (x.val > maxNum) {
maxNum = x.val;
}
}
int[] nums = new int[maxNum + 1];
for (ListNode x = pHead; x != null; x = x.next) {
nums[x.val]++;
}
ListNode first = null;
ListNode pFirst=null;
for (int index = 0; index <= maxNum; index++) {
if (nums[index] > 1) {
nums[index] = 0;
}
if (nums[index] > 0 && index > ( first==null?-1:first.val)) {
if (first != null) {
ListNode oldFirst = first;
first = new ListNode(index);
// first.val=index;
oldFirst.next = first;
} else {
first = new ListNode(index);
pFirst = first;
}

        }
    }
    return pFirst;
}

}

這是我寫出的一種答案,可能有待精簡和優(yōu)化,時間關(guān)系就在這里提供一種解法。等有時間加上注釋。![o(′^`)o](http://upload-images.jianshu.io/upload_images/3548660-40a109b5aaa98652.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1440/q/50)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容