创作不易,来了的客官点点关注,收藏,订阅一键三连❤😜
前言
程序=数据结构+算法,算法是数学理论和工程实现的杂糅,是一个十分有趣神奇的学问。搞懂算法用另一种视角看编程,又会是一种全新的感受,如果你也在学习算法,不妨跟主任萌新超差一起学习,拿下算法!
系列文章目录
python每日算法 | 基数排序PK快速排序,手撕基数排序算法!
python每日算法 | 图文+生动实例详解桶排序
python每日算法 | 揭开计数排序的秘密
概述
本期的内容将介绍数据结构基础知识(一):栈与队列以及最经典的迷宫问题。
目录
前言
系列文章目录
概述
超超python每日算法思维导图
数据结构基础知识
数据结构是什么?
数据结构的逻辑结构
线性结构
列表
栈
栈是什么
栈的基本操作
用python实现栈
栈的应用:括号匹配问题
队列
队列是什么
双向队列
队列的实现方式:环形队列
用python实现队列
python队列内置模块:deque
队列的应用:使用deque实现Linux的tail命令
迷宫问题
栈:深度优先搜索
代码实现
队列:广度优先搜索
代码实现
超超python每日算法思维导图
数据结构基础知识
数据结构是什么?
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中。
比如:列表、集合与字典等都是一种数据结构。
N.Wirth说过: “程序=数据结构+算法”
数据结构的逻辑结构
数据结构按照其逻辑结构可分为线性结构、树结构、图结构。
线性结构
线性结构:数据结构中的元素存在一对一的相互关系,例如列表。
树结构:数据结构中的元素存在一对多的相互关系,例如堆。
图结构:数据结构中的元素存在多对多的相互关系
列表
列表(python中叫列表,其他语言称数组)是一种基本数据类型。
关于列表的问题:
列表中的元素是如何存储的?
32位机器上一个整数占4个字节,一个地址占4个字节,64位一个地址占8个字节。
现有如下列表:
1 | 2 | 3 | 4 | 5 |
以32位机器为例,数组的存储是这样的:
若元素1的内存地址位100,则元素2的内存地址为104,以此类推,那么a[2]=100+2*4=108。
而对于列表,若1的地址为200,2-->300,3--88,列表是现通过查找地址,然后通过一个地址查找对应一个元素。
200 | 300 | 88 |
数组和列表的区别:
1.数组的元素类型需要一致,列表的元素类型不需要一致;
2.数组的长度是固定的,python的列表可以无限长。
列表的基本操作:插入元素、删除元素……这些操作的时间复杂度是多少?
插入(insert):O(n),每次插入都将把前后的元素移动
删除(pop):O(n),每次删除之后会把后面的地址往前移
栈
栈是什么
栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。
栈的特点:后进先出LIFO(last-in, first-out)
例如图中的一摞书,最后一本数是最先进来的,同时也是最先出去的。
栈的概念:栈顶、栈底
栈的基本操作
进栈(压栈):push,(在一摞书上再放一本书)
出栈:pop(将一摞书最上面的书拿走)
取栈顶:gettop(我只看最上面的书是什么)
用python实现栈
栈的实现:使用一般的列表结构即可实现栈。
进栈:lst.append()
出栈:lst.pop()
取栈顶:lst[-1]
class stack:
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
stack = stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
print(stack.pop())
print(stack)
print(stack.get_top())
# 输出结果:
# 4
# <__main__.stack object at 0x000001CC63BBEFD0>
# 3
栈的应用:括号匹配问题
括号匹配问题:给一个字符串,其中包含小括号、中括号、大括号,求该字符串中的括号是否匹配。
例如:
()()[]{}-->匹配
([{()}])-->匹配
[](-->不匹配
[(])-->不匹配
# 括号不匹配问题
"""
例如:()[][{()}]
1.栈顶"(",与接下来的")"与栈顶匹配,栈顶出栈;
2.接下来栈顶"[",与"]"匹配,"["出栈
3.栈顶:"[",接下里"{"进栈,紧接着"("进栈,")"进栈并于"C"栈顶匹配,因此栈顶出栈
4.接下来"}"与栈顶"{"匹配,栈顶出栈
5."]"进栈,与栈顶"["匹配,栈顶出栈
"""
def brace_match(str1):
match_dict = {')':'(','}':'{',']':'['} # 建立匹配机制
stack = Stack()
for i in str1:
if i in {'(','[','{'}: # 左部分进栈
stack.push(i)
else: # i in {')','}',']'}
if stack.is_empty():
return False
elif stack.get_top() == match_dict[i]:
stack.pop()
else: # 进栈字符与栈顶不匹配
return False
if stack.is_empty:
return True
else:
return False
print(brace_match("{{[()]}}"))
# 输出:
# True
print(brace_match("{{[()]{()}}}"))
# 输出:
# True
print(brace_match("{[)]{()}}"))
# 输出
# False
队列
队列是什么
队列(Queue):队列是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。
进行插入的一端称为队尾(rear),插入动作称为进队或入队
进行删除的一端称为队头(front),删除动作称为出队
队列的性质:先进先出(First-in, First-out)
双向队列
双向队列的两端都支持进队和出队操作,双向队列的基本操作:
队首进队
队首出队
队尾进队
队尾出队
队列的实现方式:环形队列
如图所示位环形队列的出队进队过程,rear=front是队空。
1.A进队,rear-->+1
2.BCDEFGH进队, rear依次加1
3.ABCD出队,front-->5,队首-->F,队尾-->H
4.IJKM进队-->near以此加1
5.当P进队后,发生队满,(为什么rear和front没有重合?为了确保是队空还是队满。)
因此队满-->rear + 1= front, 对空:rear = front
环形队列:当队尾指针front == Maxsize(队列的长度) - 1时,再前进一个位置就自动到0.
队首指针前进1:front = (front + 1) % MaxSize
队尾指针前进1:rear = (rear + 1) % MaxSize
队空条件:rear == front
队满条件:(rear + 1) % MaxSize == front
用python实现队列
class Queue:
def __init__(self,size=100): #队列长度为100
self.size = size
self.queue = [0 for i in range(size)]
self.rear = 0 # 队尾指针
self.front = 0 # 对首指针
def push(self,element): # 进队
if not self.is_filled(): # 确保队不满
self.rear = (self.rear+1) % self.size
self.queue[self.rear] = element
else:
raise IndexError("Queue is filled")
def pop(self): # 出队
if not self.is_empty(): # 确保队不空
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise IndexError("Queue is empty.")
def is_empty(self): # 判断队空
return self.rear == self.front
def is_filled(self): # 判断队满
return (self.rear + 1) % self.size == self.front
q = Queue(5)
for i in range(4):
q.push(i)
print(q.pop())
q.push(13)
print(q.pop())
q2 = Queue(5)
for i in range(5):
q.push(i)
print(q.is_filled())
# 输出结果
# 0
# 1
# IndexError: Queue is filled
python队列内置模块:deque
队列的内置模块:deque
from collections import deque
q = deque()
q.append(1) # 队尾进队
print(q.popleft()) # 队首出队
# 输出结果
# 1
# print(q.popleft()) # 队空情况
# 输出结果
# IndexError: pop from an empty deque
q = deque([1,2,3,4) # 长度为4的队列
q.append(5) # 队尾进队
print(q)
print(q.popleft()) # 队首出队
# 输出结果
# deque([1, 2, 3, 4, 5], maxlen=5)
# 1
# 用于双向队列
# q.appendleft() # 队首进队
# q.pop() # 队尾出队
队列的应用:使用deque实现Linux的tail命令
test:
“
cc
1314
hi
I am chaochao
follow chaochao,day day up together
“
def tail(n):
with open("test",'r') as f:
q = deque(f,n)
return q
# print(tail(1))
# 输出结果
# deque(['chaochao'], maxlen=1)
for line in tail(3):
print(line,end="")
# 输出结果
# hi
# I am chaochao
# follow chaochao,day day up together
迷宫问题
给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
maze = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
栈:深度优先搜索
回溯法思路:(一条路走到黑)从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。使用栈存储当前路径。
代码实现
maze = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
# 四个方向的表示
dir_lst = [
lambda x,y:(x+1,y),
lambda x,y:(x-1,y),
lambda x,y:(x,y-1),
lambda x,y:(x,y+1)
]
def maze_path(x1,y1,x2,y2):
stack = []
stack.append((x1,y1)) # 初始位置,是一个二元组
while(len(stack)>0): # 当栈不空时循环,即有路可走
curNode = stack[-1] # 当前坐标,列表形式
# 判断是否已经走到了终点
if curNode[0] == x2 and curNode[1]== y2:
for i in stack:
print(i)
return True
"""
四个方向表示:
左-->(x-1,y),右-->(x+1,y),上-->(x,y-1),下-->(x,y+1)
"""
for dir in dir_lst:
nextNode = dir(curNode[0],curNode[1])
# 如果下一个点能走
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
# 标记走过的点,如 此值为2代表已经走过
maze[nextNode[0]][nextNode[1]] = 2
break # 找到可以走的点就break
else:
maze[nextNode[0]][nextNode[1]] = 2
stack.pop() # 栈顶出栈,回溯随后循环找可以走的点
else:
print("无路可走")
return False
# 验证
maze_path(1,1,8,8)
# 输出结果
# (1, 1)
# (2, 1)
# (3, 1)
# (4, 1)
# (5, 1)
# (5, 2)
# (5, 3)
# (6, 3)
# (6, 4)
# (6, 5)
# (7, 5)
# (8, 5)
# (8, 6)
# (8, 7)
# (8, 8)
队列:广度优先搜索
思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口。使用队列存储当前正在考虑的节点。队列存储的是当前的点。
对列如下存储:
当前位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
前一个点位置 | -1(默认) | 0 | 1 | 2(3让它来) | 2(3让它来) | 3(4让它来的) | 4(5让他来的) |
代码实现
from collections import deque
maze2 = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
dirs = [
lambda x,y:(x+1,y),
lambda x,y:(x-1,y),
lambda x,y:(x,y-1),
lambda x,y:(x,y+1)
]
# 达到终点时输出路径
def print_r(path):
curNode = path[-1] # 最后一个节点
realpath = [] # 路径
while curNode[2] != -1: # 即上一个节点部署终点时
realpath.append(curNode[0:2])
curNode = path[curNode[2]]
realpath.append(curNode[0:2]) # 起点
realpath.reverse() # 倒叙列表
for i in realpath:
print(i)
def maze_path_queue(x1,y1,x2,y2):
queue = deque()
queue.append((x1,y1,-1))
path = [] # 出队的节点放到path列表
while len(queue) > 0: # 队不空时循环
curNode = queue.popleft()
path.append(curNode)
if curNode[0] == x2 and curNode[1] == y2: # 到达终点
print_r(path) # 到达终点,并输出路径
return True
for dir in dirs:
nextNode = dir(curNode[0],curNode[1])
if maze2[nextNode[0]][nextNode[1]] == 0:
queue.append((nextNode[0],nextNode[1],len(path[-1])-1)) # 后续节点进队,记录哪个节点带她来
maze2[nextNode[0]][nextNode[1]] = 2 # 标记此点已经走过
else:
print("无路可走")
return False
maze_path_queue(1,1,6,8)
创作不易,客官点个赞,评论一下吧!超超和你一起加油❤😜