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

相关文章

numpy求解线性代数相关问题

《numpy求解线性代数相关问题》本文主要介绍了numpy求解线性代数相关问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 在numpy中有numpy.array类型和numpy.mat类型,前者是数组类型,后者是矩阵类型。数组

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Python结合requests和Cheerio处理网页内容的操作步骤

《Python结合requests和Cheerio处理网页内容的操作步骤》Python因其简洁明了的语法和强大的库支持,成为了编写爬虫程序的首选语言之一,requests库是Python中用于发送HT... 目录一、前言二、环境搭建三、requests库的基本使用四、Cheerio库的基本使用五、结合req

使用Python处理CSV和Excel文件的操作方法

《使用Python处理CSV和Excel文件的操作方法》在数据分析、自动化和日常开发中,CSV和Excel文件是非常常见的数据存储格式,ython提供了强大的工具来读取、编辑和保存这两种文件,满足从基... 目录1. CSV 文件概述和处理方法1.1 CSV 文件格式的基本介绍1.2 使用 python 内

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修