数据结构
定义
数据结构就是设计数据以何种方式组织并存放在计算机中
eg:列表,字典,元组,堆,栈,队列
程序 = 数据结构(静态的数据) + 算法(动态的操作)
分类
- 逻辑结构
- 线性(一对一)
- 非线性
- 树结构(一对多)
- 图结构(多对多)
- 集合结构(除属于同一集合,别无其它关系)
- 存储结构(物理结构)
- 顺序存储结构(列表)
- 链式存储结构
注:逻辑上为非线性结构,存储结构可以以线性结构存储,eg:堆(在逻辑上试属于非线性结构中的树,但可以以线性存储结构中的列表存储)
c语言数组与python中列表的区别
- c语言需设置存储空间大小
- 两者空间存储内容范围不同
c语言数组只能存储同一类型元素,python可存储不同类型元素,由于python不同类型元素所占的字节不同,无法定义数组容量,因此,python数组存储元素的地址,占用一片连续的空间。
插入删除的时间复杂度为:O(n)
栈
特点
后进先出(last in first out:LIFO)
基本操作
- 进栈:push
- 出栈:pop
- 取栈顶(不拿走):gettop
栈的实现
- 进栈:li.append
- 出栈:li.pop
- 取栈顶:li[len(li)-1]
# 栈
# 访问该类属性格式:self+.+属性
class Stack: # 定义一个栈类
def __init__(self): # 初始化栈
self.stack = []
def push(self,element): # 进栈方法
self.stack.append(element)
def pop(self): # 出栈方法
if len(self.stack) > 0: # 判断栈类是否有元素
return self.stack.pop()
else:
return None
def get_top(self): # 获取栈顶元素方法
if len(self.stack) > 0: # 判断是否存在栈顶元素
return self.stack[-1]
else:
return None
stack = Stack()
stack.push(9)
stack.push(6)
stack.push(6)
print(stack.pop())
print(stack.pop())
print(stack.get_top())
栈的应用
# 栈的括号匹配问题
class Stack:
def __init__(self):
self.stack = []
def push(self,element):
self.stack.append(element)
def pop(self):
if len(self.stack) > 0:
return self.stack.pop()
else:
return None
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def is_empty(self):
return len(self.stack) == 0
def brace_match(s):
stack = Stack()
catch = {"]":"[","}":"{",")":"("}
for i in s:
if i in "([{":
stack.push(i)
else:
if stack.is_empty():
stack.push(i)
elif catch[i] == stack.get_top():
stack.pop()
else:
stack.push(i)
if stack.is_empty():
print("匹配")
else:
print("不匹配")
str = str(input())
brace_match(str)
队列
特点
先进先出(first in first out:FIFO)
队列的实现——环形队列
原理
队首指针往下一格:(front+1)%MaxSize
队尾指针往下一格:(rear+1)%MaxSize
队满条件:(rear+1)%MaxSize == front
队空条件:font == rear
实现
# 堆的底层实现
class Queue:
def __init__(self,size): # 指定列表空间大小
self.size = size
self.queue = [0 for _ in range(size)]
self.rear = 0 # rear表示队尾元素
self.front = 0 # front表示队头元素
def push(self,element):
if not self.is_filled():
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = element
else:
raise Exception("队满了!")
def pop(self):
if not self.is_empty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise Exception("队空了!")
# 队空情况
def is_empty(self):
return self.rear == self.front
# 队满情况
def is_filled(self):
return (self.rear + 1) % self.size == self.front
queue = Queue(7)
for i in range(6):
queue.push(i)
print(queue.pop())
双端队列
同时具有队列和栈的特点:元素可以从两端进行删除和插入操作
python队列内置模块的实现
from collections import deque
# 单向队列
queue = deque()
for j in range(10):
queue.append(j) # 在队尾插入元素
print(queue.popleft()) # 在队首移除元素
# 多向队列
for i in range(20,30):
queue.appendleft(i) # 在队首插入元素
print(queue.pop()) # 在队尾移除元素
队列的应用
# 队列的应用(从文件中读取后n行)
from collections import deque
def tail(n):
with open(r"C:\Users\hby\Desktop\1.txt","r") as f:
queue = deque(f,n)
# f表示初始化一个队列,n表示队列所能存放的最大空间,若超过,dequeue会将前面的元素移除,最后保留后加入的n给元素
return queue
for i in tail(5):
print(i,end="")
栈和队列的应用
背景
迷宫问题:给一个二维列表,表示迷宫(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]
]
direction = [ # 用于存储给出参数为(x,y)的上下左右坐标位置
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 path(x1,y1,x2,y2): # x1,y1表示起始点;x2,y2表示终点
stuck = [] # 将路径点存放在列表(可进行增加和删除操作)stuck中
stuck.append((x1,y1))
while(len(stuck)>0): # 当列表还存在元素时(表示可能还有通路)
curNode = stuck[-1] # 记录已走到最末的当前点位置
if curNode == (x2,y2): # 当走到终点时,输出终点
for i in stuck:
print(i)
return True
for i in direction:
newNode = i(curNode[0],curNode[1]) # 找到末点的后面的点位置,得到一个坐标(x,y)
if maze[newNode[0]][newNode[1]] == 0: # 判断该点是否是通路
stuck.append(newNode) # 是通路则存放列表中
maze[newNode[0]][newNode[1]] = 2 # 改变该点的标志为2,说明该点已走过
break # 以新的点进行重新while循环,进行深度优先搜索
else:
# 若当前点都没有下一个可走位置,则去除该点,找到前一个点,如此循环,直到找到有可走路径的点为止
stuck.pop()
maze[newNode[0]][newNode[1]] = 2 # 并设该点为已走过的点
else: # 若列表内元素为空,则表明无通路
print("无通路!")
return False
path(1,1,8,8)
队列(广度优先搜索)
- 原理:考虑一个位置所有可走的位置,分叉多条路,最后得到的路径一定是最短路径,需要一个额外的数组来存储当前位置的源头下标。
- 实现
# 迷宫
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]
]
# 方向
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_back(path):
Node = path[-1] # 从终点开始往回找
realPath = [] # 存放正确的路径
while Node[2] != -1: # 等于-1时即为起始点
realPath.append(Node[0:2])
Node = path[Node[2]]
path.append(Node[0:2])
realPath.reverse() # reverse能够使得列表本身发生颠倒
for i in realPath:
print(i)
# 广度优先搜索
from collections import deque # python内置队列
def maze_path_queue(x1,y1,x2,y2):
queue = deque()
path = [] # 存放移除队列的元素
queue.append((x1,y1,-1)) # -1表示该元素在path里源头的下标
while len(queue) > 0: # 判断找到终点元素前队列是否为空
curNode = queue.pop()
path.append(curNode) # 保存元素,以便后面从终点找起点
if curNode[0] == x2 and curNode[1] == y2:
maze_back(path)
return True
for i in directions:
newNode = i(curNode[0],curNode[1])
if maze[newNode[0]][newNode[1]] == 0:
queue.append((newNode[0],newNode[1],len(path)-1))
maze[newNode[0]][newNode[1]] = 2
else:
print("无通路!")
return False
maze_path_queue(1,1,8,8)
链表
定义
由一系列结点组成的元素集合
包含两部分:数据域(item)& 指针域(next)
数据域:存放数据
指针域:存放下一个结点的地址,指向下一个结点的位置
结点与结点之间不是顺序存放,结点内部之间则是顺序存放
链表的创建和遍历
- 链表的创建
- 头插法:需要指向头节点的指针
- 尾插法:需要指向头节点和尾节点的指针
# 链表的创建
class Node:
def __init__(self,element): # 初始化结点(包括数据域和指针域)
self.element = element
self.next = None
# 头插法
def head_insert(li):
head = Node(li[0])
for i in li[1:]:
node = Node(i) # 创建新的结点
node.next = head
head = node
return head
head = head_insert([1,2,3,4,5,6])
print(head.element)
# 尾插法
def tail_insert(li):
head = Node(li[0])
tail = head
for i in li[1:]:
node = Node(i)
tail.next = node
tail = node
return head,tail
head,tail = tail_insert([1,2,3,4,5,6])
print(head.next.element,tail.element)
链表的插入和删除
链表的插入和删除操作的效率要高于列表
- 链表的插入:
p.next = curNode.next
curNode.next = p
- 链表的删除:
p=curNode.next
curNode.next = curNode.next.next
del p
双链表
双链表有两个指针(指向前一个结点的指针prior和指向后一个结点的指针next)
双链表的插入和删除
- 插入
curNode.next.prior = p
p.next = curNode.next
curNode.next = p
p.prior = curNode
- 删除
p = curNode.next
curNode.next = p.next
p.next.prior = curNode
del p
复杂度分析
顺序表(数组/列表)与链表
- 链表在插入和删除操作的时间效率要高于顺序表,顺序表在按下标查找元素的时间效率要高于链表
- 链表的内存可以更灵活的分配(不需要连续的且既定大小的空间)
哈希表(散列表)
通过哈希函数计算数据存储位置的数据结构,属于线性存储结构,通常支持如下操作:
- insert(key,value):插入键值对
- get(key):如果存在键为key的键值对则返回其value,否则返回空值
- delete(key):删除键为key的键值对
构成
①直接寻址表 ②哈希函数
哈希冲突
当有两个值通过哈希函数计算到了同一个位置上
python中的字典和集合以哈希表的形式实现的
哈希冲突解决方法
开放寻址法
通过哈希函数找到原本该存放的位置,当位置已有元素,接着通过以下方式进行空位的查找与存放;查找元素方法与存放元素方法必须一样。
- 线性探查法:如果位置i被占用,则探查i+1,i+2,……
- 二次探查法:如果位置i被占用,则探查i+12,i-12,i+22,i-22,……
- 二度哈希法:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,则尝试使用h2,h3,……
拉链法
通过链表,将存放在同一位置的元素进行串联起来
直接寻址表(k的key为k)
- 原理
将可能的关键字范围建成一个长度为该范围的列表,关键字存放位置的下标即为该关键字的值(eg:2存放在下标为2的列表中)
- 评价
优点:能够快速进行插入和删除操作
缺点:当关键字的域很大时,浪费空间;当域很多,而关键字较集中时(实际出现的key很少),浪费空间;无法处理关键字不为数字的情况
改进直接寻指标(哈希)(k的key为h(k))
- 思想
- 构建一个大小为m的列表
- 设置哈希函数h(k)使得关键域U映射在范围为[0,m-1]的列表中
哈希表的内部实现
# 链表功能的定义
class LinkList:
# 定义结点(包括数据域和指针域)
class Node:
def __init__(self,element=None):
self.element = element
self.next = None
# 迭代器的定义(必须有iter和next)
class LinkListIterator:
def __init__(self,head):
self.head = head
def __iter__(self):
return self
def __next__(self):
if self.head:
cur_node = self.head
self.head = cur_node.next
return cur_node.element
else:
raise StopIteration
# 链表的元素添加
def __init__(self,iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
# 将元素进行拆分成单个元素
def extend(self,iterable):
for obj in iterable:
self.append(obj)
# 对单个元素进行添加
def append(self,obj):
s = LinkList.Node(obj)
if self.head:
self.tail.next = s
self.tail = s
else:
self.head = s
self.tail = s
# 对单个元素进行寻找
def find(self,obj):
for i in self:
if i == obj:
return True
else:
return False
# 设置链表迭代器
def __iter__(self):
return self.LinkListIterator(self.head)
# 输出
def __repr__(self):
return "<<"+",".join(map(str,self))+">>"
# 哈希数组的定义
class HashTable:
# 哈希数组
def __init__(self,size=101):
self.size = size
self.T = [LinkList() for i in range(self.size)]
# 哈希函数
def h(self,k):
return k % self.size
# 元素的插入
def insert(self,k):
key = self.h(k)
if self.T[key].find(k):
print("Duplicated Insert.")
else:
self.T[key].append(k)
hash = HashTable()
hash.insert(0)
hash.insert(1)
hash.insert(3)
hash.insert(102)
print(hash.T)
哈希表的应用
-
字典和集合都是通过哈希表来实现的
-
安全性上(将数据映射到128位的哈希值)
-
md5算法
-
SHA2算法(无法反推)
-
树
定义
树是一种非线性(一对多)且可以递归定义的数据结构
概念
-
根结点、叶子结点
- 根结点:第一个结点(无父结点)
- 叶子结点:无孩子的结点(无度的结点)
-
树的深度(高度)
-
树的度
整个结点中有最大度结点的度(结点的度:一个结点的分叉数)
树的实例
# 模拟文件系统(linus)
# 定义结点
class Node:
def __init__(self,name):
self.name = name
self.type = "dir"
self.childen = [] # 存放该结点的孩子结点
self.parent = None # 存放该结点的父结点
def __repr__(self):
return self.name
# 树结构
class FileSystemTree:
def __init__(self): # 初始化根目录
self.root = Node("/")
self.now = self.root
def mkdir(self,name): # 文件的创建
if name[-1] != "/":
name += "/"
newNode = Node(name)
self.now.childen.append(newNode)
newNode.parent = self.now
def ls(self): # 对当前文件的字文件进行查询
return self.now.childen
def cd(self,name): # 跳转文件
if name[-1] != "/":
name += "/"
for Name in self.now.childen:
if Name.name == name:
self.now = Name
return
raise ValueError("invalid dir")
file = FileSystemTree()
file.mkdir("bin")
file.mkdir("user")
file.cd("user")
file.mkdir("python")
print(file.ls())
二叉树
定义
- 每个结点的度都大于大于等于2
- 有左右结点孩子区分
存储形式
- 若为完全二叉树可采用顺序存储方式(如排序算法中的堆排序)
- 若不为完全二叉树可采用链式存储方式
结点的定义
class Node:
def __init__(self,name):
self.name = name
self.lchild = None # 左孩子
self.rchild = None # 右孩子
def __repr__(self): # 定义输出形式,默认输出地址
return self.name
a = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
e = Node("e")
root = a
a.lchild = c
a.rchild = b
b.rchild = d
print(root.rchild.rchild)
二叉树的遍历
标签:__,结点,return,self,数据结构,stack,def
From: https://www.cnblogs.com/byyya/p/17798742.html