鏈表與LinkedList

真的膩害

鏈表

概念

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

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

  • 單鏈表: 單鏈表是一種順序存儲的結(jié)構(gòu)杖玲。
    有一個(gè)頭結(jié)點(diǎn)顿仇,沒有值域,只有連域摆马,專門存放第一個(gè)結(jié)點(diǎn)的地址臼闻。
    有一個(gè)尾結(jié)點(diǎn),有值域囤采,也有鏈域述呐,鏈域始終為NULL.
    所以,在單鏈表中為找第i個(gè)結(jié)點(diǎn)或數(shù)據(jù)元素斑唬,必須先找到第i-1結(jié)點(diǎn)或數(shù)據(jù)元素,而且必須知道頭結(jié)點(diǎn),否則整個(gè)鏈表無法訪問恕刘。
單鏈表示意圖
  • 雙鏈表
    雙鏈表也是基于單鏈表的缤谎,單鏈表是單向的。而雙鏈表則在單鏈表的基礎(chǔ)上添加了一個(gè)鏈域褐着。通過兩個(gè)鏈域坷澡,分別指向結(jié)點(diǎn)的前結(jié)點(diǎn)和后結(jié)點(diǎn)。這樣的話可以通過雙鏈表的任何結(jié)點(diǎn)訪問到它的前結(jié)點(diǎn)和后結(jié)點(diǎn)含蓉。但是雙鏈表還是不夠靈活频敛,在實(shí)際編程中比較常用的是循環(huán)雙鏈表,但是循環(huán)雙鏈表比較麻煩馅扣。
雙向鏈表示意圖
  • 循環(huán)鏈接表
    循環(huán)鏈表由單鏈表演化而來斟赚。單鏈表的最后一個(gè)結(jié)點(diǎn)的鏈域指向NULL,而循環(huán)鏈表的建立差油,不要專門的頭結(jié)點(diǎn)拗军,讓最后一個(gè)結(jié)點(diǎn)的鏈域指向鏈表結(jié)點(diǎn)。它與單鏈表的區(qū)別:
    區(qū)別一:鏈表的建立蓄喇。單鏈表需要創(chuàng)建一個(gè)頭結(jié)點(diǎn)发侵,專門存放第一個(gè)結(jié)點(diǎn)的地址。單鏈表的鏈域指向NULL妆偏。而循環(huán)鏈表的建立刃鳄,不要專門的頭結(jié)點(diǎn),讓最后一個(gè)結(jié)點(diǎn)的鏈域指向鏈表的頭結(jié)點(diǎn)钱骂。
    區(qū)別二:鏈表表尾的判斷叔锐。單鏈表判斷結(jié)點(diǎn)是否為表尾結(jié)點(diǎn),只需判斷結(jié)點(diǎn)的鏈域值是否是NULL罐柳。如果是掌腰,則為尾結(jié)點(diǎn);否則不是张吉。而循環(huán)鏈表盤判斷是否為尾結(jié)點(diǎn)齿梁,則是判斷該節(jié)點(diǎn)的鏈域是不是指向鏈表的頭結(jié)點(diǎn)。

鏈表的實(shí)現(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é)點(diǎn)

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

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

刪除節(jié)點(diǎn)

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

雙向鏈表(DoubleLinkedList)

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

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é)點(diǎn)

 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é)點(diǎn):

  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) {//刪除第一個(gè)
        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) {//刪除最后一個(gè)
        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)點(diǎn)

鏈表的主要優(yōu)勢有兩點(diǎn):1昆码,插入的時(shí)間復(fù)雜度為o(1);2气忠,可以動態(tài)的改變大小邻储。

2 缺點(diǎn)

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

LinkedList

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

 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不僅有鏈表的特征方法還是棧和隊(duì)列的特性方法蜂绎。
對于LinkedList還有幾點(diǎn)要說的栅表。
* 在查找和刪除某元素時(shí),源碼中都劃分為該元素為null和不為null兩種情況來處理师枣,LinkedList中允許元素為null怪瓶。
* LinkedList是基于鏈表實(shí)現(xiàn)的,因此不存在容量不足的問題践美,所以這里沒有擴(kuò)容的方法洗贰。
* 注意源碼中的Entry entry(int index)方法。該方法返回雙向鏈表中指定位置處的節(jié)點(diǎn)陨倡,而鏈表中是沒有下標(biāo)索引的敛滋,要指定位置出的元素,就要遍歷該鏈表兴革,從源碼的實(shí)現(xiàn)中绎晃,我們看到這里有一個(gè)加速動作。源碼中先將index與長度size的一半比較杂曲,如果indexsize/2庶艾,就只從位置size往前遍歷到位置index處。這樣可以減少一部分不必要的遍歷擎勘,從而提高一定的效率(實(shí)際上效率還是很低)咱揍。
* LinkedList是基于鏈表實(shí)現(xiàn)的,因此插入刪除效率高棚饵,查找效率低(雖然有一個(gè)加速動作)煤裙。
* 要注意源碼中還實(shí)現(xiàn)了棧和隊(duì)列的操作方法掩完,因此也可以作為棧、隊(duì)列和雙端隊(duì)列來使用硼砰。
## 一道鏈表的測試題
這道題來自[劍指offer](https://www.nowcoder.com/ta/coding-interviews?page=)上的一道題:
在一個(gè)排序的鏈表中藤为,存在重復(fù)的結(jié)點(diǎn),請刪除該鏈表中重復(fù)的結(jié)點(diǎn)夺刑,重復(fù)的結(jié)點(diǎn)不保留,返回鏈表頭指針分别。 例如遍愿,鏈表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)化耘斩,時(shí)間關(guān)系就在這里提供一種解法沼填。等有時(shí)間加上注釋。![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)系作者
  • 序言:七十年代末括授,一起剝皮案震驚了整個(gè)濱河市坞笙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荚虚,老刑警劉巖薛夜,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異版述,居然都是意外死亡梯澜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門渴析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晚伙,“玉大人,你說我怎么就攤上這事俭茧∨亓疲” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵母债,是天一觀的道長午磁。 經(jīng)常有香客問我,道長场斑,這世上最難降的妖魔是什么漓踢? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮漏隐,結(jié)果婚禮上喧半,老公的妹妹穿的比我還像新娘。我一直安慰自己青责,他們只是感情好挺据,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布取具。 她就那樣靜靜地躺著,像睡著了一般扁耐。 火紅的嫁衣襯著肌膚如雪暇检。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天婉称,我揣著相機(jī)與錄音块仆,去河邊找鬼。 笑死王暗,一個(gè)胖子當(dāng)著我的面吹牛悔据,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俗壹,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼科汗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绷雏?” 一聲冷哼從身側(cè)響起头滔,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涎显,沒想到半個(gè)月后坤检,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡期吓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年缀蹄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘婶。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缺前,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悬襟,到底是詐尸還是另有隱情衅码,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布脊岳,位于F島的核電站逝段,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏割捅。R本人自食惡果不足惜奶躯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亿驾。 院中可真熱鬧嘹黔,春花似錦、人聲如沸莫瞬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至喂江,卻和暖如春召锈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背获询。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工涨岁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吉嚣。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓卵惦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓦戚。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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