鏈表

概念

鏈表的插入,只需要上一個(gè)節(jié)點(diǎn)的指針指向這個(gè)新增的節(jié)點(diǎn)篡腌,新增的節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn)碱璃。
刪除類似操作弄痹。

鏈表當(dāng)前節(jié)點(diǎn)只知道下個(gè)節(jié)點(diǎn)是誰,也并非連續(xù)存儲嵌器,隨機(jī)訪問元素時(shí)需要將整個(gè)鏈表遍歷來查找元素肛真。

添加和刪除時(shí)間復(fù)雜度:O(1),隨機(jī)查詢時(shí)間復(fù)雜度:O(n)

單鏈表

單鏈表有頭結(jié)點(diǎn)和尾結(jié)點(diǎn)爽航。頭結(jié)點(diǎn)用來記錄鏈表的基地址蚓让,尾結(jié)點(diǎn)指向空地址null庇谆。


循環(huán)鏈表

跟單鏈表的區(qū)別就在于尾結(jié)點(diǎn),單鏈表的尾結(jié)點(diǎn)指針指向空地址凭疮,表示這就是最后的結(jié)點(diǎn)了。而循環(huán)鏈表的尾結(jié)點(diǎn)指針是指向鏈表的頭結(jié)點(diǎn)串述。
當(dāng)要處理的數(shù)據(jù)具有環(huán)型結(jié)構(gòu)特點(diǎn)時(shí)执解,就特別適合采用循環(huán)鏈表。

雙向鏈表

雙向鏈表需要額外的兩個(gè)空間來存儲后繼結(jié)點(diǎn)和前驅(qū)結(jié)點(diǎn)的地址纲酗。所以衰腌,如果存儲同樣多的數(shù)據(jù),雙向鏈表要比單鏈表占用更多的內(nèi)存空間觅赊。雖然兩個(gè)指針比較浪費(fèi)存儲空間右蕊,但可以支持雙向遍歷,這樣也帶來了雙向鏈表操作的靈活性吮螺。


跳表

思路:升維思想饶囚,空間換時(shí)間



如果要插入一個(gè)值是4的新節(jié)點(diǎn),就不用8鸠补,7萝风,6,5紫岩,3這樣的查詢.可以7规惰,5,3這樣的查詢泉蝌。
這是一層節(jié)點(diǎn)歇万,還可以繼續(xù)擴(kuò)充成多層節(jié)點(diǎn),提取的極限就是同一層就有兩個(gè)節(jié)點(diǎn)的時(shí)候勋陪。
這種方式隨機(jī)訪問的時(shí)間復(fù)雜度降到了O(logn)
空間復(fù)雜度O(n)

鏈表的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):
鏈表本身沒有大小的限制贪磺,天然地支持動(dòng)態(tài)擴(kuò)容。
缺點(diǎn):
鏈表在內(nèi)存中并不是連續(xù)存儲诅愚,所以對CPU緩存不友好缘挽,沒辦法有效預(yù)讀。
對鏈表進(jìn)行頻繁的插入呻粹、刪除操作壕曼,還會導(dǎo)致頻繁的內(nèi)存申請和釋放,容易造成內(nèi)存碎片等浊,有可能會導(dǎo)致頻繁的 GC腮郊。
鏈表中的每個(gè)結(jié)點(diǎn)都需要消耗額外的存儲空間去存儲一份指向下一個(gè)結(jié)點(diǎn)的指針,所以內(nèi)存消耗會翻倍筹燕。

不同鏈表比較

鏈表刪除

  • 刪除結(jié)點(diǎn)中“值等于某個(gè)給定值”的結(jié)點(diǎn)轧飞;
  • 刪除給定指針指向的結(jié)點(diǎn)衅鹿。

雖然單純的刪除時(shí)間復(fù)雜度是O(1),但是要查找刪除的元素是個(gè)比較麻煩的問題过咬,鏈表的隨機(jī)訪問元素時(shí)間復(fù)雜度是O(n)大渤。
第一種情況,需要從頭開始遍歷鏈表來找到值等于給定值的結(jié)點(diǎn)掸绞。
第二種情況泵三,我們已經(jīng)找到了要?jiǎng)h除的結(jié)點(diǎn),但是刪除某個(gè)結(jié)點(diǎn)q需要知道其前驅(qū)結(jié)點(diǎn)衔掸,進(jìn)行尾結(jié)點(diǎn)指向改變來控制刪除操作烫幕。這種情況單鏈表不知道前驅(qū)節(jié)點(diǎn),查詢時(shí)間復(fù)雜度是O(n)敞映,而對于雙向鏈表時(shí)間復(fù)雜度是O(1)较曼。
插入也是一致。

