数据结构和算法笔记--12树

关于树的基本概念:可见数据结构和算法笔记–6堆排序中的树的部分

树的实例:模拟文件系统

class Node:
	def __init__(self, name, type='dir'):
		self.name = name
		self.type = type # "dir" or "file"
		# 链式储存
		self.children = []
		self.parent = None
	def __str__(self):
		return self.name
		
# 用树结构来实现linux系统中的一些命令
class FileSystemTree:
	def __init__(self):
		self.root = Node("/")
		self.now = self.root
	
	def mkdir(self, name):
		# name 以 / 结尾
		if name[-1] != "/":
			name += "/"
		node = Node(name)
		self.now.children.append(node)
		node.parent = self.now
	
	def ls(self):
		return self.now.children
	
	def cd(self, name):
		# 仅支持相对路径
		if name[-1] != "/":
			name += "/"
		if name == "../":
			self.now = self.now.parent
			return
		
		for child in self.now.children:
			if child.name == name:
				self.now = child
				return
		else:
			raise ValueError("Invalid Dir")
	

二叉树

二叉树

class BiTreeNode:
	def __init__(self, data):
		self.data = data
		self.lchild = None	# 左孩子
		self.rchild = None	# 右孩子


a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")

e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f 

root = e

例子当中创建如图所示二叉树:
二叉树1

二叉树的遍历

二叉树的遍历方式:

  • 前序遍历:EACBDGF
  • 中序遍历:ABCDEGF
  • 后序遍历:BDCAFGE
  • 层次遍历:EAGCFBD(需要用到队列)
from collections import deque
def pre_order(root):
	if root:
		print(root.data, end=",")
		pre_order(root.lchild)
		pre_order(root.rchild)

def in_order(root):
	if root:
		in_order(root.lchild)
		print(root.data, end=",")
		in_order(root.rchild)

def post_order(root):
	if root:
		post_order(root.lchild)
		post_order(root.rchild)
		print(root.data, end=",")

def level_order(root):
	queue = deque
	queue.append(root)
	while len(queue) > 0:
		node = queue.popleft()
		print(node.data,end=",")
		if node.lchild:
			queue.append(node.lchild)
		if node.rchild:
			queue.append(node.rchild)

二叉搜索树(Binary Search Tree, BST)

二叉搜索树是一颗二叉树且满足性质:设x是二叉树的一个节点。如果y是x左子树的一个节点,那么y.key <= x.key; 如果y是x右子树的一个节点,那么y.key >= x.key
二叉搜索树的操作:查询,插入,删除
二叉搜索树
二叉搜索树的中序遍历结果是有序的序列

class BiTreeNode:
	def __init__(self, data):
		self.data = data
		self.lchild = None	# 左孩子
		self.rchild = None	# 右孩子
		self.parent = None
		
class BST:
	def __init__(self, li=None):
		self.root = None
		if li:
			for val in li:
				self.insert_no_rec(val)

	def insert(self, node, val):
		if not node:
			node = BiTreeNode(val)
		elif val < node.data:
			node.lchild = self.insert(node.lchild, val)
			node.lchild.parent = node
		elif val > node.data:
			self.insert(node.rchild, val)
			node.rchild.parent = node
		return node
	
	def insert_no_rec(self, val):
		p = self.root
		if not p:	# 空树的情况
			self.root = BiTreeNode(val)
			return
		while True:
			if val < p.data:
				if p.lchild:
					p = p.lchild
				else:	# 左孩子不存在
					p.lchild = BiTreeNode(val)
					p.lchild.parent = p
					return
			elif val > p.data:
				if p.rchild:
					p = p.rchild
				else:	# 左孩子不存在
					p.rchild = BiTreeNode(val)
					p.rchild.parent = p
					return
			else:
				return

	def query(self, node, val):
		if not node:
			return None
		if node.data < val:
			return self.query(node.rchild, val)
		elif node.data > val:
			return self.query(node.lchild, val)
		else:
			return node

	def query_no_rec(self, val):
		p = self.root
		while p:
			if p.data < val:
				p = p.rchild
			elif p.data > val:
				p = p.lchild
			else:
				return p
		else:	# 找不到目标val
			return None

