转载地址:线性表的一些基本知识点
1、线性表的一些基本知识点
线性表是一种典型的线性结构。头结点无前驱有一个后继,尾节点无后继有一个前驱。链表只能顺序查找,定位一个元素的时间为O(N),删除一个元素的时间为O(1)。
1.1、线性表的顺序存储结构
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。是一种随机存取的存储结构。顺序存储指内存地址是一块的,随机存取指访问时可以按下标随机访问,存储和存取是不一样的。如果是存储,则是指按顺序的,如果是存取,则是可以随机的,可以利用元素下标进行。数组比线
性表速度更快的是:原地逆序、返回中间节点、选择随机节点。
- 便于线性表的构造和任意元素的访问
- 插入:插入新结点,之后结点后移。平均时间复杂度:O(n)
- 删除:删除节点,之后结点前移。平均时间复杂度:O(n)
1.2、线性链表
用一组任意的存储单元来依次存放线性表的结点,这组存储单元即可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址。data域是数据域,用来存放结点的值。next是指针域(亦称链域),用来存放结点的直接后继的地址(或位置)。不需要事先估计存储空间大小。
1.2.1、单链表
每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。同时,由于最后一个结点无后继,故结点的指针域为空,即NULL。头插法建表(逆序)、尾插法建表(顺序)。增加头结点的目的是算法实现上的方便,但增大了内存开销。
- 查找:只能从链表的头指针出发,顺链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
- 插入:先找到表的第i-1的存储位置,然后插入。新结点先连后继,再连前驱。
- 删除:首先找到ai-1的存储位置p。然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间.r=p->next;p->next=r->next;delete r。
- 判断一个单向链表中是否存在环的最佳方法是快慢指针。
1.2.2、静态链表
用一维数组来实现线性链表,这种用一维数组表示的线性链表,称为静态链表。静态:体现在表的容量是一定的。(数组的大小);链表:插入与删除同前面所述的动态链表方法相同。静态链表中指针表示的是下一元素在数组中的位置。
静态链表是用数组实现的,是顺序的存储结构,在物理地址上是连续的,而且需要预先分配大小。动态链表是用申请内存函数(C是malloc,C++是new)动态申请内存的,所以在链表的长度上没有限制。动态链表因为是动态申请内存的,所以每个节点的物理地址不连续,要通过指针来顺序访问。静态链表在插入、删除时也是通过修改指针域来实现的,与动态链表没有什么分别
1.2.3、循环链表
是一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
在单链表中,将终端结点的指针域NULL改为指向表头结点的或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p—>next是否为空,而是判断它们是否等于某一指定指针,如头指针或尾指针等。
1.2.4、双向链表
在单链表的每个结点里再增加一个指向其直接前趋的指针域prior。这样就形成的链表中有两个方向不同的链。双链表一般由头指针唯一确定的,将头结点和尾结点链接起来构成循环链表,并称之为双向链表。设指针p指向某一结点,则双向链表结构的对称性可用下式描述:p—>prior—>next=p=p—>next—>prior。从两个方向搜索双链表,比从一个方向搜索双链表的方差要小。
- 插入:先搞定插入节点的前驱和后继,再搞定后结点的前驱,最后搞定前结点的后继。
- 在有序双向链表中定位删除一个元素的平均时间复杂度为O(n)
- 可以直接删除当前指针所指向的节点。而不需要像单向链表中,删除一个元素必须找到其前驱。因此在插入数据时,单向链表和双向链表操作复杂度相同,而删除数据时,双向链表的性能优于单向链表
2、顺序表
2.1、顺序表的基本形式
顺序表的基本形式
图a表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc (e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即:
故,访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)。
如果元素的大小不统一,则须采用图b的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。由于每个链接所需的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。注意,图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量通常很小。
图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构。
2.2、顺序表的结构与实现
2.2.1、顺序表的两种基本实现方式
顺序表的两种基本实现方式
一个顺序表的完整信息包括两部分,一部分是表中的元素集合,另一部分是为实现正确操作而需记录的信息,即有关表的整体情况的信息,这部分信息主要包括元素存储区的容量和当前表中已有的元素个数两项。
图a为一体式结构,存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象。一体式结构整体性强,易于管理。但是由于数据元素存储区域是表对象的一部分,顺序表创建后,元素存储区就固定了。
图b为分离式结构,表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
2.2.2、元素存储区替换
一体式结构由于顺序表信息区与数据区连续存储在一起,所以若想更换数据区,则只能整体搬迁,即整个顺序表对象(指存储顺序表的结构信息的区域)改变了。
分离式结构若想更换数据区,只需将表信息区中的数据区链接地址更新即可,而该顺序表对象不变。
2.2.3、元素存储区扩充
采用分离式结构的顺序表,若将数据区更换为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行了扩充,所有使用这个表的地方都不必修改。只要程序的运行环境(计算机系统)还有空闲存储,这种表结构就不会因为满了而导致操作无法进行。人们把采用这种技术实现的顺序表称为动态顺序表,因为其容量可以在使用中动态变化。
扩充的两种策略:
每次扩充增加固定数目的存储位置,如每次扩充增加10个元素位置,这种策略可称为线性增长。
特点:节省空间,但是扩充操作频繁,操作次数多。每次扩充容量加倍,如每次扩充增加一倍存储空间。
特点:减少了扩充操作的执行次数,但可能会浪费空间资源。以空间换时间,推荐的方式。
2.3、顺序表的操作
2.3.1、增加元素
如图所示,为顺序表增加新元素111的三种方式
a. 尾端加入元素,时间复杂度为O(1);b. 非保序的加入元素(不常见),时间复杂度为O(1);c. 保序的元素加入,时间复杂度为O(n)
2.3.2、删除元素
2.4、Python中的顺序表
Python中的listtuple和tuple两种类型采用了顺序表的实现技术,具有前面讨论的顺序表的所有性质。tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似。
Python中list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序)。《具体的操作可以见参考文献2:Python3 列表》list就是一种采用分离式技术实现的动态顺序表。这就是为什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
3、链表
3.1、链表的定义
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
3.2、单向链表
单向链表也叫单链表(Linked list),是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
单向链表
- 元素域data(图中的elem)用来存放具体的数据。
- 链接域next(图中的next)用来存放下一个节点的位置(python中的标识)
- 变量head(图中的p)指向链表的头节点(首节点)的位置,从head出发能找到表中的任意节点。该链表是没有表头的链表,变量head指向链表的头节点
- 注:该代码不带头结点
3.2.1、节点实现
class Node():
"""单链表的节点"""
def __init__(self,data):
# self._data存放数据域
self._data = data
# self._next存放下一个节点的地址
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
3.2.2、单链表的操作
- is_empty(self) 链表是否为空
- length(self) 链表长度
- travel(self) 遍历整个链表
- add(self,data) 链表头部添加节点
- append(self,data) 链表尾部添加节点
- insert(self,pos, data) 指定位置添加节点
- remove(self,data) 删除节点
- search(self,data) 查找节点是否存在
1、判断是否为空
def is_empty(self):
"""判断链表是否为空:判断头结点的指针域是否为空"""
return self._head == None
2、计算链表长度
def length(self):
"""链表长度:遍历链表,不为空长度就加一"""
# curr_node 初始时指向头结点
curr_node = self._head
count = 0
# 尾节点指向None,当未到达尾部时
while curr_node != None:
count += 1
# 将当前节点curr_node后移一个节点
curr_node = curr_node.get_next()
return count
单向链表计算链表长度步骤
3、遍历链表
def travel(self):
"""遍历链表"""
curr_node = self._head
while curr_node != None:
print(curr_node.get_data())
curr_node = curr_node.get_next()
4、头部添加节点
头部添加节点
def add(self,data):
"""头部添加节点"""
# step1:先创建一个保存data值的新节点
new_node = Node(data)
# step2:将新节点的指针域next指向头结点,即self._head指向的位置
new_node.set_next(self._head)
# step3:将链表的头节点self._head指向新节点
self._head = new_node
单向链表头部添加节点步骤
5、尾部添加节点
def append(self,data):
"""尾部添加节点"""
# step1:先创建一个保存data值的新节点
new_node = Node(data)
# step2:先判断链表是否为空,若是空链表,则将self._head指向新节点
if self.is_empty():
self._head = new_node
# 若不为空,则找到尾部,将尾节点的next指向新节点
else:
curr_node = self._head
while curr_node.get_next() != None:
curr_node = curr_node.get_next()
curr_node.set_next(new_node)
单向链表尾部添加节点步骤
6、指定位置添加节点
指定位置添加节点
注意是插入第pos个节点后
def insert(self,pos,data):
"""指定位置后添加节点"""
# 若指定位置pos为第一个节点之前,则执行头部插入
if pos <= 0:
self.add(data)
# 若指定位置pos超过链表尾部,则执行尾部插入
elif pos>= self.length():
self.append(data)
# 找到指定位置
else:
new_node = Node(data)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
pre = self._head
while count < (pos-1):
count += 1
pre = pre.get_next()
# 现将新节点new_node的next指向插入位置的节点
new_node.set_next(pre.get_next())
# 将插入位置的前一个节点的next指向新节点
pre.set_next(new_node)
单向链表指定位置添加节点步骤
7、删除节点
def remove(self,data):
"""删除第一个匹配的节点"""
# 若链表为空,则直接返回
if self.is_empty():
return
curr_node = self._head
pre = None
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != None:
# 将self._head指向第二个节点
self._head = curr_node.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
while curr_node != None:
# 找到了要删除的数据
if curr_node.get_data() == data:
# 将删除位置前一个节点的next指向删除位置的后一个节点
pre.set_next(curr_node.get_next())
break
# 没有找到了要删除的数据,继续按链表向后移动节点
else:
# 继续按链表向后移动节点
pre = curr_node
curr_node = curr_node.get_next()
# 要删除的元素不存在
if curr_node == None:
print("NO %d element" % data)
删除节点
单向链表删除节点步骤
8、查找节点是否存在
def search(self,data):
"""链表查找节点是否存在,并返回Ture或者False"""
if self.is_empty():
return False
curr_node = self._head
while curr_node != None:
if curr_node.get_data() == data:
return True
curr_node = curr_node.get_next()
return False
完整代码 SingleLinkedList.py
# -*- coding:UTF-8 -*-
class Node():
"""单链表的节点"""
def __init__(self,data):
# self._data存放数据域
self._data = data
# self._next存放下一个节点的地址
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
class SingleLinkedList():
"""单链表"""
def __init__(self):
self._head = None
def is_empty(self):
"""判断链表是否为空:判断头结点的指针域是否为空"""
return self._head == None
def length(self):
"""链表长度:遍历链表,不为空长度就加一"""
# curr_node 初始时指向头结点
curr_node = self._head
count = 0
# 尾节点指向None,当未到达尾部时
while curr_node != None:
count += 1
# 将当前节点curr_node后移一个节点
curr_node = curr_node.get_next()
return count
def travel(self):
"""遍历链表"""
curr_node = self._head
while curr_node != None:
print(curr_node.get_data())
curr_node = curr_node.get_next()
def add(self,data):
"""头部添加节点"""
# step1:先创建一个保存data值的新节点
new_node = Node(data)
# step2:将新节点的指针域next指向头结点,即self._head指向的位置
new_node.set_next(self._head)
# step3:将链表的头节点self._head指向新节点
self._head = new_node
def append(self,data):
"""尾部添加节点"""
# step1:先创建一个保存data值的新节点
new_node = Node(data)
# step2:先判断链表是否为空,若是空链表,则将self._head指向新节点
if self.is_empty():
self._head = new_node
# 若不为空,则找到尾部,将尾节点的next指向新节点
else:
curr_node = self._head
while curr_node.get_next() != None:
curr_node = curr_node.get_next()
curr_node.set_next(new_node)
def insert(self,pos,data):
"""指定位置后添加节点"""
# 若指定位置pos为第一个节点之前,则执行头部插入
if pos <= 0:
self.add(data)
# 若指定位置pos超过链表尾部,则执行尾部插入
elif pos>= self.length():
self.append(data)
# 找到指定位置
else:
new_node = Node(data)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
pre = self._head
while count < (pos-1):
count += 1
pre = pre.get_next()
# 现将新节点new_node的next指向插入位置的节点
new_node.set_next(pre.get_next())
# 将插入位置的前一个节点的next指向新节点
pre.set_next(new_node)
def remove(self,data):
"""删除第一个匹配的节点"""
# 若链表为空,则直接返回
if self.is_empty():
return
curr_node = self._head
pre = None
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != None:
# 将self._head指向第二个节点
self._head = curr_node.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
while curr_node != None:
# 找到了要删除的数据
if curr_node.get_data() == data:
# 将删除位置前一个节点的next指向删除位置的后一个节点
pre.set_next(curr_node.get_next())
break
# 没有找到了要删除的数据,继续按链表向后移动节点
else:
# 继续按链表向后移动节点
pre = curr_node
curr_node = curr_node.get_next()
# 要删除的元素不存在
if curr_node == None:
print("NO %d element" % data)
def search(self,data):
"""链表查找节点是否存在,并返回Ture或者False"""
if self.is_empty():
return False
curr_node = self._head
while curr_node != None:
if curr_node.get_data() == data:
return True
curr_node = curr_node.get_next()
return False
if __name__ == "__main__":
ll = SingleLinkedList()
ll.add(1)
ll.add(2)
ll.add(3)
ll.append(4)
ll.insert(-1,5) # 插入位置小于0
ll.insert(10,5) #插入位置大于链表长度
ll.insert(2,5)
ll.remove(3) # 移除头结点
ll.remove(4) # 移除尾节点
ll.remove(5) # 移除中间节点
ll.remove(10) # 移除不存在节点
ll.travel()
print(ll.search(3))
print(ll.search(22))
print(ll.is_empty())
print(ll.length())
GitHub 代码地址 SingleLinkedList.py
3.3、单向循环链表
单向循环链表是单链表的一个变形,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
单向循环链表
3.3.1、节点实现
class Node():
"""单链表的节点"""
def __init__(self,data):
# self._data存放数据域
self._data = data
# self._next存放下一个节点的地址
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
3.3.2、单向循环链表的操作
- is_empty(self) 链表是否为空
- length(self) 链表长度
- travel(self) 遍历整个链表
- add(self,data) 链表头部添加节点
- append(self,data) 链表尾部添加节点
- insert(self,pos, data) 指定位置添加节点
- remove(self,data) 删除节点
- search(self,data) 查找节点是否存在
3.3.3、单向循环链表的实现
1、判断是否为空
def is_empty(self):
"""判断链表是否为空"""
return self._head == None
2、计算链表长度
def length(self):
"""返回链表的长度"""
# 如果链表为空,返回长度为0
if self.is_empty():
return 0
count = 1
curr_node = self._head
while curr_node.get_next() != self._head:
count += 1
curr_node = curr_node.get_next()
return count
单链表计算链表长度步骤
单向循环链表计算链表长度步骤
3、遍历链表
def travel(self):
"""遍历链表"""
if self.is_empty():
print("empty SingleCyclicLinkedList!")
curr_node = self._head
print(curr_node.get_data())
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
print(curr_node.get_data())
单向循环链表遍历链表步骤
4、头部添加节点
def add(self,data):
"""头部添加节点"""
new_node = Node(data)
if self.is_empty():
self._head = new_node
new_node.set_next(self._head)
# 添加的节点指向self._head
else:
new_node.set_next(self._head)
# 移到链表尾部,将尾节点的next指向new_nede
curr_node = self._head
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
curr_node.set_next(new_node)
# self._head指向new_node
self._head = new_node
单链表头部添加节点步骤
单向循环链表头部添加节点步骤
5、尾部添加节点
def append(self,data):
"""尾部添加节点"""
new_node = Node(data)
if self.is_empty():
self._head = new_node
new_node.set_next(self._head)
else:
# 移到链表尾部
curr_node = self._head
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
# 将尾节点指向node
curr_node.set_next(new_node)
# 将node指向头结点self._head
new_node.set_next(self._head)
单链表尾部添加节点步骤
单向循环链表尾部添加节点步骤
6、指定位置添加节点
注意是插入第pos个节点后
def insert(self,pos,data):
"""在指定位置后添加节点"""
if pos <= 0:
self.add(data)
elif pos >= self.length():
self.append(data)
else:
new_node = Node(data)
count = 0
pre = self._head
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
while count < pos-1:
count += 1
pre = pre.get_next()
# 现将新节点new_node的next指向插入位置的节点
new_node.set_next(pre.get_next())
# 将插入位置的前一个节点的next指向新节点
pre.set_next(new_node)
单链表指定位置添加节点步骤
单向循环链表指定位置添加节点步骤
7、删除节点
def remove(self,data):
"""删除第一个匹配的节点"""
# 若链表为空,则直接返回
if self.is_empty():
return
curr_node = self._head
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != self._head:
# 先找到尾节点,将尾节点的next指向第二个节点
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
# curr_node指向了尾节点
curr_node.set_next(self._head.get_next())
self._head = self._head.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
pre = self._head
while curr_node.get_next() != self._head:
# 找到了要删除的数据
if curr_node.get_data() == data:
pre.set_next(curr_node.get_next())
return
else:
pre = curr_node
curr_node = curr_node.get_next()
# curr_node指向了尾节点
if curr_node.get_data() == data:
pre.set_next(curr_node.get_next())
else:
# 要删除的元素不存在
print("NO %d element"% data)
单链表删除节点步骤
单向循环链表删除节点步骤
8、查找节点是否存在
def search(self,data):
"""查找节点是否存在"""
if self.is_empty():
return False
curr_node = self._head
if curr_node == data:
return True
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
if curr_node.get_data() == data:
return True
return False
完整代码 SingleCyclicLinkedList.py
# -*- coding:UTF-8 -*-
class Node():
"""节点"""
def __init__(self,data):
self._data = data
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
class SingleCyclicLinkedList():
"""单向循环链表"""
def __init__(self):
self._head = None
def is_empty(self):
"""判断链表是否为空"""
return self._head == None
def length(self):
"""返回链表的长度"""
# 如果链表为空,返回长度为0
if self.is_empty():
return 0
count = 1
curr_node = self._head
while curr_node.get_next() != self._head:
count += 1
curr_node = curr_node.get_next()
return count
def travel(self):
"""遍历链表"""
if self.is_empty():
print("empty SingleCyclicLinkedList!")
curr_node = self._head
print(curr_node.get_data())
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
print(curr_node.get_data())
def add(self,data):
"""头部添加节点"""
new_node = Node(data)
if self.is_empty():
self._head = new_node
new_node.set_next(self._head)
# 添加的节点指向self._head
else:
new_node.set_next(self._head)
# 移到链表尾部,将尾节点的next指向new_nede
curr_node = self._head
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
curr_node.set_next(new_node)
# self._head指向new_node
self._head = new_node
def append(self,data):
"""尾部添加节点"""
new_node = Node(data)
if self.is_empty():
self._head = new_node
new_node.set_next(self._head)
else:
# 移到链表尾部
curr_node = self._head
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
# 将尾节点指向node
curr_node.set_next(new_node)
# 将node指向头结点self._head
new_node.set_next(self._head)
def insert(self,pos,data):
"""在指定位置后添加节点"""
if pos <= 0:
self.add(data)
elif pos >= self.length():
self.append(data)
else:
new_node = Node(data)
count = 0
pre = self._head
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
while count < pos-1:
count += 1
pre = pre.get_next()
# 现将新节点new_node的next指向插入位置的节点
new_node.set_next(pre.get_next())
# 将插入位置的前一个节点的next指向新节点
pre.set_next(new_node)
def remove(self,data):
"""删除第一个匹配的节点"""
# 若链表为空,则直接返回
if self.is_empty():
return
curr_node = self._head
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != self._head:
# 先找到尾节点,将尾节点的next指向第二个节点
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
# curr_node指向了尾节点
curr_node.set_next(self._head.get_next())
self._head = self._head.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
pre = self._head
while curr_node.get_next() != self._head:
# 找到了要删除的数据
if curr_node.get_data() == data:
pre.set_next(curr_node.get_next())
return
else:
pre = curr_node
curr_node = curr_node.get_next()
# curr_node指向了尾节点
if curr_node.get_data() == data:
pre.set_next(curr_node.get_next())
else:
# 要删除的元素不存在
print("NO %d element"% data)
def search(self,data):
"""查找节点是否存在"""
if self.is_empty():
return False
curr_node = self._head
if curr_node == data:
return True
while curr_node.get_next() != self._head:
curr_node = curr_node.get_next()
if curr_node.get_data() == data:
return True
return False
if __name__ == "__main__":
ll = SingleCyclicLinkedList()
ll.add(1)
ll.add(2)
ll.add(3)
ll.append(4)
ll.insert(-1,5) # 插入位置小于0
ll.insert(10,5) #插入位置大于链表长度
ll.insert(2,5)
ll.remove(3) # 移除头结点
ll.remove(4) # 移除尾节点
ll.remove(5) # 移除中间节点
ll.remove(10) # 移除不存在节点
ll.travel()
print(ll.search(3))
print(ll.search(22))
print(ll.is_empty())
print(ll.length())
Github 代码地址 SingleCyclicLinkedList.py
3.4、双向链表
双向链表中每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
单向循环链表
3.4.1、节点实现
class Node():
"""节点"""
def __init__(self,data):
self._data = data
self._next = None
self._prev = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def get_prev(self):
return self._prev
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
def set_prev(self,new_prev):
self._prev = new_prev
3.4.2、双向链表的操作
- is_empty(self) 链表是否为空
- length(self) 链表长度
- travel(self) 遍历整个链表
- add(self,data) 链表头部添加节点
- append(self,data) 链表尾部添加节点
- insert(self,pos, data) 指定位置添加节点
- remove(self,data) 删除节点
- search(self,data) 查找节点是否存在
3.4.3、双向链表的实现
1、判断是否为空
def is_empty(self):
"""判断链表是否为空"""
return self._head == None
2、计算链表长度
def length(self):
"""返回链表的长度"""
curr_node = self._head
count = 0
while curr_node != None:
count += 1
curr_node = curr_node.get_next()
return count
单链表计算链表长度步骤
双向链表计算链表长度步骤
3、遍历链表
def travel(self):
"""遍历链表"""
curr_node = self._head
while curr_node != None:
print(curr_node.get_data())
curr_node = curr_node.get_next()
双向链表遍历链表步骤
4、头部添加节点
def add(self,data):
"""头部添加节点"""
new_node = Node(data)
# 如果是空链表,将self._head指向新节点
if self.is_empty():
self._head = new_node
else:
# 将new_node的next指向self._head的头结点
new_node.set_next(self._head)
# 将self._head的头结点的prev指向new_node
self._head.set_prev(new_node)
# 将self._head指向new_node
self._head = new_node
单链表头部添加节点步骤
双向链表头部添加节点步骤
5、尾部添加节点
def append(self,data):
"""尾部添加节点"""
new_node = Node(data)
if self.is_empty():
# 如果是空链表,将self._head指向new_node
self._head = new_node
else:
# 移到链表尾部
curr_node = self._head
while curr_node.get_next() != None:
curr_node = curr_node.get_next()
# 将尾节点curr_node的next指向new_node
curr_node.set_next(new_node)
# 将new_node的prev指向curr_node
new_node.set_prev(curr_node)
单链表尾部添加节点步骤
双向链表尾部添加节点步骤
6、指定位置添加节点
注意是插入第pos个节点后
指定位置添加节点
def insert(self,pos,data):
"""指定位置后添加节点"""
if pos<=0:
self.add(data)
elif pos>=self.length():
self.append(data)
else:
new_node = Node(data)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
pre = self._head
while count < pos-1:
count += 1
pre = pre.get_next()
# 将new_node的prev指向pre
new_node.set_prev(pre)
# 将new_node的next指向pre的下一个节点
new_node.set_next(pre.get_next())
# 将pre的下一个节点的prev指向new_node
pre.get_next().set_prev(new_node)
# 将pre的next指向node
pre.set_next(new_node)
单链表指定位置添加节点步骤
双向链表指定位置添加节点步骤
7、删除节点
删除节点
def remove(self,data):
"""删除第一个匹配的节点"""
if self.is_empty():
return
curr_node = self._head
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != None:
# 将第二个节点的prev设置为None
curr_node.get_next().set_prev(None)
# 将self._head指向第二个节点
self._head = curr_node.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
while curr_node != None:
if curr_node.get_next() != None:
# 找到了要删除的数据
if curr_node.get_data() == data:
# 将curr_node的前一个节点的next指向curr_node的后一个节点
curr_node.get_prev().set_next(curr_node.get_next())
# 将curr_node的后一个节点的prev指向curr_node的前一个节点
curr_node.get_next().set_prev(curr_node.get_prev())
break
# 没有找到了要删除的数据,继续按链表向后移动节点
else:
curr_node = curr_node.get_next()
else:
if curr_node.get_data() == data:
# 将curr_node的前一个节点的next指向curr_node的后一个节点
curr_node.get_prev().set_next(curr_node.get_next())
curr_node = curr_node.get_next()
break
else:
# 要删除的元素不存在
print("NO %d element" % data)
break
单链表删除节点步骤
双向链表删除节点步骤
注意:在删除操作中,单链表、单向循环链表、双向链表都需要经过几个步骤:
- 要分只有一个节点和有多个节点的情况;
- 要分要删除的节点存在和不存在的情况;
- 特别要注意的是删除头结点和尾节点、中间节点的区别。
8、查找节点是否存在
def search(self,data):
"""查找元素是否存在"""
curr_node = self._head
while curr_node != None:
if curr_node.get_data() == data:
return True
curr_node = curr_node.get_next()
return False
完整代码 DoublyLinkedList.py
# -*- coding:UTF-8 -*-
class Node():
"""节点"""
def __init__(self,data):
self._data = data
self._next = None
self._prev = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def get_prev(self):
return self._prev
def set_data(self,new_data):
self._data = new_data
def set_next(self,new_next):
self._next = new_next
def set_prev(self,new_prev):
self._prev = new_prev
class DoublyLinkedList():
"""双向链表"""
def __init__(self):
self._head = None
def is_empty(self):
"""判断链表是否为空"""
return self._head == None
def length(self):
"""返回链表的长度"""
curr_node = self._head
count = 0
while curr_node != None:
count += 1
curr_node = curr_node.get_next()
return count
def travel(self):
"""遍历链表"""
curr_node = self._head
while curr_node != None:
print(curr_node.get_data())
curr_node = curr_node.get_next()
def add(self,data):
"""头部添加节点"""
new_node = Node(data)
# 如果是空链表,将self._head指向新节点
if self.is_empty():
self._head = new_node
else:
# 将new_node的next指向self._head的头结点
new_node.set_next(self._head)
# 将self._head的头结点的prev指向new_node
self._head.set_prev(new_node)
# 将self._head指向new_node
self._head = new_node
def append(self,data):
"""尾部添加节点"""
new_node = Node(data)
if self.is_empty():
# 如果是空链表,将self._head指向new_node
self._head = new_node
else:
# 移到链表尾部
curr_node = self._head
while curr_node.get_next() != None:
curr_node = curr_node.get_next()
# 将尾节点curr_node的next指向new_node
curr_node.set_next(new_node)
# 将new_node的prev指向curr_node
new_node.set_prev(curr_node)
def insert(self,pos,data):
"""指定位置后添加节点"""
if pos<=0:
self.add(data)
elif pos>=self.length():
self.append(data)
else:
new_node = Node(data)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头结点开始移动到指定的位置
pre = self._head
while count < pos-1:
count += 1
pre = pre.get_next()
# 将new_node的prev指向pre
new_node.set_prev(pre)
# 将new_node的next指向pre的下一个节点
new_node.set_next(pre.get_next())
# 将pre的下一个节点的prev指向new_node
pre.get_next().set_prev(new_node)
# 将pre的next指向node
pre.set_next(new_node)
def remove(self,data):
"""删除第一个匹配的节点"""
if self.is_empty():
return
curr_node = self._head
# 若头结点的元素就是要查找的元素data
if curr_node.get_data() == data:
# 如果链表不止一个节点
if curr_node.get_next() != None:
# 将第二个节点的prev设置为None
curr_node.get_next().set_prev(None)
# 将self._head指向第二个节点
self._head = curr_node.get_next()
else:
# 链表只有一个节点
self._head = None
# 若头结点的元素不是要查找的元素data
else:
while curr_node != None:
if curr_node.get_next() != None:
# 找到了要删除的数据
if curr_node.get_data() == data:
# 将curr_node的前一个节点的next指向curr_node的后一个节点
curr_node.get_prev().set_next(curr_node.get_next())
# 将curr_node的后一个节点的prev指向curr_node的前一个节点
curr_node.get_next().set_prev(curr_node.get_prev())
break
# 没有找到了要删除的数据,继续按链表向后移动节点
else:
curr_node = curr_node.get_next()
else:
if curr_node.get_data() == data:
# 将curr_node的前一个节点的next指向curr_node的后一个节点
curr_node.get_prev().set_next(curr_node.get_next())
curr_node = curr_node.get_next()
break
else:
# 要删除的元素不存在
print("NO %d element" % data)
break
def search(self,data):
"""查找元素是否存在"""
curr_node = self._head
while curr_node != None:
if curr_node.get_data() == data:
return True
curr_node = curr_node.get_next()
return False
if __name__ == "__main__":
ll = DoublyLinkedList()
ll.add(1)
ll.add(2)
ll.add(3)
ll.append(4)
ll.insert(-1,5) # 插入位置小于0
ll.insert(10,5) #插入位置大于链表长度
ll.insert(2,5)
ll.remove(3) # 移除头结点
ll.remove(4) # 移除尾节点
ll.remove(5) # 移除中间节点
ll.remove(10) # 移除不存在节点
ll.travel()
print(ll.search(3))
print(ll.search(22))
print(ll.is_empty())
print(ll.length())
Github 代码地址 DoublyLinkedList.py
总结: 目前还只是完成了单链表、单向循环链表、双向链表的基本操作,在以后遇到其他情况在来补充。
目前实现的还是没有头结点的形式,还要追加学习有头结点的形式。
参考文献:
1. Python基础算法/剑指offer
2. 数据结构的一些概念
3. Python就业班
4. Python数据结构——链表的实现
4. 郝斌数据结构