有序列表的查詢

對于一個(gè)有序鏈表振愿,雙向鏈表的按值查詢的效率也要比單鏈表高一些捷犹。因?yàn)椋覀兛梢杂涗浬洗尾檎业奈恢胮冕末,每次查詢時(shí)伏恐,根據(jù)要查找的值與p的大小關(guān)系,決定是往前還是往后查找栓霜,所以平均只需要查找一半的數(shù)據(jù)翠桦。

如何選取用單向鏈表還是雙向鏈表

當(dāng)內(nèi)存空間充足的時(shí)候,如果我們更加追求代碼的執(zhí)行速度胳蛮,我們就可以選擇空間復(fù)雜度相對較高销凑、但時(shí)間復(fù)雜度相對很低的算法或者數(shù)據(jù)結(jié)構(gòu)。相反仅炊,如果內(nèi)存比較緊缺斗幼,比如代碼跑在手機(jī)或者單片機(jī)上,這個(gè)時(shí)候抚垄,就要反過來用時(shí)間換空間的設(shè)計(jì)思路蜕窿。
使用緩存就是空間換時(shí)間的設(shè)計(jì)思想。

如何寫鏈表代碼

理解指針或引用的含義

p->next=q呆馁。這行代碼是說桐经,p結(jié)點(diǎn)中的next指針存儲了q結(jié)點(diǎn)的內(nèi)存地址。
p->next=p->next->next浙滤。這行代碼表示阴挣,p結(jié)點(diǎn)的next指針存儲了p結(jié)點(diǎn)的下下一個(gè)結(jié)點(diǎn)的內(nèi)存地址。

防止指針丟失的情況

指針p指向節(jié)點(diǎn)A纺腊,需求想要在節(jié)點(diǎn)A畔咧,B中插入節(jié)點(diǎn)X茎芭。

p->next = x;  // 將p的next指針指向x結(jié)點(diǎn);
x->next = p->next;  // 將x的結(jié)點(diǎn)的next指針指向b結(jié)點(diǎn)誓沸;

這種寫法是錯(cuò)誤的梅桩,因?yàn)榈谝恍写a,P節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)位置已經(jīng)指向到x拜隧,不再指向B了宿百。所以第二行變成了X節(jié)點(diǎn)指向了自己,鏈表從此斷了虹蓄。
正確寫法是1和2顛倒過來,先把x的下一個(gè)節(jié)點(diǎn)指向B幸撕,再把A的下一個(gè)節(jié)點(diǎn)指向x薇组。

如果是刪除,注意C的話要釋放內(nèi)存坐儿,防止內(nèi)存泄漏律胀。

特殊節(jié)點(diǎn)的添加和刪除

1.正常節(jié)點(diǎn)的添加

// 新建的節(jié)點(diǎn)尾節(jié)點(diǎn)指向p的下一個(gè)節(jié)點(diǎn) 
new_node->next = p->next;
// p的尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
p->next = new_node;

2.添加頭節(jié)點(diǎn)

if (head == null) {
  head = new_node;
}

3.正常節(jié)點(diǎn)的刪除

p->next = p->next->next;

4.刪除最后一個(gè)節(jié)點(diǎn)

if (head->next == null) {
   head = null;
}

這種方式很容易出錯(cuò),需要引入哨兵節(jié)點(diǎn)貌矿,在任何時(shí)候炭菌,不管鏈表是不是空,head指針都會一直指向這個(gè)哨兵結(jié)點(diǎn)逛漫。我們也把這種有哨兵結(jié)點(diǎn)的鏈表叫帶頭鏈表黑低。相反,沒有哨兵結(jié)點(diǎn)的鏈表就叫作不帶頭鏈表酌毡。哨兵結(jié)點(diǎn)是不存儲數(shù)據(jù)的克握。


