目录
前面我们已经讲了重要的一种数据结构——数组,如果说数组是方便读取数据,那么今天所学习的链表便是方便写入数据的数据结构,为什么这么说呢?让我们走进今天的链表学习。
首先让我们来看一个最基础的单向链表:

由图可见,链表和数组数据结构最主要的区别是链表是单线联络的,就像是工厂的产品,一般都是生产之后,然后交给超市等批发商,最后才能到达消费者的手中,产品的运输,就像是链表。
链表(linked list)是一种在物理中非连续 ,非顺序的数据结构,由若干节点(node)所组成。
由上图可知,单向链表又包含了两个部分,一是存放数据的变量data,另一个是指向下一个节点的指针next。
public static class Node{
int data;
Node next;
}
链表的第一个节点称为头节点,最后一个节点叫做尾节点,尾节点的next指针指向NULL。
那么,有的人就要发问了,链表总是通过一个节点寻找到下一个节点,那如何通过链表的一个节点寻找到它的前一个结点呢?
这就用到了双向链表:双向链表不仅拥有原来的data和next,还拥有指向前一个节点的指针prev。
如图所示:

接下来我们来看一下链表在内存中的存储方式:
如果说数组在内存中的存储方式为顺序存储,那么链表在内存中的存储方式就是随机存储。
何为随机存储呢?
上一篇博文中我们讲到数组在内存中的存储存储方式是顺序存储。而链表则是采用了见缝插针的方式。链表的每一个节点分配在内存中的不同位置,用next指针联系起来,这样可以灵活的运用零碎的内存空间。
如图所示:

(其中:灰色表示没有数据,绿色表示其他数据,红色表示该链表数据,直线指向为next指针指向,为3->1->6->4->8->7->6)。
还是上面那个例子,如果工厂发现某一产品有质量问题,那么应该如何找到有质量问题的产品呢?
答案就是先从工厂开始找,无果,则去下一级经销商中寻找,无果,则通过下一级经销商找到下下一级经销商,若还没有,就继续寻找,最坏的结果就是直至找到最底一级——客户。由此可见,该查找方式就是一级一级寻找,链表亦是如此。
链表的查找方式就是从头结点开始,向后一个一个节点逐一查找。
例如给出一个链表,需要查找从头结点开始的第3个节点
1.第一步,将指针定位在头节点。

2.第二步,根据头结点的next ,定位到第二个节点。

3.第三步,根据第二个节点的next指针,定位到第三个节点,查找完毕。
如果不考虑查找结点的过程,链表的更新会像数组一样简单,直接把旧数据改成新数据即可。

与数组类似,链表插入节点时,同样分为三种情况。
尾部插入
中间插入
头部插入
尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。

头部插入,可以分成两个步骤。
第一步,把新节点的next指针指向原先的头节点。
第二步,将新节点设置为链表的头节点。
中间插入,同样分为两个步骤。
第一步,新节点的next指针,只想插入位置的节点。
第二步,插入位置前置节点的next指针,指向新节点。

只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要考虑像数组一样的扩容问题。
下面我们来看一下插入操作的代码:
public class ClassNode{
//链表实际大小
private int size;
//头结点指针
private Node head;
//尾结点指针
private Node last;
public static class Node{
int data;//数据域
Node next;//节点指针
Node(int data){
this.data = data;
}
//data为插入元素,index为插入位置
public void insert(int data,int index) throws Exception{
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("超出链表结点范围!");
}
Node insertedNode = new Node(data);
if(size == 0){
//空链表
head = insertedNode;
last = insertedNode;//链表的第一个节点既是头节点又是尾节点
}else if(index == 0){
//插入头部
insertedNode.next = head;//新节点指针指向原先的头节点
head = insertedNode;//新节点变为链表的头节点
}else if(size == index){
//插入尾部
last.next = insertedNode;//最后一个节点的指针指向新插入的节点
last = insertedNode;//新节点变为链表的尾节点
}else{
//插入中间
Node prevNode = get(index-1);//通过get函数找到插入位置的前一个节点
insertedNode.next = prevNode.next;
prevNode.next = insertedNode;
}
size++;
}
}
附:链表查找元素的函数get()
//find-查找的位置
public Node get(int find) throws Exception{
if(find < 0 || find > size){
throw new IndexOutOfBoundsException("超出链表结点范围!");
}
Node temp = head;
for(int i=0; i<find; i++){
temp = temp.next;
}
return temp;
}
链表的删除同样分为三种情况:
尾部删除
头部删除
中间删除
尾部删除,把倒数第二个节点的next指针指向空即可。

