数据结构(1)—单链表定义和操作
首先,需要了解变量存储的是数据的地址指向
a = 10 # a的空间指向10 ,存10的地址
b = 20
c = "aaa"
a, b = b, c # 从右边开始,找到b的存储地址,把地址引用导向改变一下
print(a, b) # 20 aaa
1.如何定义链表一个节点
node: 一个节点是一个类,
需要存储: 元素域和地址域
class Node(object): # 节点
def __init__(self, elem):
self.elem = elem # 存储元素值
2.如何定义一个单链表
对于单链表,唯一知道的信息就是头结点,所以生成单链表对象后,必须要init一个头结点
class SingLinkList(object):
"""
单链表: 数据分配的内存可以不连续,存储方便
"""
# 必须存在一个对象,指向头结点
def __init__(self, node=None):
# 初始化头结点为None 指向一个空链表,如果先初始化Node,则 把节点信息指给头结点 参数为node _head接收
self._head = node
2.1 判断链表是否为空
链表为空,即头结点指向的节点为None
def is_empty(self):
"""
是否为空,直接看头结点指的node是不是none
:return:
"""
return self._head == None
2.2 计算链表的长度
思路:
从头结点开始遍历,count为1,
cur后移,不为空的话,继续+1 ,一直后移,遍历,累加,直到cur指向None
注意:
因为循环体是 cur=cur.next
所以要判断 cur !=None 是循环条件,而不是 cur.next, 计数不要错了
def length(self):
"""链表长度. 特别注意: 判断是 cur.next还是cur 为空
思路: 定义当前节点 cur
移动当前节点 cur=cur.next
判断终止条件 cur=none
"""
cur = self._head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
2.3 链表遍历
当cur!=None时,
遍历移动元素:cur =cur.next
def travel(self):
"""遍历"""
cur = self._head
while cur != None:
print(cur.elem, end=" ")
cur = cur.next
2.4 头插法:每次头部插入元素 (考虑逆序链表的建立可用此方法)
时间复杂度:0(1)
思路:
(1)定义node
(2) 头结点的指向赋值给node的下一个节点
如何保证后面的链表不乱:
先定义node.next =_head
再把头结点的指向 node
注意:需要校验看当链表为空时,是否符合条件
如果不清楚是否包含,先用条件类的写一遍
def add(self, item):
"""时间复杂度:0(1)"""
"""头部添加:头插法"""
node = Node(item)
node.next = self._head # 先将头结点的指向赋值给node的下一个节点
self._head = node # 将node
#下面是没有合并条件时的写法
# if self.is_empty():
# self._head=node
# else:
# # 注意顺序问题: 1.先把新节点的地址存 原来链表的首结点 (要不然之前的链表丢失了) 2.再把头部节点指向新节点
# cur=self._head
# cur=node
# cur.next=cur.next.next
2.5 尾插法
时间复杂度:O(n)
思路: 先移动到尾部,再插入
特殊情况考虑:
(1)链表为空,head直接指向即可
(2)链表不为空,设置cur后一直后移,注意这里判断 cur.next,即下一个元素是否为空,一直到 为None,然后赋值 给 cur.next
def append(self, item):
"""时间复杂度:0(n):从头到尾部遍历"""
"""尾部添加:一直走到尾节点,然后传入数据,尾插法------"""
# 先把要添加的节点初始化
node = Node(item)
# cur = self._head
# 注意:
if self.is_empty():
print("当前节点为空!!插入元素")
self._head = node
else:
# 先进行遍历,一直到最后一个元素
cur = self._head
while cur.next != None: # 如果cur为空,则cur.next直接报错了,所以需要考虑特殊情况
cur = cur.next
# 在最后一个节点的下一个节点插入
cur.next = node
2.6 任意位置插入
时间复杂度:O(n)
特殊情况考虑:
(1)pos为负数,或者为0,1 之类的,采用头插法
(2)pos大于元素长度,注意不包括长度,采用尾插法
(3)其他情况: 比如pos为2,则需要插入到原来的 1和2之间,即要在pos的前一个位置拦截,cur游标到要插入位置的前一个元素
赋值: 先考虑node的next : node.next = cur.next
然后考虑cur.next=node
def insert(self, pos, item):
"""时间复杂度:0(n),考虑最差情况"""
"""指定位置插入,注意第一个元素pos是1"""
# 考虑如果插入的位置小于0或等于0 ,头插法
if pos <= 1:
self.add(item)
# 如果插入的位置大于链表长度,进行尾插法,注意不包括最后一个位置
elif pos > self.length():
self.append(item)
else:
node = Node(item)
cur = self._head
# 要在pos的前一个位置拦截,比如3,要3的位置后移,cur游标到要插入位置的前一个元素,因为后执行next,所以还要前移一个位置
for i in range(pos - 2):
cur = cur.next
node.next = cur.next # 将cur的下一个元素指向 node的下一个元素
cur.next = node # 将node指向cur的下一个元素
"""
或者使用while实现:
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
pre = self._head
while count < (pos-1):
count += 1
pre = pre.next
# 先将新节点node的next指向插入位置的节点
node.next = pre.next
# 将插入位置的前一个节点的next指向新节点
pre.next = node
"""
2.7 删除元素
时间复杂度:O(n)
思路:
采用两个指针,pre和cur ,cur要比pre先移动一步
当找到元素后,pre.next = cur.next 即可,即当前cur被销毁
考虑的特殊情况:
1.第一个节点就是要删除的
(1)只有一个元素 : 当前元素是cur和头结点指向一样,那么头结点指向 cur的next , 注意:这里写 cur=cur.next是不对
cur 和 self._head不一样
如果 cur =self._head,那么 self._head = cur.next
(2)不止一个元素
2. 要删除的是最后一个元素
cur 是最后一个元素,则 cur=cur.next 即为None
def remove(self, item):
"""移除元素: 先遍历查找元素是否存在,存在的话就remove"""
# 方法1: 使用两个指针 一个pre 一个cur cur一定要比pre先移动一个节点,所以pre先移动到cur的位置,cur再往后移动
cur = self._head
pre = None
while cur != None:
# 需要考虑的特殊情况:1.第一个节点就是要删除的 (1)只有一个元素 (2)不止一个元素 2. 要删除的是最后一个元素
# 如果当前的元素是要删除的元素
if cur.elem == item:
# 删除元素用两个指针或一个指针都可以 方法1:一个指针是 cur=cur.next (将下一个指针指向当前指针) 方法2 :如下
# 此时需要判断需要删除符合要求的是不是头结点,如果是,不需要pre节点了
if cur == self._head:
# cur=cur.next # 注意:执行这行的时候,删除失败 为啥是self._head= cur.next 有啥区别?????
self._head = cur.next
else:
pre.next = cur.next # 即cur指向的下一个元素指向 pre.next cur这个元素删除掉
break # 注意,如果不break 会重复执行if 和else
# 当cur 不是要查找的元素,pre和 cur 都后移
else:
pre = cur
cur = cur.next
2.8 查找一个元素
时间复杂度:O(n)
思路:
从cur开始遍历,看cur.elm是不是和目标值相等
分支:
1.头结点就是要找的元素: 直接返回true
2.头结点不是:
循环到cur.next 继续判断
(1)是 返回true
(2) 不是 移动cur=cur.next
如果循环完毕还是没有,返回false
def search(self, item):
"""时间复杂度:0(n):需要遍历"""
"""查找元素:遍历比对数据,如果游标所指数据是,返回true"""
cur = self._head
# 先判断头结点是不是等于item
if cur.elem == item:
return True
else:
# 如果头结点的下一个节点不为none ,
while cur.next != None:
if cur.elem == item:
return True
else:
cur = cur.next
return False # 注意当遍历后都没有后,要返回false
# 注意:此处把if去掉,也是验证成功的,因为直接不符合while,直接返回false
最后调用和执行如下:
if __name__ == "__main__":
ll = SingLinkList()
print(ll.is_empty()) # True
print(ll.length())
ll.append(1)
print(ll.is_empty()) # True
print(ll.length())
ll.add(8) # 头插法插入8
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.insert(9, 7)
ll.insert(0, 100) # 100 8 1 2 3 4 5 7
ll.insert(2, 200) # 100 200 8 1 2 3 4 5 7
ll.insert(1, 400) # 400 100 200 8 1 2 3 4 5 7
a = ll.search(999) # False
print(a)
ll.remove(100)
ll.travel() # 400 200 8 1 2 3 4 5 7
ll.remove(400)
ll.travel() # 200 8 1 2 3 4 5 7
ll.remove(7)
ll.travel() # 200 8 1 2 3 4 5