人工智能3.3 -- python算法篇(三)链表

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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值