算法导论实验三:二叉排序树、红黑树的基本操作实现

算法导论实验三 树

二叉查找树、红黑树的基本操作实现


一、实现原理

1.1 二叉查找树(Binary Search Tree)

二叉查找树又称二叉搜索树,二叉排序树,特点如下:

  1. 左子树上所有结点值均小于根结点
  2. 右子树上所有结点值均大于根结点
  3. 结点的左右子树本身又是一颗二叉查找树
  4. 二叉查找树中序遍历得到结果是递增排序的结点序列

二叉排序树如图所示:
在这里插入图片描述

1.2 红黑树

红黑树中每个结点包含五个域:color,key,left,right和p。如果某结点没有一个子结点或父节点,则该域指向NIL。
一颗二叉树如果满足下面的红黑性质,则为一颗红黑树:

  1. 每个结点或是红的,或是黑的
  2. 根结点是黑的
  3. 每个叶结点(NIL)是黑的
  4. 如果一个结点是红的,则它的两个儿子都是黑的
  5. 对每个结点,从该结点到其子孙结点的所有路径上都包含相同数目的黑结点。
    红黑树的图如下图所示:
    在这里插入图片描述
    从某个结点 x 出发 (不包括该结点) 到达一个叶结点的任意一条路径上,黑 色结点的个数称为该结点 x 的黑高度,用 bh(x) 表示。
    引理:一颗有 n 个内结点的红黑树的高度至多为 2lg(n+1)。
    典型操作:其旋转、插入、删除、扩张操作请见教材 P165-P186

1.3 二者性能比较

在这里插入图片描述

二、实验要求

2.1 实现下列关于二叉查找树、红黑树的判断、构建、删除等操作。并写出这些操作的流程图或伪代码

2.1.1 二叉查找树的相关操作

Pro1:Given a binary tree, determine if it is a valid binary search tree (BST).

Assume a BST is defined as follows:
1 The left subtree of a node contains only nodes with keys less than the node’s key.
2 The right subtree of a node contains only nodes with keys greater than the node’s key.
3 Both the left and right subtrees must also be binary search trees

e.g:
在这里插入图片描述
即验证二叉搜索树,判断每个结点的值是否满足二叉搜索树所规定的范围,因此使用递归的方式来判断,定义函数bool digui(TreeNode* node,int lower,int upper),如果出现结点的值不在(lower,upper)中,则返回不符合二叉搜索树。代码如下所示:

class Solution {
public:
    bool helper(TreeNode* node,long lower,long upper){
        if(node == nullptr){
            return true;
        }
        if(node->val <= lower || node->val >= upper){
            return false;
        }
        return helper(node->left,lower,node->val) && helper(node->right,node->val,upper);
    }
    bool isValidBST(TreeNode* root) {
        return helper(root,LONG_MIN,LONG_MAX);
    }
};

测试用例运行截图如下所示:
在这里插入图片描述
时间复杂度为O(n),n为结点个数,在递归调用时每个二叉树的结点最多被访问一次。
空间复杂度为O(n)。递归函数在递归过程中需要为每一层的递归函数分配栈空间,最坏情况下二叉树为一条链,因此递归树高度为n层,最坏情况下空间复杂度为O(n)
二叉树的插入:
分析:二叉树的插入比较简单,从根结点遍历树,找到结点插入的位置即可,插入代码如下所示:

#include <iostream>
using namespace std;
struct Node {
  int data;
  Node* left;
  Node* right;
 
  Node(int data) {
    this->data = data;
    this->left = nullptr;
    this->right = nullptr;
  }
};
 
class BinaryTree {
 public:
  Node* root;
 
  BinaryTree() {
    root = nullptr;
  }
 
  void addNode(int data) {
    Node* newNode = new Node(data);
 
    if (root == nullptr) {
      root = newNode;
    } else {
      Node* focusNode = root;
      Node* parent;
 
      while (true) {
        parent = focusNode;
 
        if (data < focusNode->data) {
          focusNode = focusNode->left;
          if (focusNode == nullptr) {
            parent->left = newNode;
            return;
          }
        } else {
          focusNode = focusNode->right;
          if (focusNode == nullptr) {
            parent->right = newNode;
            return;
          }
        }
      }
    }
  }
 