二叉搜索树的删除

  1. 如果要删除的节点是叶子节点:直接删除
    删除叶子

  2. 如果要删除的节点只有一个孩子:将此节点的父亲与孩子链接,然后删除该节点。
    删一个

  3. 如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点。
    删两个

def __remove_node_1(self, node):
	# 第一种情况:node是叶子节点
	if not node.parent:	# 如果node 是根
		self.root = None
	if node = node.parent.lchild:	# node是它父亲的左孩子
		node.parent.lchild = None
		node.parent = None
	else: 	# node是右孩子
		node.parent.rchild = None
		node.parent = None

def __remove_node_21(self, node):
	# 情况2.1:node只有一个左孩子
	if not node.parent:
		self.root = node.lchild
		node.lchild.parent = None
	elif node == node.parent.lchild:
		node.parent.lchild = node.lchild
		node.lchild.parent = node.parent
	else:
		node.parent.rchild = node.lchild
		node.lchild.parent = node.parent
		
def __remove_node_22(self, node):
	# 情况2.1:node只有一个右孩子
	if not node.parent:
		self.root = node.rchild
		node.rchild.parent = None
	elif node == node.parent.lchild:
		node.parent.lchild = node.rchild
		node.rchild.parent = node.parent
	else:
		node.parent.rchild = node.lchild
		node.lchild.parent = node.parent
		
def delete(self, val):
	if self.root:	#不是空树
		node = self.query_no_rec(val)
		if not node:
			return False
		if not node.lchild and not node.rchild:		#情况1
			self.__remove_node_1(node)
		elif not node.rchild:	#只有左孩子,没有右孩子,符合情况2.1
			self.__remove_node_21(node)
		elif not node.lchild:	# 2.2情况,只有右孩子
			self.__remove_node_22(node)
		else:
			# 情况3,两个孩子都有
			min_node = node.rchild
			while min_node.lchild:
				min_node = min_node.lchild
			node.data = min_node.data
			# 删除min_node
			if min_node.rchild:
				self.__remove_node_22(min_node)
			else:
				self.__remove_node_1(min_node)
  • 平均情况下,二叉树进行搜索的时间复杂度为O(logn).
  • 最坏情况下,二叉搜索树可能非常偏斜
  • 解决方法
    • 随机化插入
    • AVL树

AVL树的概念

AVL树:AVL树是一颗自平衡的二叉搜索树
AVL树具有以下性质

  • 根的左右子树的高度之差的绝对值不能超过1
  • 根的左右子树都是二叉平衡树
    AVL树

AVL树旋转

插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。
插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树高度差2
不平衡的出现可能有4中情况。

  1. 不平衡是由于对K的左孩子的左子树插入导致的:右旋
    AVL右旋

  2. 不平衡是由于对K的右孩子的右子树插入导致的:左旋
    AVL左旋

  3. 不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋
    AVL右旋左旋

  4. 不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋
    AVL左旋右旋
    规则:一节点如果左侧高度高,则bf为负,右侧高度高则bf为正。

插入操作并且更新bf
在插入操作过程当中,从插入节点的父亲节点开始,往上走一直传递bf的变化。左子树传来的bf-=1,右子树传来的bf+=1
传递过程中,一旦一个节点变成了0.那么将不再传递。一旦出现了2或者-2,则会发生不平衡。便将这棵树进行旋转。
avl不平衡

class AVLNode(BiTreeNode):
	def __init__(self):
		BiTreeNode.__init__(self, data)
		self.bf = 0	# balance factor,左右子树高度差

