目录
一、数据结构概念
- 指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。简单来说,就是设计数据以何种方式组织并存储在计算机中。
- 列表、集合、字典等都是一种数据结构
- 程序=数据结构+算法
- 数据结构按照其逻辑结构分为:
- 线性结构:数据结构中的元素存在一对一的相互关系
- 树结构:数据结构中的元素存在一对多的相互关系
- 图结构:数据结构中的元素存在多对多的相互关系
二、列表
列表是一种基本数据结构(其他语言称数组)
1-数组与列表的不同
a、数组里的元素类型相同,而列表可存不同数据类型
b、数组长度固定:在创建数组时,需要指定数组的大小,一旦创建,数组的大小就是固定的,不能动态改变,而列表可以一直往里面存数据
2-列表中的元素是怎么存储的?
顺序存储,是一块连续的内存,所有节点元素被存放在一块连续的存储区域中,节点的物理位置用来体现节点之间的逻辑关系。
3-列表的基本操作及时间复杂度
- 按下标查找----O(1)
- 添加元素append----O(1)
- 插入元素-----O(n),插入前需要把插入位置的元素往后挪
- 删除元素-----O(n),删除后需要把删除位置的元素往前挪
4-是如何实现不同元素存储的?
列表中存储的不是元素,而是不同元素对应的地址
5-列表长度为什么可变?
列表使用分离式技术的动态顺序表,初始是分配一个存储8个元素的存储区,当存储区容量满时就换一个四倍大的存储区,如果当存储区很大时系统会改变策略增加一倍的存储区。这里的很大目前值是50000
二、栈
2.1 栈的介绍
栈是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表
特点:后进先出
概念:栈顶、栈底
基本操作:
进栈(压栈):push
出栈:pop
取栈顶:gettop
栈的实现:使用一般的列表结构即可实现
进栈:li.append
出栈:li.pop
取栈顶:li[-4]
代码实现如下:
# 栈结构实现
class Stack:
# 初始化一个空列表
def __init__(self):
self.stack = []
# 进栈
def push(self, val):
return self.stack.append(val)
# 出栈
def pop(self):
return self.stack.pop()
# 取栈顶
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
# 判断栈是否为空,为空返回True
def is_empty(self):
return len(self.stack) == 0
2.2 栈的应用-括号匹配问题
给一个字符串,其中包含小括号、中括号、大括号,求该字符串中的括号是否匹配
如:
({}{[()]})-----匹配
({)}[]------不匹配
分析:
遍历字符串:
- 如遇到左括号({[就进栈
- 遇到右括号)}]-----判断栈内是否为空
- 为空返回不匹配
- 不为空-----判断栈顶是否为右括号所对应的左括号
- 是---左括号出栈
- 不是----返回不匹配
- 遍历结束,若栈内为空,返回匹配,否则,返回不匹配
代码:
def bracket_match(s):
stack = Stack() # 实例化栈类
dic = {")": "(", "}": "{", "]": "["}
for ch in str:
if ch in ["(", "{", "["]:
stack.push(ch)
elif ch in [")", "}", "]"]:
if stack.is_empty():
return False
elif stack.get_top() == dic[ch]: # 匹配
stack.pop()
else: # 如果不匹配
return False
if stack.is_empty():
return True
else:
return False
s = "{+-+*/}56[({123})78s]"
print(bracket_match(str))
执行结果:
三、队列(Queue)
3.1 队列介绍
- 队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除
- 进行插入的一端称为队尾(rear),插入动作称为进队或入队
- 进行删除的一端称为队头(front),删除动作称为出队
- 队列的特点:先进先出(First-in,First-out),可以想象成排队
队列的实现:
若用列表实现:
入队:append
出队:若用pop(0),那么每出一个,后面的都要往前挪,复杂度为O(n),不可取
front指针往队尾方向挪,删一个front+1,那么当需要入队时,需要开辟新的空间,会极大浪费前面空间,也不可取,所以列表方式实现队列不可行
环形队列实现:
假设队列尺寸为size,由图可知:
- 队首指针前进1:front=(front+1)% size
- 队尾指针前进1:rear=(rear+1)% size
- 队空条件:rear == front
- 队满条件:(rear+1)% size == front
代码实现如下:
# 队列实现
class Queue:
def __init__(self, size):
self.queue = [0 for _ in range(size)]
self.front = 0 # 初始化队头
self.rear = 0 # 初始化队尾
self.size = size # 初始化队列大小
# 入队
def push(self, val):
if self.is_filled():
raise IndexError("Queue is filled!")
else:
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = val
# 出队
def pop(self):
if self.is_empty():
raise IndexError("Queue is empty!")
else:
self.front = (self.front + 1) % self.size
return self.queue[self.front]
# 判断队空
def is_empty(self):
return self.rear == self.front
# 判断队满
def is_filled(self):
return (self.rear + 1) % self.size == self.front
3.2 队列内置模块
双向队列:队列两端都支持进队和出队
使用方法
from collections import deque
创建队列:queue =deque(seq,maxlen=)
- seq -- 可迭代对象,如列表、字符串、 range() 函数等。
- maxlen -- deque的限制长度
- 两个参数都为可选参数。通常不设定maxlen,但注意当限制长度的deque增加超过限制数的元素时, 另一边的元素会自动删除,返回一个deque序列。
队尾进队----append()
队首出队----popleft()
双向队列队首进队----appendleft()
双向队列队尾出队----pop()
代码示例:
from collections import deque
q1 = deque() # 创建空队列
q2 = deque([1, 2, 3, 4, 5])
q3 = deque("12345")
q4 = deque(range(1, 6))
# 队满队首元素自动出队
q5 = deque(maxlen=3)
for i in range(7):
q5.append(i) # 队尾添加元素
print(q5)
q5.popleft() # 队首弹出元素
print(q5)
# 队满队尾元素自动出队
q6 = deque(maxlen=3)
for i in range(7):
q6.appendleft(i) ##队首添加元素
print(q6)
q6.pop() # 队尾弹出元素
print(q6)
四、栈和队列的应用--迷宫问题
给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
4.1 栈--深度优先搜索
回溯法
思路:
从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。使用栈存储当前路径。
步骤:
1、新建一个空栈,把入口坐标存入
2、当栈非空时,进入循环,找下一个坐标(四个方向遍历找,看哪个方向能走,==0就可以走,走过的点存入栈同时标记为2,防止方向走错回退后再次走到这个点)
3、若没找到方向,说明此路不通,回退,即把刚存入栈的点弹出,直到找到下一个可以走的点
4、当栈顶(即存入的最后一个点)等于出口,打印找到的路径,返回True,退出函数
5、当回退到入口点,即此时栈内无值,说明没有路径可以从入口到出口,返回False,退出函数
代码实现:
# 迷宫问题
# 回溯法
def maze_path(maze, x1, y1, x2, y2): # 传入迷宫,入口,出口
stack = [] # 新建一个栈
original_point = (x1, y1) # 刚开始在入口
stack.append(original_point)
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)] # 四个方向
while len(stack) > 0:
current_point = stack[-1] # 取栈顶
if current_point[0] == x2 and current_point[1] == y2:
for p in stack:
print(p, end=",")
return True
for di in dirs:
next_point = di(current_point[0], current_point[1]) # 得到某个方向前进一格的坐标
if maze[next_point[0]][next_point[1]] == 0:
stack.append(next_point)
maze[next_point[0]][next_point[1]] = 2 # 把这次走过的点标记为2,下次不会再走
break # 方向已经找到,结束此次方向寻找
else: # 如果没找到方向,回退
stack.pop()
else:
return False
maze_1 = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
]
maze_path(maze_1, 1, 0, 9, 8)
打印:
使用栈解决迷宫问题,得到的路径不一定是最短的
4.2 队列-广度优先搜索
思路:
从一个节点开始,寻找所有能走的方向的点,直到找到出口
队列里面保存的永远是方向的最后一个节点
步骤:
入口存入队列queue=[(1,0,-1)],path=[],用来存路径queue中的前两个元素分别表示x、y,第三个元素表示xy是哪个点出来的,第三个元素就是这个点在path中的下标
1、弹出队列首部点(1,0,-1),path把弹出来的点存起来,path=[(1,0,-1)],queue=[],找到(1,0,-1)对应下个点,加入队列尾部queue=[(1,1,0)]
2、弹出队列首部点(1,1,,0),path把弹出来的点存起来,path=[(1,0,-1),(1,1,,0)],queue=[],找到(1,1,,0)对应下个点,加入队列尾部queue=[(2,1,1)]
3、弹出队列首部点(2,1,1),path把弹出来的点存起来,path=[(1,0,-1),(1,1,,0),(2,1,1)],queue=[],找到(2,1,1)对应下个点2个,依次加入队列尾部queue=[(2,2,2),(3,1,2)],此时分了两个方向继续找下一个点
4、弹出队列首部点(2,2,2),path把弹出来的点存起来,path=[(1,0,-1),(1,1,,0),(2,1,1),(2,2,2)],queue=[(3,1,2)],找到(2,2,2)对应下个点,加入队列尾部queue=[(3,1,2),(2,3,3)]
5、 弹出队列首部点(3,1,2),path把弹出来的点存起来,path=[(1,0,-1),(1,1,,0),(2,1,1),(2,2,2),(3,1,2)],queue=[(2,3,3)],找到(3,1,2)对应下个点,依次加入队列尾部queue=[(2,3,3),(4,1,4)]
。。。。。。
- 当某个点弹出后其下一个点不存在,这个方向在queue队列中就不存在了
- 当某个方向先到了出口,停止循环,这样就能找出最短路径了
- 当所有点都弹出了,且没有点进来,说明没有方向可以走了,此时说明无法走出迷宫
代码:
from collections import deque
def maze_queue_path(maze, x1, y1, x2, y2):
# 定义空队列,队列里存的是三维列表,一个是迷宫坐标点x,y,最后一个是这个点是从哪个点而来的,方便后续回溯路径
queue = deque()
queue.append((x1, y1, -1)) # 把入口存入队列尾部,且定义其是从下标为-1而来的
path = []
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)] # 四个方向
while len(queue) > 0:
current_point = queue.popleft() # 把队列首部的点弹出,也就是当前所处的点
path.append(current_point) # 把这个点存到路径列表里
if current_point[0] == x2 and current_point[1] == y2: # 当前点等于出口,输出路径
real_path = [path[len(path) - 1][0:2]]
n = path[len(path) - 1][2] # 得到path中最后一个点对应的上个点的下标
while n != -1: # 直到n=-1时,说明已经遍历到了第一个点,结束循环
real_path.append(path[n][0:2])
n = path[n][2]
real_path.reverse()
for line in real_path:
print(line)
return True
for di in dirs:
next_point = di(current_point[0], current_point[1]) # 寻找下个方向的点
if maze[next_point[0]][next_point[1]] == 0:
queue.append((next_point[0], next_point[1],
len(path) - 1)) # 如果这个方向可以走,就把它放到队列尾部,且它是从path的最后一个点而来的,通过记录最后一个点的下标就可以回溯到前一个点
maze[next_point[0]][next_point[1]] = 2 # 同时把这个点置为2,下次就不会再走了
# 没有break是因为找到一个方向会继续寻找下一个方向,而不是找到一个就停止
else: # 队列里的值都弹出了,并且都没找到可以走的方向,说明没路了
print("没找到")
return False
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
]
maze_queue_path(maze, 1, 0, 9, 8)
结果打印: