大话数据结构之链表(一)

上篇回顾

看过之前的文章,应该知道线性表包含顺序表和链表。线性表的典型是数组,List等。那么本篇来讲解一下链表的相关知识。


链表包含两种,或者说三种,即单链表, 多链表, 循环链表。其中单链表只表示的next的关系,而多链表即包含了next, 又包含了pre;循环链表则是单链表与多链表的衍生链表,即头尾相链,构成循环。循环链表又分为单循环链表及多循环链表。

有图为证

单链表
多链表
单循环链表

链表的定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

从定义可以得到三个有用的信息, 链表的空间不是连续的,是无序的, 是通过链接查询的。前面说过顺序表是通过位序查询的,所以必须要连续的空间,而且要有序。那么通俗地怎么理解链表的概念呢?举个例子,大家见过链子吗?一条链子由若干个铁环相互链接组成,如下

image.png

如果把这个链子拆分,每个铁环都是一个单位。链表中的单元称之为节点Node。两个铁坏之间是扣在一起的,如果找到铁环1,必然通过联系能找到铁环2;但是不能直接的通过铁环1,找到铁环3。这就是链表。

链表是由若干节点Node组成,相邻的两个节点之间存在单一(单链表)或相互(多链表)的联系。假设上图为链表LinkedTable,简称LT; 把每个铁环看成一个节点,分别命名为A,B,C,D; 相互关系为:A->B->C->D

这个节点可以设计为:

 private static class Node<E> {
        E item;
        Node<E> next;
        public Node(E item, Node<E> next) {
              this.item = item;
              this.next = next;
        }
    }

item为铁环赋予的数据, next为下一个铁环

        Node<String> nodeD = new Node<>("A", null);
        Node<String> nodeC = new Node<>("D", null);
        Node<String> nodeB = new Node<>("C", null);
        Node<String> nodeA = new Node<>("B", null);

上述代码创建了4个节点nodeA,nodeB,nodeC,nodeD; 分别赋予值A,B,C,D; 关系为A->B->C->D; 这就是上图的链表模型了。其中A的next是B, B的next是C, C的next是D。我们可以通过A找到B,但不能直接找到C。

链表的长度就是铁环的数量,也就是节点数。
在上述链表模型中,nodeA称为头节点, nodeD则称为尾节点。

以上的概念,称之为单链表。

既然已经了解单链表的概念,那么多链表就容易的多了。多链表在指向下一个节点的基础上,添加了上一个节点的指向。 我们修改一下上述Node

 private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> pre;
        public Node(E item, Node<E> next, Node<E> pre) {
              this.item = item;
              this.next = next;
              this.pre = pre;
        }
    }

从开篇的示意图就能看出来,多的是一个向前的箭头指向。那么什么是循环链表呢? 看开篇的图,尾节点指向头节点就是循环链表。看到这里,你可以试着自己构建一下这三种链表..


明白了链表的概念, 如果已知一个链表

 Node<Integer> n1 = new Node<>(1, null);
        Node<Integer> n2 = new Node<>(2, null);
        Node<Integer> n3 = new Node<>(3, null);
        Node<Integer> n4 = new Node<>(4, null);

        n1.next = n2;
        n2.next = n3;
        n3.next = n4;

根据链表的特性,如何来遍历所有节点呢?我想你一定用过Cursor,已知头节点,我们来遍历一下

  Node<Integer> parent = n1;
        while (parent != null) {
            System.out.println(parent.data + "");
            parent = parent.next;
        }

就是根据第一个铁环, 然后找第二个;然后根据第二个,找第三个;以此类推...

链表的链式关系,就是如此,相信大家都已经明白了。


明白了链表的概念,那么要上硬菜了,也是链表中最难理清的节点的添加与删除。有一个链表A-B-C-D,如果我想在C与D之间插入一个节点E, 那么过程是这样的


image.png

根据上图的铁链,想象一下,如果要在第3个与第4个之间再加一个铁环,你会怎么做?是不是这样的

1) 首先, 找到第3个铁环
2) 其次,找到第4个铁环

