《算法导论》第十二章——二叉搜索树

  虽然写这个博客主要目的是为了给我自己做一个思路记忆录,但是如果你恰好点了进来,那么先对你说一声欢迎。我并不是什么大触,只是一个菜菜的学生,如果您发现了什么错误或者您对于某些地方有更好的意见,非常欢迎您的斧正!

12.1什么是二叉搜索树

  一棵二叉搜索树是以二叉树来组织的。除了key和卫星数据之外,每个结点还包含属性left、right和parent,它们分别指向结点的左孩子、右孩子和父亲。根结点是树中唯一父指针为NULL的结点。
  这一节内容我看了一下大概就是介绍了二叉树的三种遍历:先序遍历、中序遍历和后序遍历。这三种遍历的具体图解我在另一篇文章中已经写了,有兴趣的可以看一下。这里对于这三种遍历就不详细讲了。如果直接点文字出现的是404,可以选择复制代码。 二叉树的前序、中序、后序遍历(https://blog.csdn.net/weixin_40851250/article/details/82994636)
12.2查询二叉树

  我们可以看到二叉树有个很明显的特征:左边的数都比根结点小,右边的数都比根结点大。这一节主要介绍了五个函数:

①查找一个关键字key,找到就返回这个关键字所在的结点的指针,否则返回NULL
②查找最大关键字元素:这个显然,只要一直沿着右儿子遍历知道next指向NULL就可以了
③查找最小关键字元素:只要一直沿着左儿子遍历知道next指向NULL就可以了
④查找前驱元素:
  1.若一个节点有左子树,那么该节点的前驱节点是其左子树中val值最大的节点
  2.若一个节点没有左子树,那么判断该节点和其父节点的关系
    2.1 若该节点是其父节点的右边孩子,那么该节点的前驱结点即为其父节点。
    2.2 若该节点是其父节点的左边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,
      直到找到一个有右子树的结点Q,那么Q就是该节点的前驱节点

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

⑤查找后继元素:
1.若一个节点有右子树,那么该节点的后继节点是其右子树中val值最小的节点
2.若一个节点没有右子树,那么判断该节点和其父节点的关系
  2.1 若该节点是其父节点的左边孩子,那么该节点的后继结点即为其父节点
  2.2 若该节点是其父节点的右边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,
    直到找到一个有左孩子的节点Q,那么Q就是该节点的后继节点

在这里插入图片描述


在这里插入图片描述

下面这张图应该是情况2.2



在这里插入图片描述

12.3插入和删除

  插入和删除操作会引起由二叉搜索树表示的动态集合的变化。
  首先来看一下插入的伪代码:(插一部分,我们看一下BtreeNode* &root),解释一下为什么要有这个&,“这是C++的语法写法,&在形参中表示“引用”实参,LNode * &lst ; 中LNode * 是个整体,表示变量类型是LNode类指针, &lst中的&表明引用实参,即代表实参的一个别名。 ”(取自 kaixingui2012的回答如果链接打不开,可以复制下面的链接)(https://zhidao.baidu.com/question/2266744263935050308.html)

InsertBtreeNode(BtreeNode* &root, BtreeNode* key)

1、y=NULL
2、x=root
3、while x≠NULL     /如果根结点不为空/
4、  y=x;        /y是x的父结点/
5、  if key.data<x.data  /如果key的值小于根结点/
6、    x=x.left     /插入到根的左边/
7、  else x=x.right    /否则插入到右边/
8、key.parent=y       /key的父结点就是y/
9、if y==NULL       /表示树是空的/
10、   root=key;     /插入的数就是根结点/
11、elseif key.data<y.data  /这几行是判断插入到左边还是插入到右边/
12、   y.left=key
13、else y.right=key


  再来看一下删除。删除分为四种情况:

①没有孩子:使它为NULL
②只有左孩子:用左孩子代替它
③只有右孩子:用右孩子代表它
④有两个孩子:找到它的后继,用它的后继代替它

这四种情况的图解如下:

在这里插入图片描述

  这里有一个辅助的子函数:Translant(root,u,v),它的作用是用一棵以v为根的子树来替换一棵以u为根的子树,这个子函数并没有处理v.left和v.right的更新,这些更新都删除函数里进行。我们先来看一下这个子函数的伪代码:

Translant(root,u,v)
1、if u.parent== NULL    /如果要替换的是根结点/
2、  root=v
3、elseif u==u.parent.left   /如果u是个左孩子/
4、   u.parent.left=v
5、else u.parent.right=v
6、if v≠NULL        /如果v非空/
7、   v.parent=u.parent  /使v的父结点变为u的父结点/


删除的伪代码:

void DeleteBtreeNode(BtreeNode* &root, BtreeNode* z)

1、if z.left=NULL         /如果z没有左孩子,这个情况包括了z没有孩子/
2、  Translant(root,z,z.right)   /交换z与z的右孩子/
3、elseif z.right==NULL      /z没有右孩子/
4、   Translant(root,z,z.left)
5、else y=MinBtreeNode(z.right)  /右子树的最小值,即要删除结点的后继/
6、   if y.parent≠z      /后继与要删除结点不是父子关系/
7、     Translant(root,y,y.right) 
8、     y.right=z.right
9、     y.right.parent=y
10、   Translant(root,z,y)    /让后继代替要删除的结点/
11、   y.left=z.left
12、   y.left.parent=y

12.4随机构建二叉搜索树

  这一章,我没怎么看,感觉没什么实质内容,就算有我也看不懂。

接下来是代码部分,建议粘贴到编辑器中自己运行一下:

二叉搜索树.h
#pragma once
/*构建二叉树结点*/
typedef struct BtreeNode
{
	int data;/*该结点的数据*/
	BtreeNode *left;/*指向左边结点的指针*/
	BtreeNode *right;/*指向右边结点的指针*/
	BtreeNode *parent;/*指向父结点的指针*/
}BtreeNode;

/*先序遍历*/
void PreBtreeNode(BtreeNode *root);
/*中序遍历*/
void MidBtreeNode(BtreeNode *root);
/*后序遍历*/
void PastBtreeNode(BtreeNode *root);

/*查找:返回找到的结点*/
BtreeNode* SearchBtreeNode(BtreeNode *root,int key);
/*查找最大值*/
BtreeNode* MaxBtreeNode(BtreeNode *root);
/*查找最小值*/
BtreeNode* MinBtreeNode(BtreeNode *root);
/*查找前驱*/
BtreeNode* BeforeBtreeNode(BtreeNode *root);
/*查找后继*/
BtreeNode* AfterBtreeNode(BtreeNode *root);

/*插入*/
void InsertBtreeNode(BtreeNode* &root, BtreeNode* key);
/*子过程:移动子树,用子树v代替子树u*/
void Translant(BtreeNode* &root, BtreeNode* u, BtreeNode* v);
/*删除*/
void DeleteBtreeNode(BtreeNode* &root, BtreeNode* z);

void BtreeTest();
二叉搜索树.cpp
#include "二叉搜索树.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
/*先序遍历*/
void PreBtreeNode(BtreeNode *root)
{
	if (root != NULL)
	{
		cout << root->data << "\t";/*打印这个数的数值*/
		PreBtreeNode(root->left);
		PreBtreeNode(root->right);
	}
}
/*中序遍历*/
void MidBtreeNode(BtreeNode *root)
{
	if (root != NULL)
	{
		MidBtreeNode(root->left);
		cout << root->data << "\t";/*打印这个数的数值*/
		MidBtreeNode(root->right);
	}
}
/*后序遍历*/
void PastBtreeNode(BtreeNode *root)
{
	if (root != NULL)
	{
		PastBtreeNode(root->left);
		PastBtreeNode(root->right);
		cout << root->data << "\t";/*打印这个数的数值*/
	}
}

/*查找:返回找到的结点*/
BtreeNode* SearchBtreeNode(BtreeNode *root, int key)
{
	if (root == NULL || root->data == key)/*如果结点为空或者找到就返回这个结点*/
		return root;
	if (key < root->data)/*如果key小于根的值,就搜索左子树*/
		return SearchBtreeNode(root->left, key);
	else
		return SearchBtreeNode(root->right, key);/*否则搜索右子树*/

	/*非递归版本*/
	/*while (root != NULL && root->data != key)
	{
		if (key < root->data)
			root = root->left;
		else
			root = root->right;
	}
	return root;*/
}
/*查找最大值*/
BtreeNode* MaxBtreeNode(BtreeNode *root)
{
	if (root == NULL)
		return root;
	while (root->right != NULL)
		root = root->right;/*不断搜索右子树*/
	return root;
}
/*查找最小值*/
BtreeNode* MinBtreeNode(BtreeNode *root)
{
	if (root == NULL)
		return root;
	while (root->left != NULL)
		root = root->left;/*不断搜索右子树*/
	return root;
}
/*查找前驱*/
BtreeNode* BeforeBtreeNode(BtreeNode *root)
{
	/*1、若一个节点有左子树,那么该节点的前驱节点是其左子树中val值最大的节点*/
	if (root->left != NULL)
		return MaxBtreeNode(root->left);
	/*2、若一个节点没有左子树,那么判断该节点和其父节点的关系 
	2.1 若该节点是其父节点的右边孩子,那么该节点的前驱结点即为其父节点。 
	2.2 若该节点是其父节点的左边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,
	直到找到一个有右子树的结点Q,那么Q就是该节点的前驱节点*/
	BtreeNode* parent = root->parent;
	while (parent != NULL && parent->left == root)/*root是左孩子*/
	{
		root = parent;
		parent = parent->parent;
	}
	return parent;
}
/*查找后继*/
BtreeNode* AfterBtreeNode(BtreeNode *root)
{
	/*1、1.若一个节点有右子树,那么该节点的后继节点是其右子树中val值最小的节点*/
	if (root->right != NULL)
		return MinBtreeNode(root->right);
	/*2/若一个节点没有右子树,那么判断该节点和其父节点的关系 
	2.1 若该节点是其父节点的左边孩子,那么该节点的后继结点即为其父节点 
	2.2 若该节点是其父节点的右边孩子,那么需要沿着其父亲节点一直向树的
	顶端寻找,直到找到一个有左孩子的节点Q,那么Q就是该节点的后继节点*/
	BtreeNode* parent = root->parent;
	while (parent != NULL && parent->right == root)/*root是右孩子*/
	{
		root = parent;
		parent = parent->parent;
	}
	return parent;
}

/*插入*/
void InsertBtreeNode(BtreeNode* &root, BtreeNode* key)
{
	BtreeNode* temp = root;/*建立一个临时指针指向根结点*/
	BtreeNode* tempParent = NULL;/*临时节点的父结点*/
	
	while (temp != NULL)/*根结点不为空*/
	{
		tempParent = temp;
		if (key->data < temp->data)/*小于根结点*/
			temp = temp->left;
		else
			temp = temp->right;
	}
	key->parent = tempParent;

	if (tempParent == NULL)/*树是空的*/
		root = key;
	else if (key->data < tempParent->data)
	{
		tempParent->left = key;
		key->parent = tempParent;
	}
	else
	{
		tempParent->right = key;
		key->parent = tempParent;
	}
}
/*子过程:移动子树,用子树v代替子树u*/
void Translant(BtreeNode* &root, BtreeNode* u, BtreeNode* v)
{
	if (u->parent == NULL)/*如果u是根结点*/
		root = v;/*v成为根结点*/
	else if (u == u->parent->left)/*u是左子树*/
		u->parent->left = v;
	else
		u->parent->right = v;
	if (v != NULL)
		v->parent = u->parent;
}
/*删除*/
void DeleteBtreeNode(BtreeNode* &root, BtreeNode* z)/*z是要删除的结点*/
{
	if (z->left == NULL)/*z没有左子树(也包括了z既没有左子树也没有右子树的情况)*/
		Translant(root, z, z->right);/*让z的右子树代替z,如果是第二种,那么交换后z=NULL*/
	else if (z->right == NULL)/*z没有右子树*/
		Translant(root, z, z->left);/*让z的左子树代替z*/
	else/*既有左子树也有右子树*/
	{
		BtreeNode* temp = MinBtreeNode(z->right);/*寻找z的后继:右子树的最小值*/
		if (temp->parent != z)/*如果它的后继的父结点不是z*/
		{
			Translant(root, temp, temp->right);/*让后继的右子树代替后继这个结点*/
			temp->right = z->right;/*这个后继肯定没有左孩子!*/
			temp->right->parent = temp;
		}
		Translant(root, z, temp);/*让后继取代要删除的结点*/
		temp->left = z->left;
		z->left->parent = temp;
	}
}

void BtreeTest()
{
	BtreeNode* root = (BtreeNode*)malloc(sizeof(BtreeNode));/*建立二叉树的根节点*/
	srand((unsigned)time(NULL));
	int i;

	/*初始化根节点*/
	root->data = rand() % 100 + 1;/*1-100之间的数*/
	root->parent = NULL;
	root->left = NULL;
	root->right = NULL;

	BtreeNode *six= (BtreeNode*)malloc(sizeof(BtreeNode));/*第六个结点*/
	BtreeNode *temp= (BtreeNode*)malloc(sizeof(BtreeNode));/*临时节点*/

	for (i = 0; i < 10; i++)
	{
		BtreeNode *key=(BtreeNode*)malloc(sizeof(BtreeNode));
		key->data = rand() % 200 + 1;/*1-200之间的数*/
		key->left = NULL;
		key->right = NULL;
		key->parent = NULL;

		if (i == 6)six = key;

		InsertBtreeNode(root, key);
	}
	cout << "插入10个数字后:" << endl;
	cout << "先序遍历:";
	PreBtreeNode(root); cout << endl;
	cout << "中序遍历:";
	MidBtreeNode(root); cout << endl;
	cout << "后序遍历:";
	PastBtreeNode(root);
	cout << endl;
	cout << endl;

	/*测试最小与最大的数*/
	temp = MinBtreeNode(root);
	cout << "最小的数是:" << temp->data << endl;
	temp = MaxBtreeNode(root);
	cout << "最大的数是:" << temp->data << endl;
	cout << endl;

	/*寻找第六个数的前驱和后继*/
	cout << "第六个数是:" << six->data << endl;
	temp = BeforeBtreeNode(six);
	if (temp != NULL)
		cout << "它的前驱是:" << temp->data << endl;
	else cout << "NULL!" << endl;
	temp = AfterBtreeNode(six);
	if (temp != NULL)
		cout << "它的后继是:" << temp->data << endl;
	else cout << "NULL!" << endl;
	cout << endl;

	cout << "删除第六个结点:" << endl;
	DeleteBtreeNode(root, six);
	cout << "先序遍历:";
	PreBtreeNode(root);
	cout << endl;
}
主函数
#include "二叉搜索树.h"
#include <stdio.h>

int main()
{
	BtreeTest();
	getchar(); 
	getchar(); 
	return 0;
}
运行结果:

在这里插入图片描述


参考网站以及博客:
二叉查找树的前驱后继(https://www.cnblogs.com/Renyi-Fan/p/8252227.html#_label1_0)
二叉查找树的删除操作(https://www.cnblogs.com/Renyi-Fan/p/8253136.html)
二叉查找树(二)之 C++的实现(http://www.cnblogs.com/skywang12345/p/3576373.html#aa1)
kaixingui2012的回答(https://zhidao.baidu.com/question/2266744263935050308.html)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值