// 在數(shù)組a中,查找key枷踏,返回key所在的位置
// 其中菩暗,n表示數(shù)組a的長度
// 我舉2個(gè)例子,你可以拿例子走一下代碼
// a = {4, 2, 3, 5, 9, 6}  n=6 key = 7
// a = {4, 2, 3, 5, 9, 6}  n=6 key = 6
int find(char* a, int n, char key) {
  if(a == null || n <= 0) {
    return -1;
  }
  
  // 這里因?yàn)橐獙[n-1]的值替換成key旭蠕,所以要特殊處理這個(gè)值
  if (a[n-1] == key) {
    return n-1;
  }
  
  // 把a(bǔ)[n-1]的值臨時(shí)保存在變量tmp中停团,以便之后恢復(fù)。tmp=6掏熬。
  // 之所以這樣做的目的是:希望find()代碼不要改變a數(shù)組中的內(nèi)容
  char tmp = a[n-1];
  // 把key的值放到a[n-1]中佑稠,此時(shí)a = {4, 2, 3, 5, 9, 7}
  a[n-1] = key;
  
  int i = 0;
  // while 循環(huán)比起代碼一,少了i<n這個(gè)比較操作
  while (a[i] != key) {
    ++i;
  }
  
  // 恢復(fù)a[n-1]原來的值,此時(shí)a= {4, 2, 3, 5, 9, 6}
  a[n-1] = tmp;
  
  if (i == n-1) {
    // 如果i == n-1說明旗芬,在0...n-2之間都沒有key讶坯,所以返回-1
    return -1;
  } else {
    // 否則,返回i岗屏,就是等于key值的元素的下標(biāo)
    return i;
  }
}

重點(diǎn)留意邊界條件處理

如果鏈表為空時(shí)辆琅,代碼是否能正常工作漱办?
如果鏈表只包含一個(gè)結(jié)點(diǎn)時(shí),代碼是否能正常工作婉烟?
如果鏈表只包含兩個(gè)結(jié)點(diǎn)時(shí)娩井,代碼是否能正常工作?
代碼邏輯在處理頭結(jié)點(diǎn)和尾結(jié)點(diǎn)的時(shí)候似袁,是否能正常工作洞辣?

LinkedList源碼

底層數(shù)據(jù)結(jié)構(gòu)雙向鏈表。鏈表中的元素是Node昙衅。

private static class Node<E> {
    E item;
    LinkedList.Node<E> next;
    LinkedList.Node<E> prev;

    Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
        this.item = var2;
        this.next = var3;
        this.prev = var1;
    }
}

增加

可以從頭部增加扬霜,也可以從尾部增加。默認(rèn)add從尾部增加而涉。
1.從頭部增加節(jié)點(diǎn)

private void linkFirst(E var1) {
    LinkedList.Node var2 = this.first;
    // 創(chuàng)建的節(jié)點(diǎn)著瓶,前一個(gè)是null,當(dāng)前節(jié)點(diǎn)是var1啼县,尾節(jié)點(diǎn)是var2
    LinkedList.Node var3 = new LinkedList.Node((LinkedList.Node)null, var1, var2);
    // 新建的節(jié)點(diǎn)作為頭節(jié)點(diǎn)
    this.first = var3;
    // 如果之前沒有頭節(jié)點(diǎn)材原,意味著之前鏈表為空,當(dāng)前添加元素季眷,頭節(jié)點(diǎn)余蟹,尾節(jié)點(diǎn)都是同一個(gè)節(jié)點(diǎn)
    if (var2 == null) {
        this.last = var3;
    } else {
        var2.prev = var3;
    }

    ++this.size;
    ++this.modCount;
}

2.從尾部增加節(jié)點(diǎn)

void linkLast(E var1) {
    LinkedList.Node var2 = this.last;
    // 當(dāng)前節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)是之前鏈表的尾節(jié)點(diǎn),下一個(gè)節(jié)點(diǎn)是null
    LinkedList.Node var3 = new LinkedList.Node(var2, var1, (LinkedList.Node)null);
    this.last = var3;

    if (var2 == null) {
        this.first = var3;
    } else {
        var2.next = var3;
    }

    ++this.size;
    ++this.modCount;
}

刪除

可以選擇從頭部刪除子刮,也可以選擇從尾部刪除威酒,給定index刪除節(jié)點(diǎn),刪除給定節(jié)點(diǎn)挺峡。刪除操作會把節(jié)點(diǎn)的值兼搏,前后指向節(jié)點(diǎn)都置為null,幫助GC進(jìn)行回收沙郭。
remove(Object o)
添加沒有特殊判斷佛呻,可以添加null,刪除也可以刪除null病线。
從頭節(jié)點(diǎn)向尾節(jié)點(diǎn)遍歷吓著,刪除null,判斷節(jié)點(diǎn)數(shù)據(jù)是否為null送挑。
刪除其余元素绑莺,通過equals判斷值是否相同。

E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    // 刪除的節(jié)點(diǎn)就是頭節(jié)點(diǎn)
    if (prev == null) {
        // 下一個(gè)節(jié)點(diǎn)作為頭節(jié)點(diǎn)
        first = next;
    } else {
        // 前一個(gè)節(jié)點(diǎn)的尾指針指向下一個(gè)節(jié)點(diǎn)
        prev.next = next;
        // 當(dāng)前節(jié)點(diǎn)的頭節(jié)點(diǎn)置空
        x.prev = null;
    }

    if (next == null) {
        // 上一個(gè)節(jié)點(diǎn)置空
        last = prev;
    } else {
        next.prev = prev;
        // 當(dāng)前節(jié)點(diǎn)的尾節(jié)點(diǎn)為空
        x.next = null;
    }
    // 刪除的節(jié)點(diǎn)數(shù)據(jù)為空
    x.item = null;
    size--;
    modCount++;
    return element;
}

