数据结构-线性结构:链表(Linked List)【基于自定义Node节点】【真正的动态数据结构,不需要处理固定容量问题】【最简单的动态数据结构】【单向链表、单向循环链表、双向链表、双向循环链表】

本文主要是介绍数据结构-线性结构:链表(Linked List)【基于自定义Node节点】【真正的动态数据结构,不需要处理固定容量问题】【最简单的动态数据结构】【单向链表、单向循环链表、双向链表、双向循环链表】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

顺序表(动态数组) V.S 链表

链表失去了顺序表(动态数组)随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。

操作链表顺序表(动态数组)
访问元素O(n)O(1)
在头部插入/删除O(1)O(n)
在尾部插入/删除O(n)O(1)
在中间插入/删除O(n)O(n)
修改操作O(n)

在这里插入图片描述

注意:“在中间插入/删除”操作
  • 虽然表面看起来复杂度都是 O(n),但是单向链表和顺序表(动态数组)在插入和删除时进行的是完全不同的操作。
  • 单向链表进行“插入/删除”操作:主要耗时操作是“遍历查找”所需位置的步骤,当查找到对应位置后,“删除”操作或“插入”操作本身的复杂度是O(1)。
  • 顺序表(动态数组)进行“插入/删除”操作:查找所需位置的步骤很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表(动态数组)进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。

在这里插入图片描述
在这里插入图片描述

将元素存放在通过链接构造起来的一系列存储块中 , 存储区是非连续的

在这里插入图片描述

一、单向链表结构

单向链表(单向链表)是链表的一种形式,它的每个节点包含两个域一个元素域和一个链接域 。这个链接指向链表中的下一个节点 , 而最后一个节点的链接域则指向一个空值。
在这里插入图片描述

  • 表元素域elem用来存放具体的数据
  • 链接域next用来存放下一个节点的位置
  • 变量head指向链表的头节点(首节点)的位置,从head出发能找到表中的任意节点

1、单向链表的操作

方法作用
is_empty()链表是否为空
length()链表长度
travel()遍历整个链表
add(item)链表头部添加元素
append(item)链表尾部添加元素
insert(pos, item)指定位置添加元素
remove(item)删除节点
search(item)查找节点是否存在

2、单向链表“节点”—代码实现

在这里插入图片描述

class SingleNode(object):"""单向链表的结点"""def __init__(self,item):# _item存放数据元素self.item = item# _next是下一个节点的标识self.next = None

如果 node 是一个结点:

  • 获取结点元素 : node.item
  • 获取下一个结点 : node.next

3、单向链表—代码实现

在这里插入图片描述
java版本:

MyLinkedList.java