头部删除,也很简单,把链表的头节点设为原先头结点的next指针即可。
中间删除,也同样简单,把删除节点的前置节点的next指针,指向删除结点的下一个节点即可。
需要注意的是,许多高级语言如java,拥有自动化的垃圾回收装置,所以不用刻意去释放删除的节点,只要没有外部引用它们,被删除的节点就会自动释放。
代码如下:
//pos:删除的位置
public Node remove(int pos) throws Exception{
if(pos < 0 || pos > size){
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
Node removedNode = null;
if(pos == 0){
//删除头节点
removedNode = head;
head.next = head;
}else if(pos == size-1){
//删除尾节点
Node prevNode = get(pos - 1);
removedNode = prevNode.next;
prevNode.next = null;
}else{
//删除中间节点
Node prevNode = get(pos - 1);
Node nextNode = prevNode.next.next;
removedNode = prevNode.next;
prevNode.next = nextNode;
}
size--;
return removedNode;
}
根据对于数组和链表的学习,我们知道它们都属于线性结构,而它们没有绝对的好与坏。
我们来对比一下性能:
| 查找 | 更新 | 插入 | 删除 | |
| 数组 | O(1) | O(1) | O(n) | O(n) |
| 链表 | O(n) | O(1) | O(1) | O(1) |
这个表格也印证了一开始的结论:读操作多,写操作少用数组,反之用链表。

前文,我们实现了认识了链表这一结构,并实现了无头单向非循环链表,接下来我们实现另一种常用的链表结构,带头双向循环链表。如有仍不了解单向链表的,请看这一篇文章(7条消息)【数据结构和算法】认识线性表中的链表,并实现单向链表_小王学代码的博客-CSDN博客目录前言一、带头双向循环链表是什么?二、实现带头双向循环链表1.结构体和要实现函数2.初始化和打印链表3.头插和尾插4.头删和尾删5.查找和返回结点个数6.在pos位置之前插入结点7.删除指定pos结点8.摧毁链表三、完整代码1.DSLinkList.h2.DSLinkList.c3.test.c总结前言带头双向循环链表,是链表中最为复杂的一种结
作者|Harper审核 |gongyouliu编辑|auroral-L机器学习的商业应用上期给大家介绍了机器学习的概念,但是理解机器学习最好方法之一,就是了解其在具体商业世界中的各种应用。在道格’罗斯的这本《认识AI,人工智能赋能商业》中,介绍了几类机器学习的商业应用,在这里我给大家归纳一下。第一,数据安全,为了避免被发现,制造恶意软件的人会不断更改代码,通常为2%~10%的修改,但是通过机器学习,安全软件可以适应这一小部分变化,并准确识别新创建的恶意软件。它还可以寻找访问方式的模式,以识别可能的安全威胁。第二,投资。机器学习使得计算机能够处理大量的财务数据,并利用其发现的规律预测市场及每只股
我在网上查了几个Ruby教程,他们似乎什么都用数组。那么如何在Ruby中实现以下数据结构呢?堆栈队列链表map组 最佳答案 (从评论中移出)好吧,通过限制堆栈或队列方法(push、pop、shift、unshift),数组可以是堆栈或队列。使用push/pop提供LIFO(后进先出)行为(堆栈),而使用push/shift或unshift/pop提供FIFO行为(队列)。map是hashes,和一个Set类已经存在。您可以使用类实现链表,但数组将使用标准数组方法提供类似于链表的行为。 关
我是JS的新手,组织数据的概念让我有些困惑,我试图从特定的数组格式中获取数据(因为这是我必须使用的格式)并将其输出为另一种特定的JSON格式。这是给D3sankey模块传递数据https://github.com/d3/d3-plugins/blob/master/sankey/sankey.js我不知道如何将节点的索引添加到链接中,而不是名称。真的,我完全迷失了它!我在这里做了一个fiddle:https://jsfiddle.net/adamdavi3s/kw3jtzx4/下面是所需数据和输出的示例vardata=[{"source":"Agricultural'waste'","
文章目录1.题目描述2.解题思路方法1:方法2:1.题目描述题目链接:力扣21,合并两个有序链表将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。2.解题思路方法1:首先我们能够想到的就是遍历一遍数组,判断两个结点的大小,将数值小的结点放在前面,数值大的不断尾插在后面。是不是听着挺简单的?具体实现:我们可以创建两个空指针,head用来存放链表的头结点,tail用来遍历两条链表,将两条链表链接起来。当某个链表为空时,我们可以直接返回另一条链表当两个链表都不为空时,我们可以不断比较两条链表的大小,当head和tail为空时,我们将较小的结点同时赋给head
所以我在JS中玩弄链表并提出了以下问题:比方说,我们有一个数组和一个链表,它们都有5000个元素。我们想在索引10处插入新元素。数组方式非常简单。我们在给定索引处插入新元素,并将其余元素向前移动一个索引。所以我尝试用链表来做这件事,并以下面的方式结束它:从NicholasZakas获取链表的实现并添加附加方法addOnPosition(data,index)。最后是代码:functionLinkedList(){this._head=null;this._length=0;}LinkedList.prototype={constructor:LinkedList,add:functio
一.认识四位共阴极数码管(1)一位八段共阴极数码管 在认识四位共阴极数码管之前我先介绍一下一位八段共阴极数码管。如左图所示为以为数码管的实物图,其中它共有10个引脚,且上下各五个。小数点位于右下时为数码管正面,在四位共阴极数码管中也是如此,在连接组装时尤为重要。 右图所示为一位数码管示意图,将数码管引脚连接在Arduino上,由图所示我认为你可以对为什么是八段及共阴极有了自己一定的理解。其中,共阴极顾名思义是这些LED小灯公用一个阴极。对于如何在一位数码管上显示0-9,也就是指点亮数码管上位置不同的LED小灯。例如:显示0,点亮a,b,c,d,e,f,也就是将其对应的引脚2,3,
✨个人主页:bitme✨当前专栏:数据结构✨刷题专栏:基础算法链表OJ🏳️一.移除链表元素🏴二.反转链表🏁三.链表的中间结点🚩四.链表中倒数第k个结点🏳️🌈五.合并两个有序链表🏳️⚧️六.链表的回文结构🏴☠️七.链表分割🏴八.相交链表🏳️🌈九.环形链表🍹十.环形链表II 🏳️一.移除链表元素简介:给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val==val的节点,并返回新的头节点。示例1:输入:head=[1,2,6,3,4,5,6],val=6输出:[1,2,3,4,5]示例2:输入:head=[],val=1输出:[]示例3:输入:he
我正在学习Go并编写了以下代码来反转链表。但是,代码没有按预期工作。这是一个节点结构以及用于打印和反转列表的函数。typeNodestruct{numberintprevious*Nodenext*Node}funcPrintList(node*Node){forn:=node;n!=nil;n=n.next{fmt.Println(n)}}funcReverseList(node*Node){varnextNodeRef*Nodeforn:=node;n!=nil;n=n.previous{ifn.next==nil{n.next=n.previousn.previous=nil*n
魔王的介绍:😶🌫️一名双非本科大一小白。魔王的目标:🤯努力赶上周围卷王的脚步。魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥❤️🔥大魔王与你分享:很喜欢宫崎骏说的一句话:“不要轻易去依赖一个人,它会成为你的习惯当分别来临,你失去的不是某个人而是你精神的支柱,无论何时何地,都要学会独立行走,它会让你走得更坦然些。”文章目录一、前言二、链表实现1、创建结构体类型2、创建结点3、打印单链表4、单链表尾插5、单链表头插6、单链表尾删7、单链表头删8、单链表查找9、单链表插入☃️该位置之后插入☃️该位置之前插入(插入正常理解)10、单链表删除11、单链表销毁三、总代码SeqListNode.hSeqListNod