因为新加的铁环要放在这两个铁环中间,要想加进去,必然要把新铁环和3和4链起来才行。 那么首先要找到3和4,这是准备工作。

3) 砸开3与4之间的连接
4)新铁环与3连接
5)新铁环与4连接

根据这个步骤,再看上图。假如已经找到了NodeC, 然后根据next找到NodeD, 那么我们准备工作就做好了,接下来就是断开原链接, 添加新链接的过程了。看代码

       // 创建要插入的新节点
        Node<String> E = new Node<>("E", null);
        // 找到待插位置的节点C与D
        Node<String> C = findNodeC();
        Node<String> D = C.next;
        // 开始插入
        C.next = E;
        E.next = D;

根据以上分析与代码,你知道怎么插入新节点了吗?总结如下:
1) 找到待插位置的两个节点
2) 断开原来的连接关系
3) 分别与前驱节点,后继节点建立新连系

如果明白了如何在单链表插入节点,那么恭喜你!删除节点你也会很快明白的。还是那个栗子,如果我想把第2个铁环去掉,怎么做呢?

1) 找到第1个和第3个铁环
2) 断开第1个和第2个, 第2个和第3个铁环的练习
3) 将第1个和第3个铁环连接

        N2.next = null;
        N1.next = N3;

按步骤,先断开三者的连系, N1与N2, N2与N3之间,N2.next = null把N2与N3的连系断开了;N1因为断开后还要和N3连接,这里直接把N1和N3连接,就相当于断开N1和N2的连系,同时创建N1和N3的新连接了。

有人问了,你把找Node节点的步骤省略了,真的好么? 好吧,知道头节点,还找不到想要的节点吗? 我们的方式就是遍历,遍历上面说了,怎么判断目标节点的前驱和后继呢? 在删除时, 前驱的next必然是target啊, target直接判断是同一个节点就行了啊, 知道target了,next就能得到后继了吧。

你看,就是这么简单,只是有点绕!结合现实理解一下,其实理解了很容易记忆的。

总结步骤:

1) 找

找什么呢? 插入时,根据插入的位置找插入后的前驱和后继,如在A与B之间插入C,那么插入后B肯定是C是后继,A是C的前驱; 删除时,根据待删除元素的位置查找受影响的前驱和后继,肯定是该元素本身的前驱和后继。

2)断
为什么是先找呢? 因为断了之后,可能就找不到了。 断字诀指的是切断原有的关系,即插入元素的前驱和后继的关系,也即删除元素与前驱, 删除元素与后继的关系。

3)连
旧的关系已断,新的关系建立起来。建立指定元素Target(指插入的元素或删除的元素)动作后,新的关系的建立。即在插入时插入后的target与前驱, target与后继的关系; 也即在删除时, 删除后原删除元素的前驱与后继的新关系。


多链表的插入与删除操作,相比于单链表来说,稍微复杂了一点,因为添加了指向前一个元素的指针。
先看插入,如果在A和B之间插入元素C


image.png

那么还是按照上面总结的单链表的三字诀,来试一下

//1. 首先切断A与B之间的关系,如图即A的next是B, B的pre是A
  NA.next = null;
  NB.pre = null;
// 2.建立A与C的新关系,如图即插入后,A的next是C, C的pre是A
 NA.next = NC;
 NC.pre = NA;
// 3. 建立B与C的新关系,如图即插入后,C的next是B, B的pre是C
NC.next = NB;
NB.pre = NC;

那么一次插入就完成了,相对单链表而言,只多了一层pre的关系。而这层关系还是相对的,A的next是B,那么B的pre必然是A。

相同的道理,如果A->B->C的链表中,要删除B,那么

1. 断开A-B的关系
NA.next = null;
NB.pre = null;
2.断开B-C的关系
NB.next = null;
NC.pre = NB;
// 3.在A与C之间建立新关系
NA.next = NC;
NC.pre = NA;

理解这个过程了,其实很好实现。 以上删除与插入的代码会有重复,为了完整的重现这个过程。根据情况可以进行合并,合并时一定要考虑清楚断开和连接的顺序。

以上就是链表的全部概念了,如果你还不理解,那么可以评论给我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容