  void preOrderTraversal(Node* focusNode) {
    if (focusNode != nullptr) {
      std::cout << focusNode->data << " ";
      preOrderTraversal(focusNode->left);
      preOrderTraversal(focusNode->right);
    }
  }
};
 
int main() {
  BinaryTree tree;
 
  tree.addNode(50);
  tree.addNode(25);
  tree.addNode(75);
  tree.addNode(12);
  tree.addNode(37);
  tree.addNode(43);
  tree.addNode(30);
 
  tree.preOrderTraversal(tree.root);
 
  return 0;
}

程序对树的前序遍历输出结果为:
在这里插入图片描述
与分析的结果一致,因此,代码正确。

二叉树的删除伪代码如下所示:

//该段伪代码TRANSPLANT为辅助过程,即在二叉树T中,将以v为根的子树替换以u为根的子树
TRANSPLANT(T,u,v){
	if u .p ==  NIL{
		T.root = v;
	}
	else if u == u.p.left{
		u.p.left = v;
	}
	else u.p.right = v;
	if v != nil{
		v.p = u.p;
	}
}
//该段代码即在树T中删除结点z
TREE_DELETE(T,z){
	//case1:删除结点没有左子结点,直接右子树替换删除结点即可
	if z.left == nil{
		TRANSPLANT(T,z,z.right);
	}
	//case2:删除结点没有右子结点,直接左子树替换删除结点即可
	if z.right == nil{
		TRANSPLANT(T,z,z.left);
	}
	//case 3,有左右孩子
	else{
		y = TREE_MINIMUM(z.right);//找到删除结点的直接后继结点
		if y.p != z{
			//即直接后继结点不是y的右子结点
			TRANSPLANT(T,y,y.right);//先用y的右子节点来替换y
			y.right = z.right;
			y.right.p = y;
		}
		//用y结点代替z结点
		TRANSPLANT(T,z,y);
		y.left = z.left;
		y.left.p = y;
	}
}

二叉树的插入和删除时间复杂度均为O(h)h为二叉树的高度

2.1.2 红黑树

Pro2:Construct a red black tree and delete
Construct a red black tree by {1,5,6,7,8,9,10,11,12,13,14,15}, show the result. Then delete the points in order {14,9,5}, show the result too.
e.g:
在这里插入图片描述
红黑树插入,首先声明插入的结点必为红色结点(因为只有插入红色结点,才有可能不破坏红黑树的性质,插入黑色结点必会破坏当前红黑树的性质)
伪代码为:

//该函数是将z结点插入红黑树T中
RB-INSERT(T,z){
	y = T.nil;
	x = T.root;
	//遍历寻找z结点要插入的位置
	while(x != T.nil){
		y = x;
		if z.key < x.key{
			x = x.left;
		}
		else{
			x = x.right;
		}
	}
	z.p = y;
	if y == T.nil{
		//即插入的红黑树为空的
		T.root = z;
	}
	else if z.key < y.key{
		//插入红黑树的结点比父节点小
		y.left = z;
	}
	else{
		y.right = z;
	}
	z.left = T.nil;
	z.right = T.nil;
	z.color = RED;
	//先将结点当作红结点插入树中,再对树调用RB-INSERT-FIXUP来维持红黑树性质
	RB-INSERT-FIXUP(T,z)	
}
RB-INSERT-FIXUP(T,z){
	//只有插入后连续两个红色结点才需要调整
	while(z.p.color == RED){
		if(z.p = z.p.p.left){
			//插入结点的父节点是爷结点的左孩子
			y = z.p.p.right;//找到插入结点的叔结点
			//case1:叔结点为红色结点
			if y.color == RED{
				z.p.color = BLACK;
				y.color = BLACK;
				z.p,p.color = BLACK;
				z = z.p.p;
			}
			//case2:叔结点为黑色
			else{
				if z == z.p.right{
					z = z.p;
					LEFT-ROTATE(T,z);//左旋
				}
				z.p.color = BLACK;
				z.p.p.color = RED;
				RIGHT-ROTATE(T,z.p.p)//右旋
			}
		}
		else (同理,即z.p == z.p.p.right){
			//将上述情况代码中的left换为right,right换为left,其它不变
		}
	}
	T.root.color = BLACK;//将根结点颜色变为黑色
}

