链表及其算法
1. 链表
2. 单向链表
3. 双向链表
4. 单向循环链表
1. 链表
为什么需要链表
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
链表的定义
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
链表与顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
链表与顺序表的各种操作复杂度如下所示:
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(1) | O(1) |
在中间插入/删除 | O(1) | O(n) |
注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作:
- 链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是 O(1)。
- 顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
2. 单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
- 元素域 elem 用来存放具体的数据。
- 链接域 next 用来存放下一个节点的位置(python 中的 id 标识)。
- 变量 p 指向链表的头节点(首节点)的位置,从 p 出发能找到表中的任意节点。
代码实现
自定义单链表的操作
- is_empty():链表是否为空。
- length():链表长度。
- travel():遍历整个链表。
- add(item):链表头部添加元素。
- append(item):链表尾部添加元素。
- insert(pos, item):指定位置添加元素。
- remove(item):删除元素。
- search(item):元素是否存在。
- reverse():反转链表。
头部增加节点:
指定位置添加节点:
删除节点:
实现:
1 class Node: 2 """单链表的节点""" 3 def __init__(self, item): 4 # item 存放数据元素 5 self.item = item 6 # next是下一个节点的标识 7 # 直接指向下一个节点对象即可 8 self.next = None 9 10 11 class SingleLinkList: 12 """单链表""" 13 def __init__(self, node=None): 14 # head只是一个变量,指向头节点 15 self.head = node 16 17 def is_empty(self): 18 """判断链表是否为空""" 19 return self.head == None 20 21 def length(self): 22 """链表长度""" 23 # cur代表当前所在节点,先初始化指向头节点 24 cur = self.head 25 count = 0 26 #尾节点指向None,当未到达尾节点时 27 while cur != None: 28 count += 1 29 # 将cur后移一个节点 30 cur = cur.next 31 return count 32 33 def travel(self): 34 """遍历链表""" 35 # cur初始化指向头节点 36 cur = self.head 37 while cur != None: 38 print(cur.item, end=" ") 39 cur = cur.next 40 print() 41 42 def append(self, item): 43 """尾部添加节点,即尾插法""" 44 node = Node(item) 45 # 先判断链表是否为空,若是空链表,则将head指向新节点 46 if self.is_empty(): 47 self.head = node 48 # 若不为空,则找到尾部,将尾部节点的next指向新节点 49 else: 50 cur = self.head 51 while cur.next != None: 52 cur = cur.next 53 cur.next = node 54 55 def add(self, item): 56 """头部添加节点,即头插法""" 57 # 先创建一个保存item值的节点 58 node = Node(item) 59 # 该节点先指向头节点 60 node.next = self.head 61 # 头节点再指向该节点 62 self.head = node 63 64 def insert(self, pos, item): 65 """指定位置添加节点""" 66 # 若指定位置为第一个元素之前,则执行头插法 67 if pos <= 0: 68 self.add(item) 69 # 若指定位置超过链表尾部,则执行尾插法 70 elif pos > (self.length()-1): 71 self.append(item) 72 # 找到指定位置 73 else: 74 node = Node(item) 75 # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置 76 pre = self.head 77 for i in range(pos-1): 78 pre = pre.next 79 # 先将新节点的next指向插入指定位置本身的节点 80 node.next = pre.next 81 # 将插入指定位置的前一个节点的next指向新节点 82 pre.next = node 83 84 def search(self, item): 85 """查看节点是否存在,返回布尔值""" 86 cur = self.head 87 while cur != None: 88 if cur.item == item: 89 return True 90 cur = cur.next 91 return False 92 93 def remove(self, item): 94 """删除节点""" 95 cur = self.head 96 pre = None 97 while cur != None: 98 # 找到了指定元素 99 if cur.item == item: 100 # 如果头节点就是被删除的节点 101 if not pre: 102 # 将头指针指向头结点的后一个节点 103 self.head = cur.next 104 else: 105 # 将删除位置前一个节点的next指向删除位置的后一个节点 106 pre.next = cur.next 107 break 108 else: 109 # 继续按链表后移节点 110 pre = cur 111 cur = cur.next 112 113 def reverse(self): 114 if self.head is None: 115 print("链表为空,无需反转") 116 return 117 pre = None 118 while self.head != None: 119 tmp = pre 120 pre = self.head 121 self.head = self.head.next 122 pre.next = tmp 123 self.head = pre 124 print("反转结果:", end="") 125 self.travel() 126 127 128 if __name__ == "__main__": 129 ll = SingleLinkList() 130 print(ll.is_empty()) # True 131 ll.append(1) 132 ll.append(2) 133 ll.append(4) 134 print(ll.is_empty()) # False 135 ll.travel() # 1 2 4 136 ll.add(5) 137 ll.travel() # 5 1 2 4 138 139 ll.insert(-1, -1) 140 ll.travel() # -1 5 1 2 4 141 ll.insert(9, 10) 142 ll.travel() # -1 5 1 2 4 10 143 ll.insert(2, 11) 144 ll.travel() # -1 5 11 1 2 4 10 145 146 print(ll.search(11)) # True 147 print(ll.search(100)) # False 148 ll.remove(-1) 149 ll.remove(1) 150 ll.travel() # 5 11 2 4 10 151 ll.reverse() # 反转结果:10 4 2 11 5
3. 双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:
- 一个指向前一个节点,当此节点为第一个节点时,指向空值;
- 而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
指定位置插入节点:
删除元素:
实现:
1 from test import SingleLinkList 2 3 4 class Node: 5 "双向链表节点" 6 def __init__(self, item): 7 self.item = item 8 self.prev = None 9 self.next = None 10 11 12 class DoubleLinkList(SingleLinkList): 13 "双向链表" 14 15 """以下方法继承自SingleLinkList""" 16 # def __init__(self, node=None): 17 # # head只是一个变量,指向头节点 18 # self.head = node 19 20 # def is_empty(self): 21 # "判断链表是否为空" 22 # return self.head == None 23 24 # def length(self): 25 # "链表长度" 26 # # cur代表当前所在节点,先初始化指向头节点 27 # cur = self.head 28 # count = 0 29 # #尾节点指向None,当未到达尾节点时 30 # while cur != None: 31 # count += 1 32 # # 将cur后移一个节点 33 # cur = cur.next 34 # return count 35 36 # def travel(self): 37 # "遍历链表" 38 # # cur初始化指向头节点 39 # cur = self.head 40 # while cur != None: 41 # print(cur.item, end=" ") 42 # cur = cur.next 43 # print() 44 45 # def search(self, item): 46 # "查看节点是否存在,返回布尔值" 47 # cur = self.head 48 # while cur != None: 49 # if cur.item == item: 50 # return True 51 # cur = cur.next 52 # return False 53 54 def append(self, item): 55 "尾部添加节点,即尾插法" 56 node = Node(item) 57 # 先判断链表是否为空,若是空链表,则将head指向新节点 58 if self.is_empty(): 59 self.head = node 60 # 若不为空,则找到尾部,将尾部节点的next指向新节点 61 else: 62 cur = self.head 63 while cur.next != None: 64 cur = cur.next 65 cur.next = node 66 node.prev = cur 67 68 def add(self, item): 69 "头部添加节点,即头插法" 70 # 先创建一个保存item值的节点 71 node = Node(item) 72 # 如果是空链表,将head指向node 73 if self.is_empty(): 74 self.head = node 75 else: 76 # 将node的next指向头节点 77 node.next = self.head 78 # 将头节点的prev指向node 79 self.head.prev = node 80 # 将头节点指向node 81 self.head = node 82 83 def insert(self, pos, item): 84 "指定位置添加节点" 85 # 若指定位置为第一个元素之前,则执行头插法 86 if pos <= 0: 87 self.add(item) 88 # 若指定位置超过链表尾部,则执行尾插法 89 elif pos > (self.length()-1): 90 self.append(item) 91 # 找到指定位置 92 else: 93 node = Node(item) 94 cur = self.head 95 # 移动到指定位置的前一个位置 96 for i in range(pos-1): 97 cur = cur.next 98 # 将node的prev指向cur 99 node.prev = cur 100 # 将node的next指向指定位置 101 node.next = cur.next 102 # 将指定位置的prev指向node 103 cur.next.prev = node 104 # 将cur的next指向node 105 cur.next = node 106 107 def remove(self, item): 108 "删除节点" 109 if self.is_empty(): 110 return 111 else: 112 cur = self.head 113 # 如果头节点就是被删除的节点 114 if cur.item == item: 115 # 如果链表只有这一个节点 116 if cur.next == None: 117 self.head = None 118 else: 119 # 将第二个节点的prev指向None 120 cur.next.prev = None 121 # 将head指向第二个节点 122 self.head = cur.next 123 return 124 while cur != None: 125 # 找到了指定元素 126 if cur.item == item: 127 # 将cur的前一个节点的next指向cur的下一个节点 128 cur.prev.next = cur.next 129 # 将cur的下一个节点的next指向cur的前一个节点 130 cur.next.prev = cur.prev 131 break 132 else: 133 # 继续按链表后移节点 134 cur = cur.next 135 136 137 # 测试 138 if __name__ == "__main__": 139 dl = DoubleLinkList() 140 dl.add(1) 141 dl.add(2) 142 dl.append(3) 143 dl.insert(2, 4) 144 dl.insert(4, 5) 145 dl.insert(0, 6) 146 print("length: ", dl.length()) 147 dl.travel() 148 print(dl.search(3)) 149 print(dl.search(13)) 150 dl.remove(6) 151 dl.travel()
4. 单向循环链表
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
操作:
- is_empty():判断链表是否为空
- length():返回链表的长度
- travel():遍历
- add(item):在头部添加一个节点
- append(item):在尾部添加一个节点
- insert(pos, item):在指定位置pos添加节点
- remove(item):删除一个节点
- search(item):查找节点是否存在
实现:
1 class Node: 2 """单链表的节点""" 3 def __init__(self, item): 4 # item 存放数据元素 5 self.item = item 6 # next是下一个节点的标识 7 # 直接指向下一个节点对象即可 8 self.next = None 9 10 11 class SingleCycLinkList: 12 """单向循环链表""" 13 def __init__(self, node=None): 14 # head只是一个变量,指向头节点 15 self.head = node 16 17 def is_empty(self): 18 """判断链表是否为空""" 19 return self.head == None 20 21 def length(self): 22 """链表长度""" 23 if self.is_empty(): 24 return 0 25 # cur代表当前所在节点,先初始化指向头节点 26 cur = self.head 27 count = 1 28 #尾节点指向头节点,当未到达尾节点时 29 while cur.next != self.head: 30 count += 1 31 # 将cur后移一个节点 32 cur = cur.next 33 return count 34 35 def travel(self): 36 """遍历链表""" 37 if self.is_empty(): 38 return 39 # cur初始化指向头节点 40 cur = self.head 41 while cur.next != self.head: 42 print(cur.item, end=" ") 43 cur = cur.next 44 print(cur.item, end="\n") 45 46 def append(self, item): 47 """尾部添加节点,即尾插法""" 48 node = Node(item) 49 # 先判断链表是否为空,若是空链表,则将head指向新节点 50 if self.is_empty(): 51 self.head = node 52 node.next = node 53 # 若不为空,则找到尾部,将尾部节点的next指向新节点 54 else: 55 cur = self.head 56 print(1) 57 while cur.next != self.head: 58 cur = cur.next 59 # 将尾节点指向node 60 cur.next = node 61 # 将node指向头节点 62 node.next = self.head 63 64 def add(self, item): 65 """头部添加节点,即头插法""" 66 # 先创建一个保存item值的节点 67 node = Node(item) 68 if self.is_empty(): 69 self.head = node 70 node.next = node 71 else: 72 # 该节点先指向头节点 73 node.next = self.head 74 # 移动到链表尾部,将尾部节点的next指向node 75 cur = self.head 76 while cur.next != self.head: 77 cur = cur.next 78 cur.next = node 79 # 头节点再指向该节点 80 self.head = node 81 82 83 def insert(self, pos, item): 84 """指定位置添加节点""" 85 # 若指定位置为第一个元素之前,则执行头插法 86 if pos <= 0: 87 self.add(item) 88 # 若指定位置超过链表尾部,则执行尾插法 89 elif pos > (self.length()-1): 90 self.append(item) 91 # 找到指定位置 92 else: 93 node = Node(item) 94 # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置 95 pre = self.head 96 for i in range(pos-1): 97 pre = pre.next 98 # 先将新节点的next指向插入指定位置本身的节点 99 node.next = pre.next 100 # 将插入指定位置的前一个节点的next指向新节点 101 pre.next = node 102 103 def search(self, item): 104 """查看节点是否存在,返回布尔值""" 105 if self.is_empty(): 106 return False 107 cur = self.head 108 while cur.next != self.head: 109 if cur.item == item: 110 return True 111 cur = cur.next 112 # cur指向尾节点 113 if cur.item == item: 114 return True 115 return False 116 117 def remove(self, item): 118 """删除节点""" 119 # 若链表为空,则直接返回 120 if self.is_empty(): 121 return 122 cur = self.head 123 pre = None 124 # 若头节点就是要被删除的节点 125 if cur.item == item: 126 # 如果链表不止一个节点 127 if cur.next != self.head: 128 # 先找到尾部节点,将尾部节点的next指向第二个节点 129 while cur.next != self.head: 130 cur = cur.next 131 cur.next = self.head.next 132 self.head = self.head.next 133 # 如果链表只有一个节点 134 else: 135 self.head = None 136 # 若头节点不是要被删除的节点 137 else: 138 pre = self.head 139 while cur.next != self.head: 140 # 找到了要删除的节点 141 if cur.item == item: 142 # 删除 143 pre.next = cur.next 144 return 145 else: 146 pre = cur 147 cur = cur.next 148 # cur 指向尾节点 149 if cur.item == item: 150 # 尾部删除 151 pre.next = cur.next 152 153 154 if __name__ == "__main__": 155 ll = SingleCycLinkList() 156 ll.add(1) 157 ll.add(2) 158 ll.append(3) 159 ll.append(7) 160 ll.insert(2, 4) 161 ll.insert(6, 5) 162 ll.insert(-1, 6) 163 print("length: ", ll.length()) # 7 164 ll.travel() # 6 2 1 4 3 7 5 165 print(ll.search(3)) # True 166 print(ll.search(13)) # False 167 ll.remove(6) 168 ll.travel() # 2 1 4 3 7 5 169