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

相关文章

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表