本文主要是介绍数据结构-线性结构:链表(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节点】【真正的动态数据结构,不需要处理固定容量问题】【最简单的动态数据结构】【单向链表、单向循环链表、双向链表、双向循环链表】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!