python数据结构:(其中的栈,队列,链表,二叉树)
1栈:
1.1概念:
1.使用队列来表示
2.后进先出
3.只是用了append和pop操作,进栈,出栈
1.2简单代码封装(非必要,直接用列表就行了,做理解)
``
class Stack(object):
#初始化属性为一个列表
def __init__(self):
self.stack = []
#入栈
def push(self,element):
self.stack.append(element)
#出栈
def pop(self):
return self.stack.pop()
#取栈顶元素
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
2队列:
2.1性质:
1.有队头和队尾
2.只允许一端插入(队尾),一端输出(队头)
3.先进先出
2.2队列实现的算法:
1.因为为了防止出队造成空间的浪费一般用环形队列,使得首尾相连
2.当队尾指针rear = 环形队尾的最后一个空间 + 1的时候,再进一个位置为0
3.队首指针前进1:front = (front + 1) % MaxSize(取余就是元素的小标)
4.队尾指针前进1 : rear=(rear +1) % MaxSize
5.队满条件:(rear+1) % 12 == front ==>即满足了rear在front时候的堆满,也满足了front在最后环形队列的队头时,rear在环形队列队尾时的队满情况,同时牺牲一个空间front指向的空间不存东西
6.队空条件:rear == front
2.3队列实现代码:(有内置函数,这里做理解)
``
class Queue(object):
def __init__(self,size = 100):
self.queue = [0 for _ in range(size)] #初始化队列的长度为100
self.front = 0 #队首指针
self.rear = 0 #队尾指针
self.size = size #d队的长度
#进队
def puch(self,element):
if self.is_filled() == False:
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] == element
else:
return '队已经满了'
#出队
def pop(self):
if self.is_empty() == False:
self.front = (self.front +1) % self.size
return self.queue[self.front]
else:
return '队已经空了'
#队满条件:
def is_filled(self):
return (self.rear + 1) % self.size == self.front
#队空条件:
def is_empty(self):
return self.front == self.rear
2.4内置模块 == > deque
1.导入模块from collections import deque
2.创建队列:queue = deque()
3.进队:append()
4.出队:popleft()
5.双向队列队首进队:appendleft()
6.双向队列队尾出队:pop()
1+2经典问题迷宫
1第一种用栈的方法 --- 深度优先搜索
1.也叫回溯法
2.思路:从开始节点开始,任意找一个可以走的节点,当到不能走的节点的时候,就退回到上一个还可以走的节点
3.用栈来存储路径
``
def lujing(x1,y1,x2,y2,tu):
'''
路径问题
:param x1:起点的横坐标
:param y1: 起点的纵坐标
:param x2: 终点的横坐标
:param y2: 终点的纵坐标
:return:
'''
# 封装一个列表,用来表示能走的方向,以及顺序
fangxiang = [
lambda x, y: (x + 1, y), # 向下做
lambda x, y: (x, y - 1), # 向左走
lambda x, y: (x - 1, y), # 向上走
lambda x, y: (x, y + 1), # 向右走
]
list1 = [] #存储路径的栈
list1.append((x1, y1)) #1.先将起点存到栈里面
while (len(list1) > 0): #2.如果栈空了则表示没有路可以走
nowdate = list1[-1] # 3.当前节点
if nowdate == (x2,y2): # 14如果当前节点时终点那么就返回这个栈列表
for i in list1:
print(i)
return '找到了'
for fx in fangxiang: #4.开始选一个方向来走
nextdate = fx(nowdate[0], nowdate[1]) #5.下一步要走的坐标
if tu[nextdate[0]][nextdate[1]] == 0: #6.表示找到一个方向可以走,
list1.append(nextdate) #7.将这个点加入到栈中
tu[nextdate[0]][nextdate[1]] = 2 #8.同时标记这个点为已经走过的
break #9.一旦找到可以走的就退出方向搜索
else: #10.表示四个方向都不可以走了,也就是死胡同了
#tu[nextdate[0]][nextdate[1]] = 2 #11.同时也要把这个点标记为已经走过的,感觉没必要所以注销了
list1.pop() #12.就回溯出栈
else: #13.表示栈已经空了,没有路径可以出去
return '无路可走'
2第二种方法用队列 == > 广度优先搜索
1.思想:(不好表达)
1.先从入口进入将开始这个节点的数据存到队列中,
比如开始节点为a1,存到列表中
2.然后将可以走的所有方向的节点存到队列中,同时该节点出队,然后记录,
比如记录a1,可以走的节点a2,a3,记录到列表中
3.然后队列中的各个节点分别开始走可以走的方向,但是不能走以及走过的节点,
比如先走a2上的节点a4,a5,然后将a4,a5,记录到列表最后,同时a2出队
然后走a3后面可以走的方向,a6,a7等,记录到列表最后,同时a3出队
4.同时记录可以走的所有方向上的节点记录到队列中,并且同时出队该节点,依次类推,
比如现在列表有a4,a5,a6,a7,而着两个都是上一次节点走一步所产生的所有可走的节点
然后按顺序同3一样出队,入队
5.直到走到了终点则停止,然后回溯,找到最短路径
就是列表中一但有终点节点出现,就停止
总:会发现该列表中只是存放了队列中所有可以走的节点,并不能找到路径,那么怎么办呢?
解决:
就是用一个列表的方式来存储每次出队的节点,并且将该节点在列表的位置,告知给该节点的下一个节点,所以队列中的每一个节点的存储方式为
(该节点的横坐标,该节点的纵坐标,上一个节点在存储出队列表的位置)
2.算法实现
``
def zuiduanluji(tu,x1,y1,x2,y2):
'''
:param tu: 传入要找路径的图
:param x1:
:param y1:
:param x2:
:param y2:
:return:
'''
# 封装一个列表,用来表示能走的方向,以及顺序
fangxiang = [
lambda x, y: (x + 1, y), # 向下做
lambda x, y: (x, y - 1), # 向左走
lambda x, y: (x - 1, y), # 向上走
lambda x, y: (x, y + 1), # 向右走
]
#这里需要使用到队列的模块
from collections import deque
#创建一个队列
dl = deque()
dl.append((x1,y1,-1)) #先将第一个节点,入口入队,因为是开始节点所以没有上一个节点让他入队的,所以为-1
list1 = [] #用来存储出队的节点
while len(dl) > 0: #如果小于0了,表示没有路可以走
nowdate = dl.popleft() # 标记当前节点,同时出队
list1.append(nowdate) #将出队的节点记录到list1中
if (nowdate[0],nowdate[1]) == (x2,y2): #表示到终点了
path = [] #用来记录路径
list1.append(nowdate) #因为出队了也要将这个终点记录到出队的队列中
while nowdate[2] != -1: #如果出队节点列表的某一个节点的前一个节点不是初始节点,就记录到路径中
path.append((nowdate[0],nowdate[1]))
nowdate = list1[nowdate[2]] #将该节点的上一个节点,赋值为该节点
path.append((x1,y1)) #将初始节点记录到路径列表中,
path.reverse() #将路径变为从头到尾,以为加入的时候是从尾到头
for i in path: #打印输出路径
print(i)
print('有出路')
return True
for fx in fangxiang:
nextdate = fx(nowdate[0],nowdate[1]) #记录下一个节点的坐标
if tu[nextdate[0]][nextdate[1]] == 0:
dl.append((nextdate[0], nextdate[1],len(list1) - 1)) #可以走就进队,同时记录那个节点让他入队的
tu[nextdate[0]][nextdate[1]] = 2 #还要标记这个点是走过的
else: #正常退出循环表示队列都出队到空了,就是没有路了
print('无路可走')
return False
3.链表
3.1概念:
链表就是由一系列节点组成的元素集合,每一个元素由两个部分组成,数据域和指向下一个节点的指针域组成,通过节点之间的相互连接,最终串联成一个链表
每个节点的结构代码代码如下:
``
class Node(object):
def __init__(self,item):
self.item = item
self.next = None
3.2创建链表:
1.头插法:
1.创建一个指针为头指针,使得头指针指向第一个节点
2.创建节点,让节点的指针域指向头指针
3.然后头指针指向新创建的头节点
代码实现:
``
def create_linklist(li):
'''
:param li: 表示传入的列表
:return: 返回头指针
'''
head = Node(li[0]) #头指针指向第一个节点
for i in li[1:]:
node = Node[i] #创建一个节点
node.next = head #将该节点插到头指针指向节点的前面
head = node #头指针指向该节点
return head #返回头指针,使得可以用头指针来遍历这个链表
2.尾插法:
1.创建头指针和尾指针,头指针和尾指针都同时指向第一个节点
2.创建一个节点,存储到尾指针指向的节点的后面,
3.而后尾指针指向该新创建的节点
代码实现
``
def create_linklist_tail(li):
'''
:param li: 传入一个列表
:return: 返回头指针
'''
head = Node(li[0]) #创建一个头指针,同时也创建了第一个节点,并且头指针指向第一个节点
tail = head # 尾指针和头指针相等都指向第一个节点
for i in li[1:]:
node = Node(i) #创建一个新节点
node.next = tail #新节点指向尾节点指向的节点
tail = node # 尾节点指向新创建的节点
return head
3.3链表的相关操作:
3.3.1遍历:
1.如果头节点不为空
2.输出头节点所指向节点的数据域
3.然后头节点指向头节点的下一个节点
``
def print_linklist(head):
while head != None:
print(head.item)
head = head.next
3.3.2插入:
1.先遍历链表找到想插入的位置的前一个节点
2.然后将该节点指向前一节点的下一个节点
3.最后将前一节点指向该节点
3.3.3删除:
1.先遍历找到想删除的节点
2.然后找到该节点的前一个节点,将前一个节点指向该节点的下一个节点
3.最后删除该节点
3.4双向链表:
就是比一般的链表多出了一个指向上一个节点的指针域而已
4.树:
4.1.概念:
1.树是一种数据结构,同时也像是我们文件的目录结构
2.树是一种可以递归定义的数据结构
3.树是由n个节点组成的集合
1.如果n为零,则表示该树是一个空树
2.如果n不为零,那么就存在一个节点作为树的根节点,其他节点可以分为m个集合,每个集合又构成一个树
4.2.树中的一些名词:
1.根节点:最开始的那个节点
2.叶子节点:它后面没有子节点的节点
3.树的深度:就是从上到下又多少层
4.树的度:就是找到这棵树中一个分叉最多的节点,那么树的度就是这个节点分几个岔就是几度
5.孩子节点/父节点
6.子树
树中节点的存储的代码结构:
``
class Node(object):
#初始化可以传入节点的名字,节点的类型等
def __init__(self,name,type):
self.name = name
self.type = type
self.childrennode = [] #该节点的子节点,因为是树所以用列表来存储他的子节点
self.father = None #该节点的父亲节点,默认为None(可能是祖先嘛),父亲节点只有一个注意啊,不能又多个父亲哈
#通过链式存储的数据结构
4.3.二叉树:
4.3.1:概念
1.二叉树的度就为二
2.二叉树每个节点最多只有两个子节点,分别叫左孩子和右孩子
4.3.2存储方式:
二叉树通过的也是链式存储,将二叉树的每一个节点看出一个对象,节点与节点之间通过链接的方式来连接
代码实现节点对象
``
class Node(object):
#初始化传入节点的值
def __init__(self,date):
self,date = date
#因为二叉树只有左孩子和右孩子,所以他的子节点只有两个
self.leftchild = Node
self.rightchild = Node
4.3.3遍历
4.3.3.1前序遍历:
1.从上到下
2.先访问根节点,输出根节点
3.然后访问左子树
4.然后再访问左子树的左子树,一直到左子树都访问完了,才开始访问右子树,而访问右子树是从访问完最后左子树的父节点下的又子树开始
5.然后还要看该右子树又没有左子树了,有就重复4,然后才开始访问右边,层层往上
总的来说就是再左边的先看,右边的最后看
以上说的可能不清楚,重要看代码(代码可能你也不看明白哈哈哈,当然啦开个玩笑,我相信你的才智)
``
#传入的是根节点
def qianxubianli(rootnode):
if rootnode:
print(rootnode.date) #输出根节点的值
qianxubianli(rootnode.leftchild) #用递归的方式访问左子树
qianxubianli(rootnode.rightchild) #用递归的方式访问又子树
#注意每次访问完,rootnode都变成了左或右子树的根节点了
4.3.3.2中序遍历
1.先遍历左子树的节点
2.然后访问自己,并输出
3.然后访问右子树节点
4.访问左子树时也是先访问左子树的左子树,然后再访问左子树的根节点并输出,然后访问左子树的右子树,一直下去
5.访问右子树时也一样先访问右子树的左子树,然后再访问右子树的根节点并输出,然后访问右子树的右子树,一直下去
``
def zhongxubianli(rootnode):
if rootnode:
qianxubianli(rootnode.leftchild) #用递归的方式访问左子树
print(rootnode.date) # 输出根节点的值
qianxubianli(rootnode.rightchild) #用递归的方式访问又子树
#注意每次访问完,rootnode都变成了左或右子树的根节点了
4.3.3.3后序遍历
1.先遍历左子树的节点
2.然后访问右子树节点
3.然后访问自己,并输出
4.访问左子树时也是先访问左子树的左子树,然后访问左子树的右子树,最后访问左子树的根节点并输出,一直下去
5.访问右子树时也一样先访问右子树的左子树,,然后访问右子树的右子树,最后访问右子树的根节点并输出,一直下去
``
def houxubianli(rootnode):
if rootnode:
qianxubianli(rootnode.leftchild) #用递归的方式访问左子树
qianxubianli(rootnode.rightchild) #用递归的方式访问又子树
print(rootnode.date) # 输出根节点的值
#注意每次访问完,rootnode都变成了左或右子树的根节点了
4.3.3.4层次遍历
用到的时队列
1.先根节点入队
2.然后根节点出队,左孩子和右孩子按次序入队
3.然后左孩子出队,左孩子的左孩子和右孩子入队
4.然后上一次的右孩子出队,然后右孩子的左孩子和右孩子入队
5.一直循环2,3,4,直到访问完二叉树,同时队列为空
``
from collections import deque
def cengcibianli(rootnode):
dq = deque() #创建一个队列
dq.append(rootnode) #先根节点入队
while len(dq) > 0: #只要队不空就一直访问
gennode = dq.popleft() #出队
print(gennode.date) #出队输出节点的值
if gennode.leftchild: #先判断左孩子有没有,有就入队
dq.append(gennode.leftchild) #左节点入队
if gennode.rightchild: #先判断右孩子有没有,有就入队
dq.append(gennode.rightchild) #右节点入队
4.4二叉搜索树:
4.4.1概念:
形状和二叉树一样,但是又一条规矩就是左子树的值都要比根节点要小,右子树的值都要比根节点的值要大
个人比较不喜欢递归而且个人对递归有点难以理解,而且递归可能运行速度慢,所以一下二叉搜索树的算法我都用非递归来写(虽然递归可以省代码,但我就是不写嘿嘿)
4.4.2插入:
``
#二叉树中的节点结构
class Node(object):
#初始化传入节点的值
def __init__(self,date):
self,date = date
#因为二叉树只有左孩子和右孩子,所以他的子节点只有两个
self.leftchild = Node
self.rightchild = Node
self.parent = Node
#一个二叉树的类
class erchashu():
#初始化该二叉树的根节点时空的
def __init__(self):
self.rootnode == None
#插入方法
def insertnode(self, val):
p = self.rootnode #用一个指针指向根节点
if p == None: #如果根节点为空
self.rootnode = Node(val) #直接将这个点变成根节点
return
while True: #否则循环查找
if p.date > val: #如果这个值小于当前根节点的值
if p.leftchild: #当前根节点的左孩子不为空
p = p.leftchild #则指针指向当前节点的左孩子
else: #如果为空则将这个值变成一个节点,放到当前根节点的左边
p.leftchild = Node(val)
p.leftchild.parent = p #并且将新连接的节点的父节点指针指向当前根节点
return
elif p.date < val: #如果这个值大于当前根节点的值
if p.rightchild: #而且当前根节点的右孩子不为空
p = p.rightchild #则指针指向当前节点的右孩子
else: #如果为空则将这个值变成一个节点,放到当前根节点的右边
p.rightchild = Node(val)
p.rightchild.parent = p #并且将新连接的节点的父节点指针指向当前根节点
return
else: #就是说这个像插入的值等于根节点,那么就什么都不做嘛,因为我默认二叉树时没有重复值的
return
4.2.3查询:
``
#查询
def chaxun(self,val):
p = self.rootnode #同样那个指针指向根节点
while p: #当p不为空
if p.date > val: #如果该节点的值大于传入的值,则指针指向左孩子
p = p.leftchild
elif p.date < val: #如果该节点的值小于传入的值,则指针指向左孩子
p = p.rightchild
elif p.date == val: #如果该节点的值等于传入的值,则返回
return p
else: #否则就指针找不到
print('没有该节点')
return None
4.2.4删除:
删除就比较复杂了又三种情况:
1.删除的节点是叶子节点:那好办,直接找到删除就可以了
2.删除的节点只有一个孩子:要删除的节点的父亲以要删除节点的孩子向连接就可以了
3.删除的节点又俩个孩子:那么就将其右子树中最小的节点删除掉,并把删除掉的孩子放到要删除节点的位置
代码实现
``
'''
删除的情况
'''
#删除情况的第一种删除的节点是叶子节点
def remove_node_1(self, node):
if node.parent == Node: #判读该节点是不是根节点,是就将根节点指向空
self.rootnode = None
if node == node.parent.leftchild: #如果该节点是其父节点的左孩子
node.parent.leftchild = None #他父亲的左孩子指向空
node.parent = None #他的父节点指针指向空
else: #如果该节点是其父节点的右孩子
node.parent.rightchild = None #他父亲的右孩子指向空
node.parent = None #他的父节点指针指向空
#删除情况的第二种删除的节点有一个孩子而且是左孩子
def remove_node_2_1(self, node):
if node.parent == Node: # 判读是不是根节点,
self.rootnode = node.leftchild #如果是那么就将根节点指向该节点的左孩子
node.leftchild.parent = None #该节点左孩子的父节点的指针指向空
if node == node.parent.leftchild: #如果该节点是其父节点的左孩子
node.parent.leftchild = node.leftchild #那么该节点的父亲节点的左孩子指向该节点左孩子
node.leftchild.parent = node.parent #该节点的左孩子的父亲节点指针指向该节点的父亲节点
else:
node.parent.rightchild = node.leftchild #如果该节点是其父节点的右孩子,那么该节点的父亲节点的右孩子指向该节点左孩子
node.leftchild.parent = node.parent #那么该节点的父亲节点的左孩子指向该节点左孩子
#删除情况的第二种中的另一个就是删除的节点的有一个孩子而且是右孩子,与上面的相同就是左右变了
def remove_node_2_2(self, node):
if node.parent == Node: # 判读是不是根节点,
self.rootnode = node.rightchild #如果是那么就将根节点指向该节点的左孩子
node.rightchild.parent = None # 该节点的右孩子父节点的指针指向空
if node == node.parent.leftchild:
node.parent.leftchild = node.rightchild
node.rightchild.parent = node.parent
else:
node.parent.rightchild = node.rightchild
node.rightchild.parent = node.parent
#删除情况的第三种
def shanchu(self, val): #传入一个值表示要删除的节点
node = self.chaxun(val) #寻找该值对应的节点
if node == Node: #如果发现没找到则直接返回flase
print('没有该节点无法删除')
return False
# 如果找到发现该节点是一个叶子节点,调用删除情况1
elif node.leftchild == None and node.rightchild == None:
self.remove_node_1(node)
return True
# 如果找到发现该节点有只左孩子,调用删除情况2_1
elif node.rightchild == None:
self.remove_node_2_1(node)
return True
#如果找到发现该节点有只有右孩子,调用删除情况2_2
elif node.leftchild == None:
self.remove_node_2_2(node)
return True
#如果找到并发现该节点有左孩子也有有孩子
else:
#用一个指针指向该节点的右孩子,并用循环遭到该节点右孩子的左孩子中最小的树
min_node = node.rightchild
while min_node.leftchild != None:
min_node = min_node.leftchild
#将要删除节点的值变为找到最小节点的值,想当于连个节点替换
node.date = min_node.date
#然后就变成了删除的是找到的那个最小节点
if min_node.rightchild == None and min_node.leftchild == None:
self.remove_node_1(min_node)
if min_node.leftchild == None:
self.remove_node_2_2(min_node)
return True