# 本章的内容是有序表和并查集,
# 在学习哈希表的时候我们提到过哈希表的构成可以使用单链表,那么我们能不能使用更高效、更多功能的方式完成呢
# 在图的最小生成树的kruskal算法中就简单接触到了并查集的简单功能,这次会完整的实现整个结构
# 岛问题(引出并查集结构)
# 一个矩阵中只有0和1两种值,每个位置都可以和自己的上下左右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛
# 解题思路:从左上开始遍历整个矩阵,当遇到1的时候,看看它的上下左右是不是也是1,如果是1就把他变成2。这样后续遍历就不会把这些识别为岛
import random
def howManyisland(map):
if map == [] or map[0] == []:
return 0
res = 0
for i in range(len(map)):
for j in range(len(map[0])):
if map[i][j] == 1:
res += 1
island(map, i, j)
return res
def island(map, i, j):
if i < 0 or j < 0 or i >= len(map) or j >= len(map[0]) or map[i][j] != 1:
return
map[i][j] = 2
island(map, i + 1, j)
island(map, i - 1, j)
island(map, i, j + 1)
island(map, i, j - 1)
# 思考一下,如何设计一个并行算法解决岛问题(多线程分片处理一个大map,如何合并不同线程的答案?)
# 在这之前我们先学习并查集结构
# 并查集
# 并查集的两个重要的方法:1.查询两个集合是否相同,2.合并两个集合
# 并查集的时间复杂度:当findHeadNode()方法被调用O(N)级别以上后,可以认为findHeadNode()为O(1)
class UnionFind_Node(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return str(self.value)
class UnionFind_set(object):
def __init__(self, arr):
self.nodemap = dict()
self.fatermap = dict()
self.lenghtmap = dict()
for value in arr:
node = UnionFind_Node(value)
self.nodemap[value] = node # value对应的UnionFind_Node结构
self.fatermap[node] = node # UnionFind_Node的fatherNode
self.lenghtmap[node] = 1 # UnionFind_Node为头的长度
def findHeadNode(self, node):
# 查找node的头节点,在查找头节点的过程中,把沿路的节点保存好。
# 返回时把沿路节点的父节点都设置为头节点,扁平化路径
buffer = []
while node != self.fatermap[node]:
buffer.append(node)
node = self.fatermap[node]
while buffer != []:
self.fatermap[buffer.pop()] = node
return node
def isSameSet(self, value_A, value_B):
# 查询两个的头节点是否为同一个节点
if value_A in self.nodemap and value_B in self.nodemap:
node_A = self.nodemap[value_A]
node_B = self.nodemap[value_B]
A_father = self.findHeadNode(node_A)
B_father = self.findHeadNode(node_B)
return A_father == B_father
return False
def union(self, value_A, value_B):
# 把较小的那个集合合并较大的集合里(把较小的那个的头节点的父节点设置为较大的集合的头节点,再删去记录)
if value_A in self.nodemap and value_B in self.nodemap:
if self.isSameSet(value_A, value_B) == False:
A_father = self.findHeadNode(self.nodemap[value_A])
B_father = self.findHeadNode(self.nodemap[value_B])
if self.lenghtmap[A_father] >= self.lenghtmap[B_father]:
self.fatermap[B_father] = A_father
self.lenghtmap[A_father] += self.lenghtmap[B_father]
self.lenghtmap.pop(B_father)
else:
self.fatermap[A_father] = B_father
self.lenghtmap[B_father] += self.lenghtmap[A_father]
self.lenghtmap.pop(A_father)
def __repr__(self):
return str(self.lenghtmap)
# 现在再来看看如何并行的解决岛问题
# 首先,我们要知道,如果单纯的相加是可能会导致岛的数量变多的(如下图所示)
# [1,1,1,1,1,1,1,1] [1,1,1,1] [1,1,1,1]
# [1,0,0,0,0,0,0,1] [1,0,0,0] [0,0,0,1]
# [1,0,1,1,1,1,1,1] [1,0,1,1] [1,1,1,1]
# [1,0,1,0,0,0,0,0]---> [1,0,1,0] [0,0,0,0]
# [1,0,1,1,1,1,1,1] [1,0,1,1] [1,1,1,1]
# [1,0,0,0,0,0,0,1] [1,0,0,0] [0,0,0,1]
# [1,1,1,1,1,1,1,1] [1,1,1,1] [1,1,1,1]
# 现在我们在切分边界上标记出岛A、B、C、D
# [1,1,1,1,1,1,1,1] [1,1,1,1] [1,1,1,1] [1,1,1,A] [C,1,1,1]
# [1,0,0,0,0,0,0,1] [1,0,0,0] [0,0,0,1] [1,0,0,0] [0,0,0,1]
# [1,0,1,1,1,1,1,1] [1,0,1,1] [1,1,1,1] [1,0,1,B] [C,1,1,1]
# [1,0,1,0,0,0,0,0]---> [1,0,1,0] [0,0,0,0]---> [1,0,1,0] [0,0,0,0]
# [1,0,1,1,1,1,1,1] [1,0,1,1] [1,1,1,1] [1,0,1,B] [D,1,1,1]
# [1,0,0,0,0,0,0,1] [1,0,0,0] [0,0,0,1] [1,0,0,0] [0,0,0,1]
# [1,1,1,1,1,1,1,1] [1,1,1,1] [1,1,1,1] [1,1,1,A] [D,1,1,1]
# 得到A、B、C、D四个集合,检查相连出A、C是否为同一个集合,不是则合并(A、C)岛的数量4-1。
# 检查相连(A、C)和B是否为同一个集合,不是则合并(A、B、C)岛的数量4-1-1。
# 检查相连的(A、B、C)和D是否为同一个集合,不是则合并(A、B、C、D)岛的数量4-1-1-1。
# 检查相连的(A、B、C、D)和D是否为同一个集合,是则不做任何操作
# 所以岛的数量为4-1-1-1=1
# 花费的时间从遍历一整个大map,变成了遍历一半map加上查看边界的时间
'''
下面是有序表的内容,包括红黑树、AVL、SB树、跳表四中结构。
什么是有序表?O(logN)
有序表除了支持哈希表的所有操作之外,还支持按照key有序组织(哈希表是繁乱组织的)
可以找到最小的key,找到最大的key,以及找到大于等于某value的最近的key
其中红黑树、AVL、SB树都是平衡搜索二叉树。
'''
# 搜索二叉树:左树 < 头 < 右树
# 搜索二叉树的删除:
# 一、如果是根节点:
# 1.没有左右孩子,直接把根节点指向空
# 2.只有一个子树,
# ① 左孩子cur无右树 (右孩子无左树),把根节点指向cur
# ② 左孩子有右树 (右孩子有左树),找到左孩子最右的节点 (右孩子最左的节点)cur2和它的父节点p2。
# 把cur2的左树(右树)接到p2的右树(左树),把根节点的左孩子(右孩子)接到cur2的左树(右树),根节点指向cur2
# 3.左右孩子双全,
# ① 左孩子cur无右树,把根节点的右孩子接到cur右树,把根节点指向这个节点
# ② 左孩子有右树,找到左孩子最右的节点,cur2和它的父节点p2。
# 把cur2的左树接到p2的右树,把根节点的左孩子接到cur2的左树,根节点的右孩子接到cur2的右树,根节点指向cur2
# 二、不是根节点:
# 不断向下找找到要删除的cur和他的父节点p (把原来指向根节点的,改成cur对应p的方向)
# 1.没有左右孩子,直接把p对应的方向指向空
# 2.只有一个子树,
# ① 左孩子cur2无右树 (右孩子无左树),把p对应的方向指向cur2
# ② 左孩子有右树 (右孩子有左树),找到左孩子最右的节点 (右孩子最左的节点)cur2和它的父节点p2。
# 把cur2的左树(右树)接到p2的右树(左树),把cur的左孩子(右孩子)接到cur2的左树(右树),p对应的方向指向cur2
# 3.左右孩子双全,
# ① 左孩子cur2无右树,把cur的右孩子接到cur2右树,把p对应的方向指向这个节点
# ② 左孩子有右树,找到左孩子最右的节点,cur2和它的父节点p2。
# 把cur2的左树接到p2的右树,把cur的左孩子接到cur2的左树,cur的右孩子接到cur2的右树,p对应的方向指向cur2
class TreeNode(object):
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def __repr__(self):
return str(self.value)
class BST(object):
def __init__(self):
self.root = None
def add(self, value):
node = TreeNode(value)
if self.root == None:
self.root = node
return
cur = self.root
while True:
if node.value < cur.value:
if cur.left == None:
cur.left = node
return
cur = cur.left
elif node.value > cur.value:
if cur.right == None:
cur.right = node
return
cur = cur.right
def find(self, value):
father = None
cur = self.root
while cur.value != value:
father = cur
if value < cur.value:
cur = cur.left
elif value > cur.value:
cur = cur.right
if cur == None:
return False, None, None
return True, cur, father
def delete(self, value):
if self.find(value)[0] == False:
# 如果不存在,直接返回
return None
p = self.root
if value < p.value:
cur = p.left
elif value > p.value:
cur = p.right
else:
# 当要删的是根节点
cur = p
if cur.left == None and cur.right == None:
# 如果根节点没有左右子树,直接把根节点指向空
self.root = None
return None
if cur.left == None or cur.right == None:
# 如果只有一个子树
if cur.left == None:
# 如果只有右子树,找到右子树的最左的位置
cur = cur.right
while cur.left != None:
p = cur
cur = cur.left
if p == self.root:
# 如果右子树最左位置的父节点还是根节点,代表右孩子就是右子树最左的位置,把根节点指向右孩子
self.root = cur
return self.root
p.left = cur.right # 把右子树最左位置的右子树接到它父节点的左树上
cur.right = self.root.right # 把根节点的右孩子接到右子树最左位置的右孩子上
self.root = cur
return p
else:
# 如果只有左子树,找到左子树最右的位置
cur = cur.left
while cur.right != None:
p = cur
cur = cur.right
if p == self.root:
# 如果左子树最右位置的父节点还是根节点,就代表左孩子就是左子树最右的位置,把根节点指向左孩子
self.root = cur
return self.root
p.right = cur.left # 把左子树最右位置的左子树接到它的父节点的右树上
cur.left = self.root.left # 把根节点的左孩子接到左子树最右位置的左孩子的上
self.root = cur
return p
else:
# 左右孩子双全,随便选一种。
# 找到左子树最右的位置
cur = cur.left
while cur.right != None:
p = cur
cur = cur.right
if p == self.root:
# 如果左子树最右位置的父节点还是根节点,就代表左孩子就是左子树最右的位置,把根节点的右孩子接到左孩子的右树上,把根节点指向左孩子
cur.right = self.root.right
self.root = cur
return self.root
p.right = cur.left # 把左子树最右位置的左子树接到它的父节点的右树上
cur.left = self.root.left # 把根节点的左孩子接到左子树最右位置的左孩子的上
cur.right = self.root.right # 把根节点的右孩子接到左子树最右位置的右孩子的上
self.root = cur
return p
while cur.value != value:
# 往下找到要删除的节点cur
p = cur
if value < cur.value:
cur = cur.left
elif value > cur.value:
cur = cur.right
if cur.left == None and cur.right == None:
# 如果cur没有左右子树,把p对应的方向指向空
if p.left == cur:
p.left = None
else:
p.right == None
return p
if cur.left == None or cur.right == None:
# 如果只有一个子树
if cur.right != None:
# 如果cur没有左子树,找到右树最左的位置
p2 = cur
cur2 = cur.right
while cur2.left != None:
p2 = cur2
cur2 = cur2.right
if p2 == cur:
# 如果右树最左位置的父节点还是cur,代表右孩子就是右树最左的位置,把p对应的方向改成右孩子
if p.left == cur:
p.left = cur2
elif p.right == cur:
p.right = cur2
return p2
p2.left = cur2.right # 把右树最左位置的右子树接到它父节点的左孩子上
cur2.left = cur.left # 把cur的左子树(None)接到右树最左位置的左孩子上
cur2.right = cur.right # 把cur的右子树接到右树最左位置的右孩子上
if p.left == cur:
p.left = cur2
else:
p.right = cur2
return
if cur.left != None:
# 如果cur没有右子树,找到左树最右的位置
p2 = cur
cur2 = cur.left
while cur2.right != None:
p2 = cur2
cur2 = cur2.right
if p2 == cur:
# 如果左树最右位置的父节点还是cur,代表左孩子就是左树最右的位置,把p对应的方向改成左孩子
if p.left == cur:
p.left = cur2
else:
p.right = cur2
return
p2.right = cur2.left # 把左树最右位置的左子树接到它父节点的左孩子上
cur2.left = cur.left # 把cur的左子树接到左树最右位置的左孩子上
cur2.right = cur.right # 把cur的右子树(None)接到左树最右位置的右孩子上
if p.left == cur:
p.left = cur2
else:
p.right = cur2
return
else:
# 左右孩子双全,随便选一种
# 找到右子树最左的位置
p2 = cur
cur2 = cur.right
while cur2.left != None:
p2 = cur2
cur2 = cur2.right
if p2 == cur:
# 如果右树最左位置的父节点还是cur,代表右孩子就是右树最左的位置,把cur的左树接到右树最左位置的左孩子上,把p对应的方向改成右孩子
cur2.left = cur.left
if p.left == cur:
p.left = cur2
else:
p.right = cur2
return
p2.left = cur2.right # 把右树最左位置的右子树接到它父节点的左孩子上
cur2.left = cur.left # 把cur的左子树接到右树最左位置的左孩子上
cur2.right = cur.right # 把cur的右子树接到右树最左位置的右孩子上
if p.left == cur:
p.left = cur2
else:
p.right = cur2
return
def simple_delete(self, value):
found = self.find(value)
cur = found[1]
father = found[2]
if found[0] == False:
return None
if father == None:
if cur.left == None and cur.right == None:
self.root = None
return None
elif cur.left == None or cur.right == None:
if cur.left == None:
cur2 = cur.right
while cur2.left != None:
cur2 = cur2.left
if cur2 == cur.right:
self.root = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.left = cur2.right
cur2.right = cur.right
self.root = cur2
else:
cur2 = cur.left
while cur2.right != None:
cur2 = cur2.right
if cur2 == cur.left:
self.root = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.right = cur2.left
cur2.left = cur.left
self.root = cur2
return cur2_father
else:
cur2 = cur.right
while cur2.left != None:
cur2 = cur2.left
if cur2 == cur.right:
cur2.left = cur.left
self.root = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.left = cur2.right
cur2.right = cur.right
cur2.left = cur.left
self.root = cur2
return cur2_father
else:
if cur.left == None and cur.right == None:
if cur == father.left:
father.left = None
elif cur == father.right:
father.right = None
return father
elif cur.left == None or cur.right == None:
if cur.left == None:
cur2 = cur.right
while cur2.left != None:
cur2 = cur2.left
if cur2 == cur.right:
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.left = cur2.right
cur2.right = cur.right
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2_father
else:
cur2 = cur.left
while cur2.right != None:
cur2 = cur2.right
if cur2 == cur.left:
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.right = cur2.left
cur2.left = cur.left
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2_father
else:
cur2 = cur.left
while cur2.right != None:
cur2 = cur2.right
if cur2 == cur.left:
cur2.right = cur.right
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2
else:
found_cur2 = self.find(cur2.value)
cur2_father = found_cur2[2]
cur2_father.right = cur2.left
cur2.left = cur.left
cur2.right = cur.right
if cur == father.left:
father.left = cur2
elif cur == father.right:
father.right = cur2
return cur2_father
def select(self):
res = []
queue = []
queue.append(self.root)
while queue != []:
root = queue.pop(0)
if root != None:
res.append(root)
queue.append(root.left)
queue.append(root.right)
else:
res.append("#")
return res
def __repr__(self):
return str(self.select())
# 平衡性:为了防止数据情况不好破坏性能和指标,左树和右树的节点体量相差不要太悬殊,左树和右树高度差不超过k(不同结构k不同)
# AVL树是平衡性要求最严格的,它左树和右树的高度差不超过1
# 调整平衡性的方法:
# 1.左旋:整个树向左倒,让原头节点的右孩子作为头节点,新头节点的左孩子放到原头节点的右树上,原头节点变成新头节点的新左孩子
# 2.右旋:整个树向右倒,让原头节点的左孩子作为头节点,新头节点的右孩子放到原头节点的左树上,原头节点变成新头节点的新右孩子
class Balance(object):
def Turn_left(self, head, father):
if head.right == None:
# 如果没有右子树,不能左旋
return
newhead = head.right # 新头节点是原头节点的右孩子
head.right = newhead.left # 把原头节点的右孩子替换成新头节点的左树
newhead.left = head # 把新头节点的左孩子替换成原头节点
if father == None:
# 如果要左旋的是整个树,让根节点指向新头节点
self.root = newhead
elif father.left == head:
# 把新头节点接在父节点的下面
father.left = newhead
else:
father.right = newhead
def Turn_right(self, head, father):
if head.left == None:
return
newhead = head.left
head.left = newhead.right
newhead.right = head
if father == None:
self.root = newhead
elif father.left == head:
father.left = newhead
else:
father.right = newhead
# 怎么使用左旋和右旋让树拥有平衡性?
# 一、什么时候检查平衡性?
# 1.增:从新增节点开始往上检查,当前节点所在的树有没有平衡性
# 2.删:从替换节点的父节点开始往上检查,当前节点所在的树有没有平衡性
# 二、怎么检查平衡性,怎么检查平衡性? (记住LL型和RR型怎么操作,其他两种的策略就是转换成这样的结构)
# 1.LL型: 左孩子的左树过长导致平衡性失效,右旋
# 2.LR型: 左孩子的右树过长导致平衡性失效,左孩子左旋,再左旋
# 3.RR型: 右孩子的右树过长导致平衡性失效,左旋
# 4.RL型: 右孩子的左树过长导致平衡性失效,右孩子右旋,再左旋
class AVL(object):
def __init__(self):
self.Tree = BST()
def add(self, value):
self.Tree.add(value)
found = self.Tree.find(value)
node = found[1]
father = found[2]
self.balance(node, father)
def select(self):
return self.Tree.select()
def find(self, node):
if type(node) == int:
return self.Tree.find(node)
if node == None:
return False, None, None
value = node.value
return self.Tree.find(value)
def delete(self, value):
node = self.Tree.simple_delete(value)
found = self.find(node)
node = found[1]
father = found[2]
self.balance(node, father)
return node
def height(self, node):
if node == None:
return 0
leftHeight = self.height(node.left)
rightHeight = self.height(node.right)
height = max(leftHeight, rightHeight) + 1
return height
def balance(self, node, father):
while node != None:
nodeBalance = self.height(node.left) - self.height(node.right)
if nodeBalance >= 2:
leftBalance = self.height(node.left.left) - self.height(node.left.right)
if leftBalance >= 0:
# 左树过长,LL
Balance.Turn_right(self.Tree, node, father)
else:
# LR
Balance.Turn_left(self.Tree, node.left, node)
Balance.Turn_right(self.Tree, node, father)
elif nodeBalance <= -2:
rightBalance = self.height(node.right.right) - self.height(node.right.left)
if rightBalance >= 0:
# 右树过长,RR
Balance.Turn_left(self.Tree, node, father)
else:
# RL
Balance.Turn_right(self.Tree, node.right, node)
Balance.Turn_left(self.Tree, node, father)
father_father = self.find(father)
node = father_father[1]
father = father_father[2]
def __repr__(self):
return str(self.select())
# 红黑树和SB树
# 这两个和AVL树的区别就是平衡的标准不同.
# 1.AVL树: 左树和右树高度相差不超过1
# 2.SB树: 每一颗树的大小要大于等于其兄弟树的子树大小 (如果左树大小为10,则右树大小最大为10+10+1)
# 3.红黑树: 如果一条路最长就是黑红相间,最短就是全黑,所以从当前点出发,每一条路径长度不超过两倍
# ① 点的标签(红、黑)
# ② 整棵树的头和叶节点的子节点(None)都是黑
# ③ 红点不相邻
# ④ 每一条到结束的路径黑点一样多
# SB树
# T
# L R
# A B C D
# LL型:左子树的左边大小过大,即R<A,右旋。L变为头节点,因为R的孩子没有变化,T增加新的孩子节点B,L的右孩子变化,所以再判断T和L的情况
# RR型:右子树的右边大小过大,即L<D,左旋。调节T和R的平衡
# LR型:左子树的右边大小过大,即R<B,先L左旋再T右旋,把B调整到头节点位置。调节L、T的平衡再调节B的平衡
# T T B
# L R B R L T
# A B C D --> L F C D -->A E F R
# E F A E C D
#
# RL型:右子树的左边大小过大,即L<C.先R右旋再T左旋,把C调节到头节点。调节R、T的平衡再调节C的平衡
# 跳表
# 跳表的结构和前面三种都不一样,它是由多条链表组成的一个类似于网状的结构
# 每加入一个节点的时候,按照随机的方式添加层数。
# 所有操作都是从高层开始,查找比value小的最右的节点,修改这个节点的next就相当于修改了表的结构
# 因为增加一层的概率是1/2,增加两层的概率就是1/4.....第0层右N个节点,第1层大概N/2个节点,第2层大概N/4个节点,
# 它的每一层都能加速掉1/2、1/4...个节点
class SkipNode(object):
def __init__(self, value):
self.value = value
self.next = [None]
def add(self, num):
self.next += [None] * num
def __repr__(self):
return str(self.value)
class Skip_List(object):
def __init__(self):
self.root = SkipNode(None)
def find(self, node, value, index):
# 找到在index层从node点开始,小于value最右的位置
while True:
if node.next[index] == None or node.next[index].value >= value:
break
node = node.next[index]
return node
def exist(self, value):
# 查找是否存在,返回小于value最高最右的节点,以及查找到的节点的当前层数
index = len(self.root.next) - 1
cur = self.root
while index >= 0:
node = self.find(cur, value, index)
cur = node
if cur.next[index] != None and cur.next[index].value == value:
return True, cur, index
index -= 1
return False, None, -1
def insert(self, value):
n = 0
while True:
if random.randint(0, 1) == 1:
n += 1
else:
break
print(n)
node = SkipNode(value)
node.add(n)
if n + 1 > len(self.root.next):
self.root.add(n + 1 - len(self.root.next))
index = len(self.root.next) - 1
cur = self.root
while index >= 0:
while True:
if cur.next[index] == None or cur.next[index].value > value:
if index <= n:
node.next[index] = cur.next[index]
cur.next[index] = node
break
cur = cur.next[index]
index -= 1
def delete(self, value):
found = self.exist(value)
if found[0] == False:
return
cur = found[1]
index = found[2]
cur.next[index] = cur.next[index].next[index]
if cur == self.root and cur.next[index] == None:
cur.next.pop()
index -= 1
while index >= 0:
cur = self.find(cur, value, index)
cur.next[index] = cur.next[index].next[index]
if cur == self.root and cur.next[index] == None:
cur.next.pop()
index -= 1
python算法复习(六)----有序表(详解 AVL树和跳表 )和并查集
最新推荐文章于 2022-09-12 13:22:26 发布
本文深入探讨了并查集在解决岛问题中的应用,通过实例展示了如何利用并查集有效地计算矩阵中的岛屿数量。此外,还介绍了有序表的概念,特别关注了红黑树、AVL树和SB树这三种平衡搜索二叉树的数据结构,分析了它们的平衡性调整策略,并阐述了跳表在加速查找过程中的作用。
摘要由CSDN通过智能技术生成