文章目录
一、数据结构
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中元素之间的关系组成。
简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中。
比如:列表、集合与字典等都是一种数据结构。
N.Wirth: “程序=数据结构+算法”
1.数据结构分类
数据结构按照其逻辑结构可分为线性结构、树结构、图结构
线性结构:数据结构中的元素存在一对一的相互关系
树结构:数据结构中的元素存在一对多的相互关系
图结构:数据结构中的元素存在多对多的相互关系
2.列表/数组
列表(其他语言称数组)是一种基本数据类型。
关于列表的问题:
Q1:表中的元素是如何存储的?
A1:线性存储。在内存中,数组存储的元素的值,而列表存储的是列表元素的地址。
Q2:列表的基本操作:按下标查找、插入元素、删除元素……这些操作的时间复杂度是多少?
A2:
插入元素的内置方法:insert
删除元素的内置方法:pop、remove
若pop删除的是最后一个元素,则O(1)
否则为O(n),n为列表长度
插入和删除后,原列表需要进行重新的复位,需要花费时间,所以时间复杂度为O(n)
扩展:Python的列表是如何实现的?
数组和列表的区别:
1.数组元素类型都一致
2.数组大小固定
列表中存储的是列表元素的地址,因为地址在计算机中长度固定(1个16进制数)所以在查找元素的时候可以按照:起始地址+n*地址长度这个公式来找,对于数组,找到元素就停止;对于列表,需要拿着地址找到元素。
二、栈
栈(stack)是一个数据集合,可以理解为只能在一端进行插入或者删除操作的列表。
栈的特点:后进先出 LIFO(last-in, first-out)
栈的概念:栈顶、栈底
栈的基本操作:
进栈(压栈):push
出栈:pop
取栈顶:gettop
1.栈的实现
使用一般的列表结构即可实现栈
进栈:li.append
出栈:li.pop
取栈顶:li[-1]
也可以封装为一个栈类
python
class Stack:
#后进先出
def __init__(self):
self.stack = []
#进栈
def push(self,element):
self.stack.append(element)
#出栈
def pop(self):
if not self.is_empty:
self.stack.pop()
else:
print('栈空!')
#查看栈顶
def gettop(self):
if not self.is_empty:
return self.stack[-1]
else:
print('栈空!')
#判断栈是否为空
@property
def is_empty(self):
return len(self.stack) == 0
2.小应用:括号匹配问题
括号匹配问题:给一个字符串,其中包含小括号、中括号、大括号,求该字符串中的括号是否匹配。
例如:
()()[]{} 匹配
([{()}]) 匹配
[]( 不匹配
[(]) 不匹配
思路分析:
括号匹配是以左括号开始,右括号结束(右括号开始的话直接认为不匹配);若为左括号,则进栈,若为右括号则出栈,操作的均为左括号;最终判断栈若为空则匹配成功。
def brace_math(s):
match = {
'}': '{', ']': "[", ')': '('}
stack = Stack()
for ch in s:
if ch in ['(','<','{','[']:
stack.push(ch)
else:
if stack.is_empty:
return False
elif stack.gettop() == match[ch]:
stack.pop()
else:
return False
if not stack.is_empty:
return False
else:
return True
三、队列
队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。
进行插入的一端称为队尾(rear),插入动作称为进队或入队
进行删除的一端称为队头(front),删除动作称为出队
队列的性质:先进先出(First-in, First-out)
队列不能用列表简单实现:对于出队,需要涉及到列表复位,时间复杂度比较高。
1.队列的实现
front是队首,rear是队尾
对于11变为0:实际上是对两个指针进行取除12余数的操作
环形队列:当队尾指针rear == Maxsize - 1时,再前进一个位置就自动到0.
队首指针前进1:front = (front + 1) % MaxSize
队尾指针前进1:rear = (rear + 1) % MaxSize
队空条件:rear == front
队满条件:(rear + 1) % MaxSize == front
实现方式一:
封装一个队列类
class Queue:
def __init__(self, size=100):
self.queue = [0 for _ in range(size)]
self.size = 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
利用python内置模块
from collections import deque
q = deque([1,2,3,4,5], 5)
q.append(6) # 队尾进队
print(q.popleft()) # 队首出队
# 用于双向队列
q.appendleft(1) # 队首进队
q.pop() # 队尾出队
2.双向队列
双向队列的两端都支持进队和出队操作
双向队列的基本操作:
队首进队
队首出队
队尾进队
队尾出队
四、迷宫问题
给一个二维列表,表示迷宫(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]]
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]
]
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 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 p in stack:
print(p)
return True
# x,y 四个方向 x-1,y; x+1,y; x,y-1; x,y+1
for dir in dirs:
nextNode = dir(curNode[0], curNode[1])
# 如果下一个节点能走
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] = 2 # 2表示为已经走过
break
else:
maze[curNode[0]][curNode[1]] = 2
stack.pop()
else:
print("没有路")
return False
maze_path(1,1,8,8)
2.队列——广度优先搜索
思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口。
使用队列存储当前正在考虑的节点
from collections import deque
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]
]
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 node in realpath:
print(node)
def maze_path_queue(x1, y1, x2, y2):
queue = deque()
queue.append((x1, y1, -1))
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 maze[nextNode[0]][nextNode[1]] == 0:
queue.append((nextNode[0], nextNode[1], len(path) - 1)) # 后续节点进队,记录哪个节点带他来的
maze[nextNode[0]][nextNode[1]] = 2 # 标记为已经走过
else:
print("没有路")
return False
maze_path_queue(1, 1, 8, 8)
五、视频链接
迷宫问题