前言
本文内容讲解几种常见的非线性数据结构(树,字典树,堆,图,并查集)的概念,功能及其实现。
树
树 (tree) 是一种非常高效的非线性存储结构。树,自然中的树有根,有叶子,对应在数据结构中的树就是根节点、叶子节点。同一层的节点叫兄弟节点,邻近不同层的叫父子节点。
树又分门别类,分为二叉树、满二叉树和完全二叉树
二叉树:每个节点都至多有二个子节点的树;
满二叉树:在二叉树的基础上,除了叶子节点外,每个节点都有左右两个子节点,这种二叉树叫做满二叉树;
完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除第d层外的所有节点构成满二叉树,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
-
树的深度:一棵树中节点的最大深度就是树的深度,也称为高度
-
节点深度:对任意节点x,x节点的深度表示为根节点到x节点的路径长度。所以根节点深度为0,第二层节点深度为1,以此类推
python实现二叉树
class Node():
def __init__(self,data):
self.data = data #相应的元素数据
self.left = None #左子节点
self.right = None #右级节点
#当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印在这个方法中return的数据
#__str__方法需要返回一个字符串,当做这个对象的描写
def __str__(self):
return str(self.data)
#二叉树的实现逻辑
class Tree():
def __init__(self):
#根节点定义为 root 永不删除
self.root = Node("root")
#添加节点到二叉树
def add(self,data):
node = Node(data)
#如果根节点不存在,插入的节点则为根节点
if self.root is None:
self.root = node
else:
#先在 q 列表中,添加二叉树的根节点
q = [self.root]
while True:
#先把root节点取出来(此时q列表为空了),判断左右子节点为不为空,若有一个为空则插入,插入成功直接return结束循环
#若都不为空,则把root的左右子节点依次添加到q中,再按同样的逻辑依次从root的左右子节点找位置插入,插入成功直接return结束循环
pop_node = q.pop(0)
#左子树为空则将点添加到左子树
if pop_node.left == None:
pop_node.left = node
return
#右子树为空则将点添加到右子树
elif pop_node.right == None:
pop_node.right = node
return
else:
q.append(pop_node.left)
q.append(pop_node.right)
#找到给定data的父节点
def get_parent(self,data):
#如果给定data为root的data,返回None
if self.root.data == data:
return None
else:
#依次轮询每一个节点的左右子节点,若这个节点的左右子节点的数据为给定的data,则返回这个节点
#否则依次添加其左右子节点进temp列表,不断轮询查找,若轮询完所有的节点依旧没有找到符合的节点,返回None
temp = [self.root]
while temp:
pop_node = temp.pop(0)
if pop_node.right is not None and pop_node.right.data == data:
return pop_node
if pop_node.left is not None and pop_node.left.data == data:
return pop_node
if pop_node.left is not None:
temp.append(pop_node.left)
if pop_node.right is not None:
temp.append(pop_node.right)
return None
#删除给定data的节点
"""
删除节点的情况有以下几种:
1、删除的节点为root,删除失败;
2、删除的节点无子节点,直接删除
3、删除的节点只有一个子节点,其子节点接替删除节点的位置
4、删除的节点有2个子节点。这里面又要判断删除的节点的右子节点有无左子节点:
> 如无,将右子节点替换删除的节点即可;如有,找到最左子节点,去替换要删除的节点,
> 还要注意最左子节点可能也有右子节点,这个节点也要接到最左子节点的父节点上去
"""
def delete(self,data):
if self.root is None:
return False
#找出给定data的节点的父节点
parent = self.get_parent(data)
if parent:
#确定待删除节点
del_node = parent.left if parent.left.data == data else parent.right
#若待删除节点的左子树为空
if del_node.left is None:
#如果待删除节点是其父节点的左孩子
if parent.left.data == data:
parent.left = del_node.right
else:
parent.right = del_node.right
del del_node
return True
#若待删除节点的右子树为空
elif del_node.right is None:
# 如果待删除节点是父节点的左孩子
if parent.left.data == data:
parent.left = del_node.left
# 如果待删除节点是父节点的右孩子
else:
parent.right = del_node.left
# 删除变量 del_node
del del_node
return True
#当左右子树都不为空
else:
tmp_pre = del_node
#待删除节点的右子树
tmp_next = del_node.right
#寻找待删除节点右子树中的最左叶子节点并完成替代
#如果待删除节点的右子树的左子树为空
if tmp_next.left is None:
tmp_pre.right = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
else:
#while循环把tmp_next指向右子树的最左叶子节点
while tmp_next.left:
tmp_pre = tmp_next
tmp_next = tmp_next.left
tmp_pre.left = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
if parent.left.data == data:
parent.left == tmp_next
else:
parent.right = tmp_next
del del_node
return True
else:
return False
#二叉树的中序遍历
def inorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.inorder(node.left)
right_data = self.inorder(node.right)
return left_data + result + right_data
#二叉树的先序遍历
def preorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.preorder(node.left)
right_data = self.preorder(node.right)
return result + left_data + right_data
#二叉树的后序遍历
def postorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.preorder(node.left)
right_data = self.preorder(node.right)
return left_data + right_data + result
#作业:初始化创建 Node,Tree类;将1-10添加到二叉树里面(使用add());将三种遍历过程打印出来
if __name__ == "__main__":
t = Tree()
for i in range(1,11):
t.add(i)
print("中序遍历",t.inorder(t.root))
print("先序遍历",t.preorder(t.root))
print("后序遍历",t.postorder(t.root))
字典树
字典树介绍可见:看动画轻松理解「Trie树」
python实现字典树
class TrieNode:
#初始化字典树节点,每个节点都是字典,其键为某字符,其值为对应的子节点
def __init__(self):
self.nodes = dict()
#是否叶子节点
self.is_leaf = False
#插入单词
def insert(self,word:str):
curr = self
for char in word:
if char not in curr.nodes:
curr.nodes[char] = TrieNode()
curr = curr.nodes[char]
curr.is_leaf = True
#插入多个单词
def insert_many(self,words:[str]):
for word in words:
self.insert(word)
#查询单词是否在字典树中
def search(self,word:str):
curr = self
for char in word:
if char not in curr.nodes:
return False
curr = curr.nodes[char]
return curr.is_leaf
堆
堆有两点需要了解
- 堆一般采用完全二叉树;
- 堆中的每一个节点都大于其左右子节点(大顶堆),或者堆中每一个节点都小于其左右子节点(小顶堆)。
python实现堆
import math
class Heap:
def __init__(self):
#初始化一个空堆,使用数组来初始化堆元素
self.data_list = []
#得到指定序号的节点的父节点的序号
def get_parent_index(self,index):
if index == 0 or index > len(self.data_list) - 1:
return None
else:
return (index-1) >> 1
#交换两个序号的数据
def swap(self,index_a,index_b):
self.data_list[index_a],self.data_list[index_b] = self.data_list[index_b],self.data_list[index_a]
#插入元素
def insert(self,data):
# 先把元素放在最后,然后从后往前依次堆化
# 这里以大顶堆为例,如果插入元素比父节点大,则交换,直到最后
self.data_list.append(data)
index = len(self.data_list)-1
parent = self.get_parent_index(index)
while parent is not None and data > self.data_list[parent]:
#交换操作
self.swap(index,parent)
index = parent
parent = self.get_parent_index(parent)
#删除堆顶元素
def removeMax(self):
remove_data = self.data_list[0]
self.data_list[0] = self.data_list[-1]
del self.data_list[-1]
#堆化
self.heapify(0)
return remove_data
def heapify(self,index):
#从上往下堆化
total_index = len(self.data_list)
while True:
maxvalue_index = index
if 2*index+1 < total_index and self.data_list[2*index+1] > self.data_list[maxvalue_index]:
maxvalue_index = 2*index+1
if 2*index+2 < total_index and self.data_list[2*index+2] > self.data_list[maxvalue_index]:
maxvalue_index = 2*index+2
if maxvalue_index == index:
break
self.swap(index,maxvalue_index)
index = maxvalue_index
if __name__ == "__main__":
myheap = Heap()
for i in range(10):
myheap.insert(i+1)
print("建堆:",myheap.data_list)
print("删除堆顶元素:",[myheap.removeMax() for i in range(10)])
图
图的邻接表实现
图的点集可以用列表实现,图的边集可以用字典实现
python以邻接表实现图(无向图)
#无向图的邻接表表现形式
class Graph(object):
def __init__(self):
self.nodes = []
self.edge = {}
def insert(self,a,b):
#如果a不在图的点集里,则添加a
if not(a in self.nodes):
self.nodes.append(a)
self.edge[a] = list()
if not(b in self.nodes):
self.nodes.append(b)
self.edge[b] = list()
#a连接b
self.edge[a].append(b)
#b连接a
self.edge[b].append(a)
#返回与a连接的点
def succ(self,a):
return self.edge[a]
#返回图的点集
def show_nodes(self):
return self.nodes
def show_edge(self):
print(self.edge)
if __name__ == "__main__":
graph = Graph()
graph.insert('0', '1')
graph.insert('0', '2')
graph.insert('0', '3')
graph.insert('1', '3')
graph.insert('2', '3')
graph.show_edge()
图的邻接矩阵实现
python以邻接矩阵实现图(无向图)
class Graph:
def __init__(self,vertex):
#图有几个节点
self.vertex = vertex
#初始化图的邻接矩阵,全为0
self.graph = [[0] * vertex for i in range(vertex)]
def insert(self,u,v):
#对存在连接关系的两个点,在矩阵里置1代表存在连接关系,没有连接关系则置0
self.graph[u-1][v-1] = 1
self.graph[v-1][u-1] = 1
#显示图的邻接矩阵
def show(self):
for i in self.graph:
for j in i:
print(j,end = " ")
print(" ")
if __name__ == "__main__":
graph = Graph(5)
graph.insert(1,4)
graph.insert(4,2)
graph.insert(4,5)
graph.insert(2,5)
graph.insert(5,3)
graph.show()
图的深度优先遍历
图的深度优先遍历算法的主要思想是:
-
首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边选择一条支路一直往下探寻未访问过的顶点;
-
当支路一头扎到底,没有未访问过的顶点时,则回退上一个顶点,继续往下探寻,简而言之就是一头扎到底,走到尽头时就回退,然后再继续一头扎到底,循环往复;
深度优先遍历算法简而言之就是一头扎到底,走到尽头时就回退,然后再继续一头扎到底,循环往复。
python实现图的深度优先遍历算法
#图的深度优先遍历算法实现
def dfs(G,s,S=None,res=None):
if S is None:
# 储存已经访问节点
S=set()
if res is None:
# 存储遍历顺序
res=[]
res.append(s)
S.add(s)
for u in G[s]:
if u in S:
continue
S.add(u)
dfs(G,u,S,res)
return res
G = {'0': ['1', '2'],
'1': ['2', '3'],
'2': ['3', '5'],
'3': ['4'],
'4': [],
'5': []}
print(dfs(G, '0'))
图的广度优先遍历
广度优先搜索(BFS)是树的按层次遍历的推广,它的基本思想是:首先访问初始点 vi,并将其标记为已访问过,接着访问 vi 的所有未被访问过的邻接点 vi1,vi2,…, vin,并均标记已访问过,然后再按照 vi1,vi2,…, vin 的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点 vi 有路径相通的顶点都被访问过为止。
python实现图的广度优先遍历算法
#图的广度优先遍历算法实现
def bfs(graph,start):
# explored:已经遍历的节点列表,queue:寻找待遍历的节点队列
explored = []
queue = [start]
explored.append(start)
while queue:
#将要遍历v节点
v = queue.pop(0)
for w in graph[v]:
if w not in explored:
explored.append(w)
queue.append(w)
return explored
G = {'0': ['1', '2'],
'1': ['2', '3'],
'2': ['3', '5'],
'3': ['4'],
'4': [],
'5': []}
print(bfs(G,"0"))
并查集
python实现并查集
#用python实现一个并查集,思路见:https://segmentfault.com/a/1190000013805875
class Union_find:
#并查集:类似于森林
def __init__(self,data_list):
self.father_dict = {}
self.set_size = {}
for node in data_list:
self.father_dict[node] = node
self.set_size[node] = 1
#寻找父节点(最终父节点)
def find(self,node):
father = self.father_dict[node]
if node != father:
father = self.find(father)
self.father_dict[node] = father
return father
#判断两个节点是不是同一个集合里的,只需判断两个节点的父节点是不是同一个
def is_same_set(self,node_a,node_b):
return self.find(node_a) == self.find(node_b)
#将两个集合合并在一起
def union(self,node_a,node_b):
if node_a is None or node_b is None:
return
a_head = self.find(node_a)
b_head = self.find(node_b)
if a_head != b_head:
a_set_size = self.set_size[a_head]
b_set_size = self.set_size[b_head]
#将小集合合并在大的集合里
if a_set_size >= b_set_size:
self.father_dict[b_head] = a_head
self.set_size[a_head] = a_set_size + b_set_size
else:
self.father_dict[a_head] = b_set_size
self.set_size[b_head] = a_set_size + b_set_size
if __name__ == "__main__":
a = [1,2,3,4,5]
union_a = Union_find(a)
union_a.union(1,2)
union_a.union(3,5)
union_a.union(3,1)
print(union_a.is_same_set(2,5))