插入示例红黑树的流程图(省略了叶结点的T.nil)如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
因此最后中序遍历输出为
1 black
5 black
6 black
7 black
8 black
9 red
10 black
11 black
12 black
13 red
14 black
15 red

红黑树的删除伪代码如下所示:

//首先辅助过程RB-TRANSPLANT
RB-TRANSPLANT(T,u,v){
	if u.p == T.nil{
		T.root = v;
	}
	else if u == u.p.left{
		u.p.left = v;
	}
	else u.p.right = v;
	v.p = u.p;
}
//在红黑树中删除结点的函数
RB-DELETE(T,z){
	y = z;
	y-original-color = y.color;
	//删除结点无左结点
	if z.left == T.nil{
		x = z.right;
		RB-TRANSPLANT(T,z,z.right);
	}
	//删除结点无右结点
	else if z.right == T.nil{
		z = z.left;
		RB-TRANSPLANT(T,z,z.left);
	}
	//删除结点有左右结点
	else {
		y = TREE-MINIMUM(z.right);
		y-original-color = y.color;
		x = y.right;
		if y.p == z{
			//直接后继节点是删除结点的右子节点
			x.p = y;
		}
		else{
			//直接后继结点不是删除结点的右孩子
			RB-TRANSPLANT(T,y,y.right);
			y.right = z.right;
			y.right.p = y;
		}
		RB-TRANSPLANT(T,z,y);
		y.left = z.left;
		y.left.p = y;
		y.color = z.color;
	}
	if y-original-color == BLACK{
		RB-DELETE-FIXUP(T,x);
	}
}
RB-DELETE-FIXUP(T,x){
	while( x != T.root and x.color == BLACK){
		if x == x.p.left{
			w = x.p.right;
			if w.color == RED{
				//case1:兄弟结点为红色结点
				w.color = BLACK;
				x.p.color = RED;
				LEFT-ROTATE(T,x.p);
				w = x.p.right;
			}
			if w.left.color == BLACK and w.right.color == BLACK{
				//case2:兄弟结点是黑色的,且兄弟结点的两个子结点均是黑色的
				w.color = RED;
				x = x.p;
			}
			else{
				if w.right.color == BLACK{
					//case3:兄弟结点是黑色的,且兄弟结点的左子结点是红色的,右子结点是黑色的
					w.left.color = BLACK;
					w.color = RED;
					RIGHT-ROTATE(T,w);
					w = x.p.right;
				}
				//case4:兄弟结点是黑色的,且兄弟结点的右孩子是红色的
				w.color = x.p.color;
				x.p.color = BLACK;
				w.right.color = BLACK;
				LEFT-ROTATE(T,z.p);
				x = T.root;
			}
		}
		else{同理,left。right替换即可}
	}
	x.color = BLACK;
}

红黑树的删除流程如下图所示:
在这里插入图片描述
在这里插入图片描述
因此最后中序遍历输出为
1 red
6 black
7 blackj
8 red
10 black
11 black
12 black
13 black
15 black

2.2 请说明二叉查找树和红黑树的区别以及时间、空间性能

  • 二叉查找树是红黑树的基础,即红黑树一定满足二叉查找树的特性。二叉查找树在极端情况下会出现一条链表的情况,因此出现了红黑树来解决极端情况下性能差的问题。红黑树可以保证二叉树最长的一条路径不会大于最短路径的两倍。
  • 二叉查找树的插入、删除操作都需要O(h)的时间复杂度,(h为树的高度,最小logn,最大n)。红黑树的插入、删除操作需要O(logn)的时间复杂度。空间复杂度二者均为O(n),即用n个结点存储数据,无需赘述。

总结

本次实验主要熟悉并实践了二叉查找树和红黑树的插入、删除、判断操作以及流程,红黑的相关操作要比二叉查找树复杂不少。通过该实验,让我对于这两种树有了更深的理解,知识得到了很大的提升。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值