/*** Title: 带虚拟头结点的链表* Description:* 链表结构包含两个要素: 头结点head + 链表大小size,* 操作包括:* 链表的增删* 链表是否为空* 链表的大小* 链表的打印输出* 删除链表重复节点* 链表倒数第K个元素* 链表的反转* 链表的倒序输出* 链表的中间节点* 链表是否有环* 链表节点的删除(不知道头结点的情况下)* 链表是否相交* 链表的交点*/
public class MyLinkedList<E> {// 节点private class Node {private E elem;private Node next;private Node(E elem, Node next) {this.elem = elem;this.next = next;}private Node(E elem) {this(elem, null);}private Node() {this(null, null);}@Overridepublic String toString() {return elem.toString();}}private Node dummyHead; // 链表虚拟头节点private int size; // 链表大小// 初始化链表public MyLinkedList() {dummyHead = new Node();size = 0;}// 获取链表的头结点public Node getHead() {return dummyHead;}// 获取链表中的元素个数public int getSize() {return size;}// 链表是否为空public boolean isEmpty() {return size == 0;}// 在链表的index(0-based)位置添加新的元素elem【此操作在链表中不是一个常用的操作,仅仅练习使用】public void add(int index, E elem) {if (index < 0 || index > size) {throw new IllegalArgumentException("超出范围...");}Node prev = dummyHead;for (int i = 0; i < index; i++) {   // 查找插入处(index处)的前一个节点prev = prev.next;   // 最后一个 prev.next 表示 index节点}
/*            Node node = new Node(elem); // 将新元素链入链表node.next = prev.next;prev.next = node;*/prev.next = new Node(elem, prev.next);  // 新建一个Node节点,让该节点的next指针指向head,然后让head指向该节点【完成插入头结点的操作,和上面三行代码结果相同】size++;}// 重载添加方法,如果不写添加元素的下标,则默认添加头节点public void add(E elem) {addFirst(elem);}// 链表头添加新的元素elempublic void addFirst(E elem) {add(0, elem);}// 在链表末尾添加新的元素elempublic void addLast(E elem) throws Exception {add(size, elem);}// 获得链表的第index(0-based)个位置的元素【此操作在链表中不是一个常用操作,仅仅练习使用】public E get(int index) {if (index < 0 || index > size) {throw new IllegalArgumentException("超出范围...");}Node cur = dummyHead.next;for (int i = 0; i < index; i++) {cur = cur.next;}return cur.elem;}// 获得链表的第一个元素public E getFirst() {return get(0);}// 获得链表的最后一个元素public E getLast() {return get(size - 1);}// 修改链表的第index(0-based)个位置的元素为elem【此操作在链表中不是一个常用操作,仅仅练习使用】public void set(int index, E elem) {if (index < 0 || index > size) {throw new IllegalArgumentException("超出范围...");}Node cur = dummyHead.next;for (int i = 0; i < index; i++) {cur = cur.next;}cur.elem = elem;}// 查找链表中是否有元素elempublic boolean contains(E elem) {Node cur = dummyHead.next;while (cur != null) {if (cur.elem.equals(elem)) {return true;}cur = cur.next;}return false;}// 从链表汇总删除第index(0-based)个位置的元素【此操作在链表中不是一个常用操作,仅仅练习使用】public E remove(int index) {if (index < 0 || index > size) {throw new IllegalArgumentException("超出范围...");}Node prev = dummyHead;for (int i = 0; i < index; i++) {prev = prev.next;}Node retNode = prev.next;   // retNode代表待删除节点prev.next = retNode.next;   // 将待删除节点的前一个节点的next指向待删除节点的下一个节点retNode.next = null;   // 将待删除节点的next指向nullsize--;return retNode.elem;}// 删除链表中该元素的节点public void remove(E elem) {if (elem == null) {return;}Node prev = dummyHead;Node node = dummyHead.next;while (node != null) {if (elem.equals(node.elem)) {prev.next = node.next;node.next = null;size--;break;}prev = prev.next;node = node.next;}}// 从链表中删除第一个元素,返回删除的元素public E removeFirst() {return remove(0);}// 从链表中删除最后一个元素,返回删除的元素public E removeLast() {return remove(size - 1);}@Overridepublic String toString() {StringBuilder string = new StringBuilder();
//        for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
//            string.append(cur + "->");
//        }Node cur = dummyHead.next;while (cur != null) {string.append(cur + "->");cur = cur.next;}string.append("NULL");return string.toString();}}

Main.java

public class Main {public static void main(String[] args) throws Exception {MyLinkedList<Integer> linkedList = new MyLinkedList<>();for(int i = 0; i <5; i++){linkedList.addFirst(i);System.out.println(linkedList);}System.out.println("===========================================================================");linkedList.add(2, 666);System.out.println(linkedList);System.out.println("===========================================================================");linkedList.remove(2);System.out.println(linkedList);System.out.println("===========================================================================");linkedList.removeFirst();System.out.println(linkedList);System.out.println("===========================================================================");linkedList.removeLast();System.out.println(linkedList);System.out.println("===========================================================================");}
}

输出结果:

0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
===========================================================================
4->3->666->2->1->0->NULL
===========================================================================
4->3->2->1->0->NULL
===========================================================================
3->2->1->0->NULL
===========================================================================
3->2->1->NULL
===========================================================================Process finished with exit code 0

python版本:

通过append(item) 方法不断添加元素 , 最终实现单向链表

class SingleNode(object):"""单向链表的结点"""def __init__(self, item):# _item存放数据元素self.item = item# _next是下一个节点的标识self.next = Noneclass SingleLinkList(object):"""单向链表的实现"""def __init__(self):self._head = Nonedef is_empty(self):"""判断链表是否为空"""return self._head == Nonedef length(self):"""链表长度"""# cur初始时指向头节点cur = self._headcount = 0# 尾节点指向None,当未到达尾部时while cur != None:count += 1# 将cur后移一个节点cur = cur.nextreturn countdef travel(self):"""遍历链表"""cur = self._headwhile cur != None:print(cur.item)cur = cur.nextprint("")def add(self, item):"""头部添加元素"""# 先创建一个保存item值的节点node = SingleNode(item)# 将新节点的链接域next指向头节点,即_head指向的位置node.next = self._head# 将链表的头_head指向新节点self._head = nodedef append(self, item):"""尾部添加元素"""node = SingleNode(item)# 先判断链表是否为空,若是空链表,则将_head指向新节点if self.is_empty():self._head = node# 若不为空,则找到尾部,将尾节点的next指向新节点else:cur = self._headwhile cur.next != None:cur = cur.nextcur.next = nodedef insert(self, pos, item):"""指定位置添加元素"""# 若指定位置pos为第一个元素之前,则执行头部插入if pos <= 0:self.add(item)# 若指定位置超过链表尾部,则执行尾部插入elif pos > (self.length() - 1):self.append(item)# 找到指定位置else:node = SingleNode(item)count = 0# pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置pre = self._headwhile count < (pos - 1):count += 1pre = pre.next# 先将新节点node的next指向插入位置的节点node.next = pre.next# 将插入位置的前一个节点的next指向新节点pre.next = nodedef remove(self, item):"""删除节点"""cur = self._headpre = Nonewhile cur != None:# 找到了指定元素if cur.item == item:# 如果第一个就是删除的节点if not pre:# 将头指针指向头节点的后一个节点self._head = cur.nextelse:# 将删除位置前一个节点的next指向删除位置的后一个节点pre.next = cur.nextbreakelse:# 继续按链表后移节点pre = curcur = cur.nextdef search(self, item):"""链表查找节点是否存在,并返回True或者False"""cur = self._headwhile cur != None:if cur.item == item:return Truecur = cur.nextreturn Falseif __name__ == "__main__":ll = SingleLinkList()ll.add(1)ll.add(2)ll.append(3)ll.insert(2, 4)print("length:", ll.length())ll.travel()print(ll.search(3))print(ll.search(5))ll.remove(1)print("length:", ll.length())ll.travel()

4、单向链表的操作

