本文是博主的学习笔记,若有不足请指正
更多精品,请看博客专栏
关注我,一起学python!
1.数据结构介绍
1.1 什么是数据结构?
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。即数据结构有两个要素:数据元素的集合以及数据元素之间的关系。
在python中,我们常用的列表、字典等等都是一种数据结构。
1.2数据结构的分类
在逻辑结构上,我们把数据结构分为为3种:
- 线性结构:数据元素存在一对一的相互关系
- 树结构:数据元素存在一对多的相互关系
- 图结构:数据元素存在多对多的相互关系
2. 数据结构-列表
列表是一种线性结构,且是python中较为基础的数据结构。
2.1 列表在内存中的存储方式
列表在内存中是依照顺序表来存储的,列表中的元素在内存中是紧挨着的,占用的是一块连续的内存空间。
2.2 列表的中的元素查找(元素储存方式)
在此之前,我们先来看一下C语言中的数组元素查找的方法:
假定a在内存中的地址是1432,我们想要查找a[2],即元素23。对于c来说,a[2]的地址 = a的地址+元素占用内存数。若此处的一个整型占用4个字节,那么,a[2]的地址 = 1432+2*4 = 1440 。
但是在python中,列表不同于数组(列表元素的数据类型可以不同,长度可以不固定)。因此列表在内存中的保存数据的形式也与C有一些差异。 在python中,a这一块内存中存放的不再是实际数据,而是这些数据的引用(地址),因此,python的列表可以实现储存不同的数据类型;对于不固定长度而言,python实际上分配的内存还是固定的,只是当内存长度不够时,系统会再次分配一个更大的内存,并将原先的数据复制过去。
3.数据结构-栈
3.1 栈的基本介绍
栈(Stack)是一个数据集合,可以理解为只有一端开口的容器。因此,需要遵循先进后出的原则。如下图,A先进入栈中,则A必须等上方所有都出去后才能出去;而由于F是最后进入的,所以F是第一个出去的。
栈的基本操作有以下几种:
- 进栈:也叫压栈(push)
- 出栈:pop
- 取栈顶:gettop,仅明确栈顶信息,不取出。
3.2 栈的的实现
通过python中的列表即实现栈:
- 进栈:list.append()
- 出栈:list.pop()
- 取栈顶:list[-1]
我们简单实现一下栈:
class Stack():
def __init__(self):
self.stack = list()
def push(self,element):
self.stack.append(element)
def pop(self):
try:
return self.stack.pop()
except Exception as e:
print("Error:栈为空")
def get_top(self):
try:
return self.stack[-1]
except Exception as e:
print("Error:栈为空")
if __name__ == '__main__':
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.get_top())
print(stack.pop())
print(stack.get_top())
输出为:
3
3
2
3.3 栈的应用-括号匹配问题
给定一个字符串,其中包含多种类型的括号,求这些字符串中括号是否匹配。
如()[]{}是匹配;[()]是匹配;[]( 是不匹配;{(})是不匹配。
代码如下:
class Stack():
def __init__(self):
self.stack = list()
def push(self,element):
self.stack.append(element)
def pop(self):
try:
return self.stack.pop()
except Exception as e:
print("Error:栈为空")
def get_top(self):
try:
return self.stack[-1]
except Exception as e:
return None
def is_empty(self):
return len(self.stack) == 0
def is_match(s):
match = {
"]":"[","}":"{",")":"("}
stack = Stack()
# s = list(s)
for ch in s:
if ch in ["{","[","("]:
stack.push(ch)
elif ch in ["}","]",")"]:
if stack.is_empty():
return False
elif match[ch] == stack.get_top():
stack.pop()
elif match[ch] != stack.get_top():
return False
if stack.is_empty():
return True
else:
return False
if __name__ == '__main__':
print(is_match("这{}dfa{}[](({
{你好}你好呀}[]))"))
print(is_match("{
{
{}"))
输出为:
True
False
Process finished with exit code 0
4.数据结构-队列
4.1 队列的基本概念
队列(Queue)和栈一样,都是一个数据集合,但队列是一个由上下开口的容器,形象的来说就像一个管道。队列仅允许在一端进行插入,另一端进行删除,即遵循先进先出的原则。如下图
对于队列,进入队列我们称为进队或入队,而插入的那一端称为队尾;进行删除的一端称为队头,删除动作称为出队。
4.2 简单实现队列
我们可以通过一个环形列表来实现队列。所谓环形列表,就是当队尾指针达到列表的最后时,它的下一个位置就自动到0了,即 front =lenSize — 1时,front+1 =0。除此之外,还应该满足以下条件:
- 队首指针front前进1: front = (front+1)% lenSize
- 队尾指针rear前进1:rear = (rear+1)% lenSize
- 队空:rear = front
- 队满:(rear+1)%lenSize == front
代码实现如下:
class Queue:
def __init__(self,size=100):
self.size = size # 队列长度
self.queue = [0 for i in range(size)]
self.front = 0 # 队首指针
self.rear = 0 # 队尾指针
#队空
def is_empty(self):
return self.front == self.rear
# 队满
def is_fulled(self):
return (self.rear+1)% self.size == self.front
# 进队
def push(self,element):
if not self.is_fulled():
self.rear = (self.rear+1) % self.size
self.queue[self.rear] = element
else:
raise IndexError("queue is fulled")
# 出队
def pop(self):
if not self.is_empty():
self.front = (self.front+1) % self.size
return self.queue[self.front]
else:
raise IndexError("queque is empty")
q= Queue(20)
for i in range(19):
q.push(i)
print(q.is_fulled())
print(q.pop())
5. 栈和队列的应用
迷宫问题:
给定一个二维列表,表示迷宫,其中1表示墙不可通过,0表示,通道,可通过。现写一个算法,求一条走出迷宫的路径。
如下图:
5.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]
]
def maze_path(x1,y1,x2,y2): # 起点(x1,y1) 终点(x2,y2)
stack = []
stack.append((x1,y1))
directions = [ # 方向 上右下左
lambda x,y:(x-1,y),
lambda x,y:(x,y+1),
lambda x,y:(x+1,y),
lambda x,y:(x,y-1)]
while len(stack) > 0:
now_point = stack[-1] # 当前位置点
if now_point == (x2,y2): # 判断是否到达终点
print("path is:")
for point in stack:
print(point)
return True
for direction in directions: # 寻找下一个点
next_point = direction(now_point[0],now_point[1]) # 传入当前点,得到下一个点
if maze[next_point[0]][next_point[1]] == 0:
stack.append(next_point)
maze[next_point[0]][next_point[1]] = 2
break
else:
maze[next_point[0]][next_point[1]] = 2
stack.pop()
else:
print("no path")
return False
# 测试
maze_path(1,1,8,8)
输出如下:
path is:
(1, 1)
(1, 2)
(2, 2)
(3, 2)
(3, 1)
(4, 1)
(5, 1)
(5, 2)
(5, 3)
(6, 3)
(6, 4)
(6, 5)
(5, 5)
(4, 5)
(4, 6)
(4, 7)
(3, 7)
(3, 8)
(4, 8)
(5, 8)
(6, 8)
(7, 8)
(8, 8)
5.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]
]
def print_path(path):
now_point = path[-1] # 终点
realPath = [] # 迷宫路径
while now_point[2] != -1:
realPath.append((now_point[0:2]))
now_point = path[now_point[2]]
realPath.append(now_point[0:2]) # 将起点放入
realPath.reverse()
for path1 in realPath:
print(path1)
directions = [ # 方向 上右下左
lambda x, y: (x - 1, y),
lambda x, y: (x, y + 1),
lambda x, y: (x + 1, y),
lambda x, y: (x, y - 1)]
def maze_path_queue(x1,y1,x2,y2):
queue = deque() # 创建一个队列
queue.append((x1,y1,-1))
path = []
while len(queue) >0:
now_point = queue.popleft() # 获取当前位置
path.append(now_point)
if (now_point[0] ==x2) & (now_point[1] == y2): # 判断是否到达终点
print_path(path)
return True
for direction in directions:
next_point = direction(now_point[0],now_point[1])
if maze[next_point[0]][next_point[1]] == 0:
queue.append((next_point[0],next_point[1],len(path)-1))
maze[next_point[0]][next_point[1]] == 2 # 2表示已经走过
else:
print("path is null")
return False
maze_path_queue(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)
6. 数据结构-链表
6.1 链表的基本概念
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个点的指针next。每个节点之间相互连接,最终串联成一个链表。
简单代码如下:
class Node:
def __init__(self,item):
self.item = item
self.next = None
a = Node(1)
b = Node(2)
c = Node(3)
d = Node(4)
a.next = b
b.next = c
c.next = d
print(a.item)
print(a.next.item)
print(a.next.next.item)
输出如下:
1
2
3
6.2 创建链表
创建列表一共有两种方法,一是头插法,二是尾插法:
- 头插法即将新元素插入入到头部之前,作为新head
- 尾插法就是将新元素插入到尾部之后,作为新tail
头插法代码实现:
class Node:
def __init__(self,item):
self.item = item
self.next = None
def create_linkList_head(li):
head = Node(li[0])
for i in li[1:]:
node = Node(i)
node.next = head
head = node
return head
linkLst = create_linkList_head(list("123"))
while linkLst:
print(linkLst.item)
linkLst = linkLst.next
输出为:
1
2
3
尾插法代码实现;
class Node:
def __init__(self,item):
self.item = item
self.next = None
def create_linkList_tail(li):
head = Node(li[0])
tail = head
for i in li[1:]:
node = Node(i)
tail.next = node
tail = node
return head
linkList = create_linkList_tail([1,2,3,4,5])
while linkList:
print(linkList.item)
linkList = linkList.next
输出为:
1
2
3
4
5
6.3 链表的插入和删除
对于链表的插入,我们只需解开要插入位置的链表,然后将新元素插入即可。如下图:
若:
yellow_point = now_point.next
则插入操作为:
yellow_point.next = now_point.next
now_point.next = yellow_point
同样的,链表的删除就是将目标元素的前一个next指向目标元素的next。如图:
若:
yellow_point = now_point.next
则删除操作为:
now_point.next = now_point.next.next
6.4 双链表
双链表就是指有前(prior)后(next)两个指针,prior指向前一个节点,next指向后一个节点。如图:
简单实现代码如下:
class Node:
def __init__(self,item):
self.item = item
self.prior = None
self.next = None
a = Node(1)
b = Node(2)
c = Node(3)
d = Node(4)
e = Node(4)
a.next = b
b.prior = a
b.next = c
c.prior = b
c.next = d
d.prior = c
print(b.item)
print(b.next.item)
print(b.prior.item)
输出为:
2
3
1