data_structure_2_链表
2 链表
首先复习顺序表
事实上右侧的顺序表已经很像链表的形式,但不同在于一开始规定了顺序表的大小而链表是一种手拉手的形式。
直观感受链表(不同的存储空间)(找一根线串起来数据)
链表的定义:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)(屁股保存地址)。
单向链表
第一个节点叫头节点最后一个叫尾节点
单链表的操作
is_empty() 链表是否为空
length() 链表长度
travel() 遍历整个链表
add(item) 链表头部添加元素
append(item) 链表尾部添加元素
insert(pos, item) 指定位置添加元素
remove(item) 删除节点
search(item) 查找节点是否存在
此为创建一个节点的模型,应该是每个节点的第二个元素储存指向下一个节点元素的地址
下图就是__head 为头节点,node是数据通过Node构造为节点的形式(代码中体现)
代码实现:
# 节点实现
"""class SingleNode(object):
#单链表的节点
def __init__(self, item):
# _item存放数据元素
self.item = item
# _next是下一个节点的标识
self.next = None
"""
# 构造一个节点 类
class Node(object):
"""节点"""
def __init__(self, elem): # init 表示初始化,elem指把数据保存下来 elem作为参数进来
self.elem = elem # 一个节点应该有两个元素 所以将元素的值放在 self.elem中
self.next = None # self.next 存放下一个节点数据的地址,一开始指向谁不知道所以指向空(None)
class SingleLinkList(object):
"""单链表"""
def __init__(self, node=None): # 3: 如果用户不输入头节点值 当成默认参数 None 直接创建空列表 None 其实又回到了下面一行
# self.__head = None 1:一上来设置为空 意思是不指向任何节点 | 私有加 __ 意义:只有这个函数内部使用
self.__head = node # 2:在上一行的基础上人性化,用户如果先构造节点,就将头指向这个头节点 node
def is_empty(self):
"""链表是否为空"""
return self.__head is None # class在变量和None进行比较时,应该使用 is。
# 可以把比较的值直接作为返回值 如果相等即为真就是1 即返回1
# 只要头节点指的是空那么就是空链表
def length(self):
"""链表长度"""
cur = self.__head # 让它等于指向表头指向的第一个节点,cur 用于移动遍历元素
# count 记录数量
count = 0
while cur is not None: # count 从0开始的好处就是能够记录空表的长度,一个代码实现空表与一般表的长度计算
count += 1
cur = cur.next # cur指向了 下一个节点
return count
def travel(self):
"""遍历整个链表"""
cur = self.__head
while cur is not None:
print(cur.elem, end=" ") # 打印每一个元素 空格隔开
cur = cur.next
print("") # 换行
def add(self, item):
"""链表头部添加元素,头插法"""
node = Node(item) # 把item这个数据封装成链表所需要的节点形式
node.next = self.__head
self.__head = node # 当一开始是空的时候 头和节点都指向None 所以跑一遍上述两行还是满足的
def append(self, item):
"""链表尾部添加元素,尾插法"""
node = Node(item)
if self.is_empty(): # 如果链表是空的 头直接指向插入的节点
self.__head = node
else:
cur = self.__head
while cur.next is not None:
cur = cur.next
cur.next = node
def insert(self, pos, item):
"""指定位置添加元素
:param item: 元素
:param pos从0开始
"""
if pos <= 0: # 如果输入位置是<=0的默认是在头部插入
ll.add(item)
elif pos > self.length() - 1:
self.append(item)
else:
pre = self.__head
count = 0
while count < (pos - 1):
count += 1
pre = pre.next
# 当循环退出后 pre指向pos-1位置
node = Node(item)
node.next = pre.next
pre.next = node
def remove(self, item):
"""删除节点,找到和你想删除的数一样的数"""
cur = self.__head
pre = None
while cur is not None:
if cur.elem == item:
# 先判断此节点是否是头节点 如果是头节点
if cur == self.__head:
self.__head = cur.next
else:
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
def search(self, item):
"""链表查找节点是否存在,并返回True或者False"""
cur = self.__head
while cur is not None:
if cur.elem == item:
return True
else:
cur = cur.next
return False
# 测试代码
if __name__ == "__main__":
ll = SingleLinkList()
print(ll.is_empty())
print(ll.length())
ll.append(1)
print(ll.is_empty())
print(ll.length())
ll.append(2)
ll.add(8)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append(6)
ll.insert(-1, 9)
ll.travel()
ll.insert(2, 100)
ll.travel()
ll.insert(10, 200)
ll.travel()
ll.remove(200)
ll.travel()
ll.insert(20, 200)
ll.travel()
ll.insert(21, 200)
ll.travel()
ll.remove(200)
ll.travel()
一些说明:
插入的原理:
代码顺序不能错,如果先让pre.next先指向400,整个链表后半部分直接丢失。
其余的都是类似的原理保证好先后顺序,考虑好特殊情况。不足是不能够删除重复元素
总结:
这样的一组序列元素的组织形式,我们可以将其抽象为线性表。
根据线性表的实际存储方式,分为两种实现模型:
顺序表: 将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
链表: 将元素存放在通过链接构造起来的一系列存储块中。
单链表和顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(n) | O(1) |
在中间插入/删除 | O(n) | O(n) |
注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作。链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。