class AVLTree(BST):
	def __init__(self, li=None):
		BST.__init__(self, li)
	
	def rotate_left(self, p, c):
		s2 = c.lchild
		p.rchild = s2
		if s2:
			s2.parent = p
		c.lchild = p
		p.parent = c
		# 这是在插入时的bf情况,在删除时的bf会变化
		p.bf = 0
		c.bf = 0
		return c
		
	def rotate_right(self, p, c):
		s2 = c.rchild
		p.lchild = s2
		if s2:
			s2.parent = p
		c.rchild = p
		p.parent = c
		# 这是在插入时的bf情况,在删除时的bf会变化
		p.bf = 0
		c.bf = 0
		return c
		
	def rotate_right_left(self, p, c):
		g = c.lchild
		s3 = g.rchild
		c.lchild = s3
		if s3:
			s3.parent = c
		g.rchild = c
		c.parent = g
		
		s2 = g.lchild
		p.rchild = s2
		if s2:
			s2.parent = p
		g.lchild = p
		p.parent = g
		# 更新bf
		if g.bf > 0:
			p.bf = -1
			c.bf = 0
		else:	# g.bf <0 或者 == -1			p.bf = 0
			c.bf = 1
		g.bf = 0
		return g

	def rotate_left_right(self, p, c):
		g = c.rchild
		
		s2 = g.lchild
		c.rchild = s2
		if s2:
			s2.parent = c
		g.lchild = c
		c.parent = g

		s3 = g.rchild
		p.lchild = s3
		if s3:
			s3.parent = p
		g.rchild = p
		p.parent = g
		# 更新bf
		if g.bf < 0: # 插入到s2上
			p.bf = 1:
			c.bf = 0
		else:
			p.bf = 0
			c.bf = -1
		g.bf = 0
		return g

	def insert_no_rec(self, val):
		# 1. 和BST二叉搜索树,插入
		p = self.root
		if not p:	# 空树的情况
			self.root = BiTreeNode(val)
			return
		while True:
			if val < p.data:
				if p.lchild:
					p = p.lchild
				else:	# 左孩子不存在
					p.lchild = BiTreeNode(val)
					p.lchild.parent = p
					node = p.lchild	# node存储的就是插入的节点
					break
			elif val > p.data:
				if p.rchild:
					p = p.rchild
				else:	# 左孩子不存在
					p.rchild = BiTreeNode(val)
					p.rchild.parent = p
					node = p.rchild
					break
			else:	# val == p.data
				return
			
		# 2. 更新balance factor
		# node是从叶子开始遍历,然后遍历上来的,已经不是之前插入的值了
		while node.parent:	#node的父亲不为空
			if node.parent.lchild == node:	# 传递是左子树来对,左子树更深
				# 更新node.parent的bf -= 1
				if node.parent.bf < 0: # 原来node.parent.bf == -1, 更新之后变成-2
					# 做选择
					# 看node哪边沉
					g = node.parent.parent	# 为了连接旋转之后的子树
					x = node.parent
					if node.bf > 0:
						n = self.rotate_left_right(node.parent, node)
					else:
						n = self.rotate_right(node.parent, node)
						# 集的把n和g连接起来
				elif node.parent > 0: # 原来node.parent.bf==1,更新之后变成0
					node.parent.bf = 0
					break
				else: #原来的node.parent.bf = 0,更新之后变成-1
					node.parent.bf = -1
					node = node.parent
					continue
			else: # 传递是从右子树来的,右子树更深。
				# 更新后的node.parent.bf += 1
				if node.parent.bf > 0: # 原来node.parent.bf == 1, 更新之后变成2
					# 做旋转
					# 看node哪边沉
					g = node.parent.parent
					x = node.parent	# 旋转前的子树的根
					if node.bf < 0:
						n = self.rotate_right_left(node.parent, node)
					else: # node.bf = -1
						n = self.rotate_left(node.parnet, node)
					# 记得把n和g连接起来
				elif node.parent.bf < 0: # node.parent.bf == -1,更新之后变成0
					node.parent.bf = 0
					break
				else: #原来的node.parent.bf = 0,更新之后变成1
					node.parent.bf = 1
					node = node.parent
					continue
					
			# 连接旋转之后的子树
			n.parent = g
			if g:
				if x == g.lchild:
					g.lchild = n
				else:
					g.rchild = n
				break	
			else:	# 如果g为空
				self.root = n
				break

二叉搜索树的扩展应用–B树

B树(B-Tree):B树是一颗自平衡的多路搜索树。常用于数据库的索引。
B树

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值