  • is_empty() 判断单向链表是否为空
    在这里插入图片描述
  • length() 单向链表长度
    在这里插入图片描述
  • travel() 遍历整个单向链表
    在这里插入图片描述
  • add(item) 单向链表头部增加节点
    在这里插入图片描述
  • append(item) 单向链表尾部增加节点
    在这里插入图片描述
  • 空单向链表添加节点
    在这里插入图片描述
  • remove(item) 删除单向链表上的某一节点
    在这里插入图片描述
  • search(item) 查找单向链表上是否存在某节点
    在这里插入图片描述

二、单向循环链表结构

单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
在这里插入图片描述

1、单向循环链表的操作

方法作用
is_empty()判断链表是否为空
length()返回链表的长度
travel()遍历
add(item)在头部添加一个节点
append(item)在尾部添加一个节点
insert(pos, item)在指定位置pos添加节点
remove(item)删除一个节点
search(item)查找节点是否存在

2、单向循环链表“节点”—代码实现

class Node(object):"""节点"""def __init__(self, item):self.item = itemself.next = None

3、单向循环链表—代码实现

class Node(object):"""节点"""def __init__(self, item):self.item = itemself.next = Noneclass SinCycLinkedlist(object):"""单向循环链表"""def __init__(self, node=None):self.__head = nodeif node:node.next = nodedef is_empty(self):"""判断链表是否为空"""return self._head == Nonedef length(self):"""返回链表的长度"""# 如果链表为空,返回长度0if self.is_empty():return 0# count记录数量count = 1# cur游标,用来移动遍历节点cur = self._headwhile cur.next != self._head:count += 1cur = cur.nextreturn countdef travel(self):"""遍历整个链表"""if self.is_empty():returncur = self._headprint(cur.item)while cur.next != self._head:cur = cur.nextprint(cur.item)# 退出循环,cur指向尾节点,但尾节点的元素未打印print("")def add(self, item):"""链表头部添加元素,头插法"""node = Node(item)if self.is_empty():self._head = nodenode.next = self._headelse:# 添加的节点指向_headnode.next = self._head# 移到链表尾部,将尾部节点的next指向nodecur = self._headwhile cur.next != self._head:cur = cur.nextcur.next = node# _head指向添加node的self._head = nodedef append(self, item):"""链表尾部添加元素, 尾插法"""node = Node(item)if self.is_empty():self._head = nodenode.next = self._headelse:# 移到链表尾部cur = self._headwhile cur.next != self._head:cur = cur.next# 将尾节点指向nodecur.next = node# 将node指向头节点_headnode.next = self._headdef insert(self, pos, item):"""指定位置添加元素:param  pos 从0开始"""if pos <= 0:self.add(item)elif pos > (self.length() - 1):self.append(item)else:node = Node(item)cur = self._headcount = 0# 移动到指定位置的前一个位置while count < (pos - 1):count += 1cur = cur.nextnode.next = cur.nextcur.next = nodedef remove(self, item):"""删除一个节点"""# 若链表为空,则直接返回if self.is_empty():return# 将cur指向头节点cur = self._headpre = None# 若头节点的元素就是要查找的元素itemif cur.item == item:# 如果链表不止一个节点if cur.next != self._head:# 先找到尾节点,将尾节点的next指向第二个节点while cur.next != self._head:cur = cur.next# cur指向了尾节点cur.next = self._head.nextself._head = self._head.nextelse:# 链表只有一个节点self._head = Noneelse:pre = self._head# 第一个节点不是要删除的while cur.next != self._head:# 找到了要删除的元素if cur.item == item:# 删除pre.next = cur.nextreturnelse:pre = curcur = cur.next# cur 指向尾节点if cur.item == item:# 尾部删除pre.next = cur.nextdef search(self, item):"""查找节点是否存在"""if self.is_empty():return Falsecur = self._headif cur.item == item:return Truewhile cur.next != self._head:cur = cur.nextif cur.item == item:return Truereturn Falseif __name__ == "__main__":ll = SinCycLinkedlist()ll.add(1)ll.add(2)ll.append(3)ll.insert(2, 4)ll.insert(4, 5)ll.insert(0, 6)print("length:", ll.length())ll.travel()print(ll.search(3))print(ll.search(7))ll.remove(1)print("length:", ll.length())ll.travel()

三、双向链表结构

一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。

在这里插入图片描述

1、双向链表的操作

方法作用
is_empty()链表是否为空
length()链表长度
travel()遍历链表
add(item)链表头部添加
append(item)链表尾部添加
insert(pos, item)指定位置添加
remove(item)删除节点
search(item)查找节点是否存在

2、双向链表“节点”—代码实现

class Node(object):"""双向链表节点"""def __init__(self, item):self.item = itemself.next = Noneself.prev = None

3、双向链表—代码实现

class Node(object):"""双向链表节点"""def __init__(self, item):self.item = itemself.next = Noneself.prev = Noneclass DLinkList(object):"""双向链表"""def __init__(self):self._head = Nonedef is_empty(self):"""判断链表是否为空"""return self._head is Nonedef length(self):"""返回链表的长度"""cur = self._headcount = 0while cur is not None:count += 1cur = cur.nextreturn countdef travel(self):"""遍历链表"""cur = self._headwhile cur is not None:print(cur.item)cur = cur.nextprint("")def add(self, item):"""头部插入元素"""node = Node(item)if self.is_empty():# 如果是空链表,将_head指向nodeself._head = nodeelse:# 将node的next指向_head的头节点node.next = self._head# 将_head的头节点的prev指向nodeself._head.prev = node# 将_head 指向nodeself._head = nodedef append(self, item):"""尾部插入元素"""node = Node(item)if self.is_empty():# 如果是空链表,将_head指向nodeself._head = nodeelse:# 移动到链表尾部cur = self._headwhile cur.next is not None:cur = cur.next# 将尾节点cur的next指向nodecur.next = node# 将node的prev指向curnode.prev = curdef search(self, item):"""查找元素是否存在"""cur = self._headwhile cur is not None:if cur.item == item:return Truecur = cur.nextreturn Falsedef insert(self, pos, item):"""在指定位置添加节点"""if pos <= 0:self.add(item)elif pos > (self.length() - 1):self.append(item)else:node = Node(item)cur = self._headcount = 0# 移动到指定位置的前一个位置while count < (pos - 1):count += 1cur = cur.next# 将node的prev指向curnode.prev = cur# 将node的next指向cur的下一个节点node.next = cur.next# 将cur的下一个节点的prev指向nodecur.next.prev = node# 将cur的next指向nodecur.next = nodedef remove(self, item):"""删除元素"""if self.is_empty():returnelse:cur = self._headif cur.item == item:# 如果首节点的元素即是要删除的元素if cur.next is None:# 如果链表只有这一个节点self._head = Noneelse:# 将第二个节点的prev设置为Nonecur.next.prev = None# 将_head指向第二个节点self._head = cur.nextreturnwhile cur is not None:if cur.item == item:# 将cur的前一个节点的next指向cur的后一个节点cur.prev.next = cur.next# 将cur的后一个节点的prev指向cur的前一个节点cur.next.prev = cur.prevbreakcur = cur.nextif __name__ == "__main__":ll = DLinkList()ll.add(1)ll.add(2)ll.append(3)ll.insert(2, 4)ll.insert(4, 5)ll.insert(0, 6)print("length:", ll.length())ll.travel()print(ll.search(3))print(ll.search(4))ll.remove(1)print("length:", ll.length())ll.travel()

四、链表结构的扩展

  • 单向链表---->单向循环链表
  • 双向链表---->双向循环链表
  • 给链表添加表头“信息区”:描述链表的相关信息
  • 在链表“信息区”添加头结点位置、尾节点位置等信息
  • 扩展其他功能
    在这里插入图片描述



参考资料:
超硬核十万字!全网最全 数据结构 代码,随便秒杀老师/面试官,我说的

这篇关于数据结构-线性结构:链表(Linked List)【基于自定义Node节点】【真正的动态数据结构,不需要处理固定容量问题】【最简单的动态数据结构】【单向链表、单向循环链表、双向链表、双向循环链表】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128944

相关文章

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

Ubuntu固定虚拟机ip地址的方法教程

《Ubuntu固定虚拟机ip地址的方法教程》本文详细介绍了如何在Ubuntu虚拟机中固定IP地址,包括检查和编辑`/etc/apt/sources.list`文件、更新网络配置文件以及使用Networ... 1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工