1、链表的定义
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
具体结构如下图所示:
2、Python中变量标识的本质
3、单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
表元素域elem用来存放具体的数据
链接域next用来存放下一个节点的位置(python中的标识)
变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点
4、单链表的操作
(1)is_empty() 链表是否为空
(2)length() 链表长度
(3)travel() 遍历整个链表
(4)add(item) 链表头部添加元素
(5)append(item) 链表尾部添加元素
(6)insert(pos, item) 指定位置添加元素
(7)remove(item) 删除节点
(8)search(item) 查找节点是否存在
5、单链表的节点实现
# 节点实现
class SingleNode(object):
"""单链表的结点"""
"""
存放节点类
每次调用该类,实例化一个节点
默认每个节点中有数据区,next区。
数据区为该数据,next区为空
"""
def __init__(self, item):
# _item存放数据元素
self.item = item
# _next是下一个结点的标识
self.next = None
6、单链表的操作实现
class SingleNode(object):
"""单链表的结点"""
def __init__(self, item):
# _item存放数据元素
self.item = item
# _next是下一个结点的标识
self.next = None
class SingleLinkList(object):
"""单链表"""
"""
存放数据类
将所有的节点存放起来的容器
首先,默认链表为空,在这里需要有一个头节点,默认头节点为空
"""
def __init__(self, node=None):
self.__head = node
def is_empty(self):
"""判断链表是否为空"""
return self.__head == None
def length(self):
"""链表长度"""
# 实例化游标
# cur游标,用来移动遍历结点 cur初始时指向头节点
cur = self.__head
# 计数,这里对空链表进行判断,如果是链表,则不会进入循环,直接输出 0
count = 0
# 移动游标,获取下一个数据,然后让count +=1
# 尾节点指向None,当未达到尾部时
while cur != None:
count += 1
# 将cur后移一个节点
cur = cur.next
return count
def travel(self):
"""遍历整个链表"""
# 实例化一个游标
cur = self.__head
# 获取每个数据区中的数据
# 移动游标,每移动一次,输出一次数据区内的数据
while cur != None:
print(cur.item)
cur = cur.next
尾部添加元素
def append(self, item):
"""链表尾部添加元素"""
"""
往链表尾部添加数据
1、实例化游标:使用游标指向每一个数据,检索数据和判断数据next是否为空
2、移动游标,从头节点开始,每次使用self.next移动,以为next指向的就是下一个数据
3、添加数据,首先判断链表是否为空,为空的情况下,直接将头节点等于node数据
4、如果不为空,需要从头节点开始,移动游标,判断游标指向的next是否为空
5、使用循环不停的移动节点,当遇到节点中的next为空的情况下停止移动
6、循环条件: 当 条件 != None, 进入循环获取,当cur为空时就不会进入循环,所以在这里要使用 cur != None
"""
# 首先要实例化一个节点
node = SingleNode(item)
# 先判断链表是否为空,若是空链表,则将_head指向新节点
if self.is_empty():
self.__head = node
# 若不为空,则找到尾部,将尾节点的next指向新节点
else:
# 实例化一个游标
cur = self.__head
while cur.next != None:
# 移动游标,得到最后一个游标的数据
cur = cur.next
# 将移动后的数据的下一个next添加上 node
cur.next = node
测试结果:
头部添加元素
def add(self, item):
"""链表头部添加元素"""
"""
分析:将数据添加到第一个节点中
1、需要先将node的next指向原第一个节点,这原第一个节点就是self._head
2、再将self._head指向node进行连接
"""
# 先实例化节点
node = SingleNode(item)
# 将数据添加到头部去
node.next = self.__head
self.__head = node
指定位置添加元素
def insert(self, pos, item):
"""指定位置添加元素"""
"""
分析:
1、首先找到该位置的数据
2、将添加的数据的next = 原位置的next数据
3、原数据的next = node新数据
"""
if pos <= 0:
# 如果输入的索引小于或者等于0,默认使用头插法
self.add(item)
elif pos > self.length()-1:
# 如果输入的索引大于链表的长度,使用尾插法
self.append(item)
else:
# 实例化数据节点
node = SingleNode(item)
# 找到该数据的位置
# 实例化一个游标
# pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
pre = self.__head
# 计数
count = 0
while count < pos-1:
count += 1
pre = pre.next
# 先将新节点node的next指向插入位置的节点
# 将添加的数据的next = 原位置的next数据
node.next = pre.next
# 原数据的next = node新数据
pre.next = node
测试结果:
查找节点是否存在
def search(self, item):
"""查找节点是否存在"""
"""
查询指定的数据是否存在
分析: 查询指定的数据,需要对整个链表扫描,判断这个数据是否等的该节点中的数据
"""
# 实例化一个游标
cur = self.__head
# 进行判断是否相等
while cur != None:
# 判断
if cur.item == item:
return True
else:
cur = cur.next
# 否则返回FALSE
return False
测试结果:
删除节点
def remove(self, item):
"""删除结点"""
"""
移除指定的数据
分析:
1、删除数据,需要首先判断数据是否存在
2、找到该数据所在的位置,将该数据的上一个数据的next指向自己的next
3、如何获取该数据的指向,和上一个数据的指向
4、需要定义两个指针
5、先将两个指针相等,再讲一个指针先移动一次,再同时移动
"""
# 先找到该数据
cur = self.__head
per = None
while cur != None:
if cur.item == item:
# 判断是否为第一个节点
if cur == self.__head:
# 将头指针指向头节点的后一个节点
self.__head = cur.next
else:
# 将删除位置前一个节点的next指向删除位置的后一个节点
per.next = cur.next
break
else:
# 如果在当前节点中没有相等的,将进行节点移动
# 移动要注意,先将两个游标相等,再将另一个游标移动一次
# 使得两个游标保持一个身位
per = cur
cur = cur.next
测试结果:
链表与顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
链表与顺序表的各种操作复杂度如下所示:
注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作。**链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。顺序表查找很快,主要耗时的操作是拷贝覆盖。**因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。