remove(int index)
需要找到這個(gè)index對應(yīng)的節(jié)點(diǎn)信息惕耕,通過上面的刪除方式刪除節(jié)點(diǎn)纺裁。

Node<E> node(int index) {
    // 二分
    if (index < (size >> 1)) {
        // 獲取到頭節(jié)點(diǎn),根據(jù)index一直向下獲取
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // 獲取到尾節(jié)點(diǎn)根據(jù)index向上獲取
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

remove()
相當(dāng)于刪除鏈表頭節(jié)點(diǎn)元素

private E unlinkFirst(Node<E> f) {
    // 獲取要?jiǎng)h除節(jié)點(diǎn)的下個(gè)條目和當(dāng)前條目
    final E element = f.item;
    final Node<E> next = f.next;
    // 清空要?jiǎng)h除節(jié)點(diǎn)的內(nèi)容,把它的尾指針指空
    f.item = null;
    f.next = null; // help GC
    // 頭節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)成為頭節(jié)點(diǎn)
    first = next;
    // 當(dāng)個(gè)鏈表只有一個(gè)節(jié)點(diǎn)
    if (next == null)
        last = null;
    else
        // 頭節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)頭指針指向null欺缘,它要成為頭節(jié)點(diǎn)
        next.prev = null;
    size--;
    modCount++;
    return element;
}

迭代器

從頭到尾方式迭代栋豫。

hashNext方法

public boolean hasNext() {
    // 下一個(gè)節(jié)點(diǎn)的索引小于鏈表的大小
    return this.nextIndex < LinkedList.this.size;
}

next方法

public E next() {
    this.checkForComodification();
    if (!this.hasNext()) {
        throw new NoSuchElementException();
    } else {
        // 當(dāng)前要?jiǎng)h除的節(jié)點(diǎn)
        this.lastReturned = this.next;
        // next是下一個(gè)節(jié)點(diǎn)了,為下次迭代做準(zhǔn)備
        this.next = this.next.next;
        ++this.nextIndex;
        return this.lastReturned.item;
    }
}

remove方法

public void remove() {
    this.checkForComodification();
    if (this.lastReturned == null) {
        throw new IllegalStateException();
    } else {
        LinkedList.Node var1 = this.lastReturned.next;
        // 刪除當(dāng)前節(jié)點(diǎn)
        LinkedList.this.unlink(this.lastReturned);
        if (this.next == this.lastReturned) {
            this.next = var1;
        } else {
            --this.nextIndex;
        }

        this.lastReturned = null;
        ++this.expectedModCount;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谚殊,一起剝皮案震驚了整個(gè)濱河市丧鸯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漆羔,死亡現(xiàn)場離奇詭異,居然都是意外死亡蜂怎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門置尔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杠步,“玉大人,你說我怎么就攤上這事撰洗±河洌” “怎么了腐芍?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵差导,是天一觀的道長。 經(jīng)常有香客問我猪勇,道長设褐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任泣刹,我火速辦了婚禮助析,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椅您。我一直安慰自己外冀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布掀泳。 她就那樣靜靜地躺著雪隧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪员舵。 梳的紋絲不亂的頭發(fā)上脑沿,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音马僻,去河邊找鬼庄拇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛韭邓,可吹牛的內(nèi)容都是我干的措近。 我是一名探鬼主播溶弟,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼熄诡!你這毒婦竟也來了可很?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤凰浮,失蹤者是張志新(化名)和其女友劉穎我抠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袜茧,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菜拓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笛厦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纳鼎。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖裳凸,靈堂內(nèi)的尸體忽然破棺而出贱鄙,到底是詐尸還是另有隱情,我是刑警寧澤姨谷,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布逗宁,位于F島的核電站,受9級特大地震影響梦湘,放射性物質(zhì)發(fā)生泄漏瞎颗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一捌议、第九天 我趴在偏房一處隱蔽的房頂上張望哼拔。 院中可真熱鬧,春花似錦瓣颅、人聲如沸倦逐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檬姥。三九已至,卻和暖如春守谓,著一層夾襖步出監(jiān)牢的瞬間穿铆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工斋荞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荞雏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像凤优,于是被迫代替她去往敵國和親悦陋。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353