人工智能
python,大数据,机器学习,深度学习,计算机视觉
三、python算法篇(三)链表
链表的提出
前言
前面学了顺序表,但顺序表的存储地址是连续的,有时候处理起来不方便,因为顺序表在构建时需要预先知道数据大小来申请连续的存储空间,在扩充时又需要进行数据的搬迁来尤其是保序插入或删除要保证顺序表顺序不变,若存储数据量很大,使用起来非常不灵活。
那么我们能不能设计一种表,扩充的时候扩一个存一个,删除的时候想删哪个就直接删哪个,实现插入,修改,删除地灵活操作,思路?
我们可以把每个元素存到任意地址,不必连续,然后每个元素所占的存储地址空间其自身再存储两个信息(注1):我上一个元素的地址,我下一个元素的地址。这样就能把这些元素穿起来成一串了。而且方便增删改等操作。这种设计就是链表。
注1:存两个信息是双链表,存1个信息是单链表,后面讲。
链表的定义
链表是一种线性表,但不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
举例:
链表的好处
可以充分利用计算机内存空间,实现灵活的内存动态管理。
分类
链表和上一篇讲的顺序表都属于线性表。线性表是一维的,再扩展成二维就变成树、图了(后面讲)。
线性表分为;两种:顺序表,链表。
python中变量标识的本质
a = 10
b = 20
a, b = b, a #实现了a, b数值的交换,只有python有这种写法
"a = 10"内存中实现过程是怎样的?
(1)一般别的编程语言的理解是:先在内存开辟一内存来存储10,而a是存储10这内存地址的别名。
(2)但在python中是下面这样的:先在内存开辟一内存来存储10,再开辟一内存来存储对象a,最后将对象a指向10(即a存了10的地址)。如下图:
注:其实严格来说,python中有专门的存储10,20这样的小int型的存储空间的,我们先不考虑这个,假设10, 20都是动态分配存储空间的。我们接着分析:
“a, b = b, a” (python专有写法)将对象b指向的内存地址赋值给对象a(变量a),将对象a指向的内存地址赋值给对象b。
在python中,一切皆是对象!所以同样,可以推广,将函数看成一个对象。
#定义一个函数,将函数赋值给对象a。
Def f():
pass
a = f
也就是对象a指向函数f,即对象a存放了函数f的地址,因为定义了一个函数,必定有一块内存空间来存放这个函数对象及其代码。
结论:
在赋值过程中,
- 赋值号"=" 右侧,无论数值,函数,字符串…,都可以看成对象,
- 赋值号"=" 只是改变了指向,
- 赋值号"="左边无非是一个简单的对象名字而已(占用小小的存储空间),存放的是指向目标对象的地址。
head,next
头节点被head所指,
head本来是没有的,是后加的,方便操作首元素/首节点
下面是类创建对象且没有默认参数初始化 生成空链表的过程:
文件命名要规范
注意:python文件命名时候,前面视情况最好加上数字用来区分,以免命名和引用的包重复出现bug。哪怕你随便起名叫abc,但巧的是"abc"也是一个包。
链表的操作
(1) 单(向)链表的操作
文件03_single_link_list.py
代码如下:
#coding: utf-8
"""节点"""
class Node(object): #节点Node
def __init__(self, elem):
self.elem = elem
self.next = None #python的None 等价 php的Null的意思。
#node = Node(100)
"""单链表"""
class SingleLinkList(object):
def __init__(self, node = None):
self.__head = node #双下划线__ 表私有的
"""链表是否为空"""
def is_empty(self):
return self.__head == None # self.head即head所指向的地址,即头节点
# 或写 return self.__head is None
"""链表长度"""
def length(self):
# 定义cur指针/游标,用来移动遍历节点。cur是current,当前指针位置。
cur = self.__head #一定看懂这句话!! self.head是第一个元素,头节点。
count = 0 #注意空链表的特殊情况
while cur != None:
count += 1 #注意python没有count++语法
cur = cur.next
return count
"""遍历整个链表"""
def travel(self):
cur = self.__head
while cur != None:
print(cur.elem, end=" ") #end=" "是打印分隔符,若不写这句则默认换行。
cur = cur.next
print("") #换行。print默认带有换行功能
"""链表头部添加元素---头插法"""
def add(self, item): #item是要添加的节点
node = Node(item)
node.next = self.__head #原head所指向的是原来的首节点,被新加的node去指向
self.__head = node
"""链表尾部添加元素----尾插法"""
def append(self, item): #item是要添加的节点
node = Node(item)
if self.__head == None:#原链表为空链表 #或调用上面自己定义的函数if self.is_empty():
self.__head = node
else:
cur = self.__head
while cur.next != None:
cur = cur.next
cur.next = node
"""指定位置插入元素"""
def insert(self, pos, item): #pos是插入节点的位置,插到哪里;item是要添加的节点
# :param pos 从0开始
if pos <= 0: #如果插入位置小于头部元素下标0,则默认头插法,在首部插入
self.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 != None:
if cur.elem == item:
# 先判断此节点是否是头节点
if cur == self.__head: #若头节点,删除头节点
self.__head = cur.next #__head指向 cur的下一个元素。(__head指向谁,谁就是头节点。这里__head选cur的下一个元素为头节点)。
else: #否则,删除当前节点。
pre.next = cur.next #cur的前一个元素指针pre指向cur的下一个元素。
break
else:
pre = cur
cur = cur.next
"""查找节点是否存在"""
def search(self, item):
cur = self.__head
while cur != None:
if cur.elem == item:
return True #True False必须要大写
else:
cur = cur.next
return False
"""
#single_obj = SingleLinkList()
node = Node(100)
single_obj = SingleLinkList(node)
single_obj.add()
"""
if __name__ == "__main__": #解释参考https://blog.csdn.net/anshuai_aw1/article/details/82344884
ll = SingleLinkList() #没传参,默认生成空链表
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
ll.append(100) #扩充链表,追加元素
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
#扩充链表,尾插法
ll.append(200)
ll.append(300)
ll.append(400)
#遍历打印整个链表
ll.travel()
#任意位置插入元素
ll.insert(-1, 22) #插入位置-1小于0,按照上面函数定义,默认头插法
ll.insert(2, 33)
ll.insert(100, 44) #插入位置100小于0,按照上面函数定义,默认尾插法
#遍历打印整个链表
ll.travel()
#任意位置删除元素
ll.remove(100)
#遍历打印整个链表
ll.travel()
#查找某元素是否在链表中
ll.search(100)
ll.search(200)
(2) 双(向)链表的操作
文件04_double_link_list.py
代码如下:
#coding: utf-8
"""节点"""
class Node(object):
def __init__(self, elem):
self.elem = elem
self.next = None #后继next
self.prev = None #前驱prev #previous
"""双链表"""
class DoubleLinkList(object):
def __init__(self, node = None):
self.__head = node
#######start:下面这三种方法同单链表一样##############
"""链表是否为空"""
def is_empty(self):
return self.__head is None
"""链表长度"""
def length(self):
# 定义cur指针/游标,用来移动遍历节点。
cur = self.__head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
"""遍历整个链表"""
def travel(self):
cur = self.__head
while cur != None:
print(cur.elem, end=" ")
cur = cur.next
print("")
#######end####################################
"""链表头部添加元素---头插法"""
def add(self, item): #item是要添加的节点
node = Node(item)
node.next = self.__head
self.__head = node
node.next.prev = node
"""链表尾部添加元素----尾插法"""
def append(self, item): #item是要添加的节点
node = Node(item)
if self.__head is None:#原链表为空链表 #或调用上面自己定义的函数if self.is_empty():
self.__head = node
else:
cur = self.__head
while cur.next != None:
cur = cur.next
cur.next = node
node.prev = cur
"""指定位置插入元素"""
def insert(self, pos, item): #pos是插入节点的位置,插到哪里;item是要添加的节点
# :param pos 从0开始
if pos <= 0: #如果插入位置小于头部元素下标0,则默认头插法,在首部插入
self.add(item)
elif pos > (self.length()-1): #如果插入位置大于末尾元素下标,则默认尾插法,在尾端插入
self.append(item)
else:
cur = self.__head
count = 0
while count < pos:
count += 1
cur = cur.next
#当循环退出后,pre指向pos-1的位置
node = Node(item)
node.next = cur
node.prev = cur.prev
cur.prev.next = node
cur.prev = node
"""删除节点"""
def remove(self, item):
cur = self.__head
while cur != None:
if cur.elem == item:
# 先判断此节点是否是头节点
if cur == self.__head: #若头节点,删除头节点
self.__head = cur.next
if cur.next: #判断链表是否只有一个节点
cur.next.prev = None
else: #否则,删除当前节点。
cur.prev.next = cur.next
if cur.next:
cur.next.prev = cur.prev
break
else:
cur = cur.next
"""查找节点是否存在"""
def search(self, item):
cur = self.__head
while cur != None:
if cur.elem == item:
return True #True False必须要大写
else:
cur = cur.next
return False
#test
if __name__ == "__main__":
ll = DoubleLinkList() #没传参,默认生成空链表
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
ll.append(100) #扩充链表,追加元素
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
#扩充链表,尾插法
ll.append(200)
ll.append(300)
ll.append(400)
#遍历打印整个链表
ll.travel()
#任意位置插入元素
ll.insert(-1, 22)
ll.insert(2, 33)
ll.insert(100, 44)
#遍历打印整个链表
ll.travel()
ll.remove(100)
ll.travel()
注:若在实际中,已定义好了前面那种单链表类及相关的方法,则写双链表类时候可以直接将方法继承过来。
(3) 单(向)循环链表的操作
写代码前要弄清楚逻辑,画好图,理清思路!
文件05_single_cycle_link_list.py
代码如下:
class Node(object):
def __init__(self, elem):
self.elem = elem
self.next = None #后继next
self.prev = None #前驱prev #previous
"""单向循环链表"""
class SingleCycleLinkList(object):
def __init__(self, node = None):
self.__head = node
if node:#如果类生成对象传参了,即初始时单个节点,则node指向自己(单向循环链表)
node.next = node
"""链表是否为空"""
def is_empty(self):
return self.__head == None
"""链表长度"""
def length(self):
if self.is_empty():
return 0
# 定义cur指针/游标,用来移动遍历节点。
cur = self.__head
count = 1
##
## 对于循环链表,所有的while条件几乎全都由'cur != None' 变成 'cur.next != self.__head'。
##
while cur.next != self.__head: #直到再一次遇到__head(循环链表),循环才结束。
count += 1
cur = cur.next
return count
"""遍历整个链表"""
def travel(self):
cur = self.__head
while cur.next != self.__head:
print(cur.elem, end=" ")
cur = cur.next
#退出循环,cur指向尾结点,但尾结点的元素还未打印。
print(cur.elem)
"""链表头部添加元素---头插法"""
def add(self, item): #item是要添加的节点
node = Node(item)
if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
#退出循环,cur指向尾结点
node.next = self.__head
self.__head = node
#cur.next = node
cur.next = self.__head
"""链表尾部添加元素----尾插法"""
def append(self, item): #item是要添加的节点
node = Node(item)
if self.is_empty():#原链表为空链表 #或调用上面自己定义的函数if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
# node.next = cur.next
node.next = self.__head
cur.next = node
"""指定位置插入元素"""
def insert(self, pos, item): #pos是插入节点的位置,插到哪里;item是要添加的节点
# :param pos 从0开始
if pos <= 0: #如果插入位置小于头部元素下标0,则默认头插法,在首部插入
self.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):
if self.is_empty():
return #直接退出函数
cur = self.__head
pre = None
while cur.next != self.__head:
if cur.elem == item:
# 先判断此节点是否是头节点
if cur == self.__head: #若头节点,删除头节点
#找一下尾结点
rear = self.__head
while rear.next != self.__head:
rear = rear.next
self.__head = cur.next
rear.next = self.__head
else: #否则,删除当前节点。
pre.next = cur.next
return #要退出函数,不能用break否则出错!
else:
pre = cur
cur = cur.next
#退出循环,cur指向尾结点
if cur.elem == item:
if cur == self.__head:
#链表只有一个节点,删掉
self.__head = None
else:
pre.next = self.__head
# 或写 pre.next = cur.next
"""查找节点是否存在"""
def search(self, item):
cur = self.__head
while cur.next != self.__head:
if cur.elem == item:
return True #True False必须要大写
else:
cur = cur.next
#退出循环,cur指向尾结点。
if cur.elem == item:
return True
return False
#test
if __name__ == "__main__":
ll = SingleCycleLinkList() #没传参,默认生成空链表
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
ll.append(100) #扩充链表,追加元素
print('是空列表吗:', ll.is_empty())
print('列表长度:', ll.length())
#扩充链表,尾插法
ll.append(200)
ll.append(300)
ll.append(400)
#遍历打印整个链表
ll.travel()
#任意位置插入元素
ll.insert(-1, 22)
ll.insert(2, 33)
ll.insert(100, 44)
#遍历打印整个链表
ll.travel()
ll.remove(100)
ll.travel()