数据结构-线性结构:链表(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

相关文章

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

Python实现自动化接收与处理手机验证码

《Python实现自动化接收与处理手机验证码》在移动互联网时代,短信验证码已成为身份验证、账号注册等环节的重要安全手段,本文将介绍如何利用Python实现验证码的自动接收,识别与转发,需要的可以参考下... 目录引言一、准备工作1.1 硬件与软件需求1.2 环境配置二、核心功能实现2.1 短信监听与获取2.

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详