1 相关概念
1.1 定义
一棵空树,或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的结点。
1.2 性质
- 对二叉树进行中序遍历,可以得到一个递增的有序序列。
- 插入的新结点一定是某个叶子结点。
2 二叉排序树的实现
2.1 结点结构
struct BSTNode {
int data;
BSTNode *lChild, *rChild;
BSTNode(int a) {
data = a;
lChild = NULL;
rChild = NULL;
}
};
2.2 二叉排序树的基本框架(BSTree.hpp)
class BSTree {
BSTNode *root;
public:
BSTree();
~BSTree();
///创建二叉排序树
void create();
///获取根节点
BSTNode *getRoot();
///输出该排序树
void print();
///先序遍历输出。
void preOrder(BSTNode *p);
///中序遍历输出。
void midOrder(BSTNode *p);
///插入值x
void insert(int x);
///销毁二叉树
void destroy(BSTNode* p);
///搜索值为x的结点并返回结点指针
BSTNode* search(int x);
///返回直接前驱--方法1
BSTNode* preNode_1(BSTNode* p);
///返回直接前驱--方法2
BSTNode* preNode_2(BSTNode* p);
void preNodeStack(BSTNode* subTree, stack<BSTNode*> &s, BSTNode* p);//辅助函数
///返回直接后继--方法1:
BSTNode* postNode_1(BSTNode* p);
///返回直接后继--方法2:使用栈将中序遍历中结点x和其后的一个结点压入栈中
BSTNode* postNode_2(BSTNode* x);
void postNodeStack(BSTNode* p, stack<BSTNode*> &s, BSTNode* x, int &num, bool &open);//辅助函数
///返回子树最大值:一直遍历所给结点的右子树
BSTNode* subTreeMax(BSTNode* p);
///返回子树最小值:一直遍历所给结点的左子树
BSTNode* subTreeMin(BSTNode* p);
///返回某个节点的父节点
BSTNode* parent(BSTNode* subTree, BSTNode* current);
///删除值为x的结点
void remove(int x) {
rmvRecursion(search(x));
}
void rmvRecursion(BSTNode* p); //删除操作时的辅助递归函数
};
2.3 成员函数详解
2.3.1 创建二叉排序树
将从命令行输入的数字依次调用insert()函数构建二叉排序树。
假如我们要根据输入的[62, 88, 58, 47, 35, 73, 51, 99, 37,93]构建二叉排序树,插入过程是这样的:
- 在初始化状态下我们二叉排序树的根节点为空,我们依次将集合中的元素通过搜索插入到二叉排序树中合适的位置。
- 首先在二叉排序中进行搜索62的位置,树为空,所以将62存入到二叉排序树的根节点中,及root=(62)。
- 从集合中取出88,然后查找我们的二叉排序树,发现88大于我们的根节点62,所以将88插入到62节点的右子树中,即(62)->rchild=(88)。
- 从集合中取出58,然后从根节点开始查找我们现有的二叉排序树,发现55<62,将55作为62的左结点,即(62)->lchild=(55)。
- 从集合中取出47,然后对二叉排序树进行搜索,发现47<55, 所以(55)->leftChild=(47)。
- 从集合中取出35,再次对二叉排序树进行搜索,发现35又小于47,所以(47)->leftChild=(35)。
- 从集合中取出73,再次对二叉排序树进行搜索,发现62<73<88, 所以有(88)->leftChild=(73)。
以此类推,要做的事情就是不断从集合中取值,然后对二叉排序树进行查找,找到合适的插入点,然后将相应的节点进行插入,具体步骤就不做过多赘述了。
///创建二叉排序树
void create() {
int temp;
cout << "please input: " << endl;
while (cin >> temp) {
insert(temp);
}
}
2.3.2 插入值为x的结点
- 递归地和分支结点比大小
- 比分支节点大(小)且其右节点(左节点)为空,则在此创建该节点
///插入值x
void insert(int x) {
if (root==NULL) {
root = new BSTNode(x);
return;
}
BSTNode *p = root;
while (p)
{
if (x > p->data) {
if (p->rChild == NULL) {
p->rChild = new BSTNode(x);
return;
}
p = p->rChild;
}
else {
if (p->lChild == NULL) {
p->lChild = new BSTNode(x);
return;
}
p = p->lChild;
}
}
}
2.3.3 输出该排序树
本文重点不在介绍各种遍历算法,如对先序、中序、后序遍历的递归和非递归算法感兴趣请查阅:二叉树成员函数代码实现及简单原理分析(C++)
///输出该排序树
void print() {
cout << "中序遍历为: ";
midOrder(root);
cout << endl;
cout << "先序遍历为: ";
preOrder(root);
cout << endl;
}
///先序遍历输出
void preOrder(BSTNode *p) {
if (p != NULL) {
cout << p->data << " ";
preOrder(p->lChild);
preOrder(p->rChild);
}
}
///中序遍历输出
void midOrder(BSTNode *p) {
if (p != NULL) {
midOrder(p->lChild);
cout << p->data << " ";
midOrder(p->rChild);
}
}
2.3.4 获取根节点
///获取根节点
BSTNode *getRoot() {
return root;
}
2.3.5 销毁二叉树
使用递归的方式销毁二叉树
///销毁二叉树
void destroy(BSTNode* p)
{
if(p){
destroy(p->lChild);
destroy(p->rChild);
delete p;
}
}
2.3.6 搜索值为x的结点并返回结点指针
和分支节点比较大小直到找到值为x的结点。
///搜索值为x的结点并返回结点指针
BSTNode* search(int x) {
BSTNode* p = root;
while (p) {
if (p->data == x)
return p;
if (x > p->data)
p = p->rChild;
else
p = p->lChild;
}
return NULL;
}
2.3.7 返回直接前驱
这里有两种方法:一是分类讨论;二是使用栈将中序遍历到x的所有值保存到栈中。
///返回直接前驱--方法1
BSTNode* preNode_1(BSTNode* p) {
if (p->lChild != NULL)
return subTreeMax(p->lChild); //若左子树不为空,则返回左子树最大节点
else
{
BSTNode *parentOfp = parent(root, p); //返回第一个右节点为p(或p的祖先节点)的祖先节点
while (parentOfp!=NULL && parentOfp->lChild == p)
{
BSTNode * temp = parentOfp;
parentOfp = parent(root,parentOfp);
p = temp;
}
return parentOfp;
}
}
///返回直接前驱--方法2
BSTNode* preNode_2(BSTNode* p) { //使用中序遍历,将x之前的结点都保存在栈中
stack<BSTNode*> s;
preNodeStack(root, s, p);
if (s.size() < 2)
return NULL;
s.pop();
return s.top();
}
void preNodeStack(BSTNode* subTree, stack<BSTNode*> &s, BSTNode* p) {
if (subTree != NULL)
{
preNodeStack(subTree->lChild,s,p);
if (!s.empty() && s.top() == p)
return;
s.push(subTree);
preNodeStack(subTree->rChild,s,p);
}
}
2.3.8 返回直接后继
这里有两种方法:一是分类讨论;二是使用栈将中序遍历到x后一个结点的所有值保存到栈中。
///返回直接后继--方法1:
BSTNode* postNode_1(BSTNode* p){
if (p->rChild != nullptr) //如果该节点的右节点不为空,直接返回右子树的最小值,其就是直接后继
return subTreeMin(p->rChild);
else {
BSTNode *parentOfp = parent(root, p); //返回第一个左节点为p(或迭代后的p的祖先节点)的祖先节点
while (parentOfp != NULL && parentOfp->rChild == p)
{
BSTNode * temp = parentOfp;
parentOfp = parent(root, parentOfp);
p = temp;
}
return parentOfp;
}
}
///返回直接后继--方法2:使用栈将中序遍历中结点x和其后的一个结点压入栈中
BSTNode* postNode_2(BSTNode* x) {
stack<BSTNode*> s;
int num = 0;
bool open = false;
postNodeStack(root, s, x, num, open);
if (open == true && s.top()!=x)
return s.top();
else
return NULL;
}
void postNodeStack(BSTNode* p, stack<BSTNode*> &s, BSTNode* x,int &num,bool &open) {
//当该节点入栈后,open置为true,只允许num累加一次(栈只能再压栈一次,从而获得了直接后继)
if (p != NULL)
{
postNodeStack(p->lChild, s, x,num,open);
if (num >= 1)
return; //num从0->1之后,之后都直接返回,不再进行压栈
s.push(p);
if (open)
num++;
if (s.top() == x)
open=true;
postNodeStack(p->rChild, s, x, num, open);
}
}
2.3.9 返回子树最大值
///返回子树最大值:一直遍历所给结点的右子树
BSTNode* subTreeMax(BSTNode* p) {
while (p) {
if (p->rChild == NULL)
return p;
else {
p = p->rChild;
}
}
return NULL;
}
2.3.10 返回子树最小值
///返回子树最小值:一直遍历所给结点的左子树
BSTNode* subTreeMin(BSTNode* p) {
while (p) {
if (p->lChild == NULL)
return p;
else {
p = p->lChild;
}
}
return NULL;
}
2.3.11 返回某个节点的父节点
使用递归的形式返回父节点。
///返回某个节点的父节点
BSTNode* parent(BSTNode* subTree, BSTNode* current) {
if (subTree == NULL)
return NULL;
if (subTree->lChild == current || subTree->rChild == current)
return subTree;
BSTNode* p;
if ((p = parent(subTree->lChild, current)) != NULL)
return p;
else
return parent(subTree->rChild, current);
}
2.3.12 删除值为x的结点
二叉排序树删除结点,主要分为三种情况讨论:
- 叶子节点-直接删除就可以了
- 没有左孩子的节点-直接嫁接右子树就可以了(没有右孩子的节点-直接嫁接左子树就可以了)
- 如果左右子树都存在,则令z的直接前驱(或直接后继)替代z,然后转变为从排序树中删除这个直接前驱或直接后继–这样就转换成了第一或第二种情况。
///删除值为x的结点
void remove(int x) {
rmvRecursion(search(x));
}
void rmvRecursion(BSTNode* p) {
if (p->lChild == NULL && p->rChild == NULL) {
//情况1:删除叶子结点或者根节点时
BSTNode* q = parent(root, p);
if (!q) { //无父节点时为根节点,直接删除,主意要将root赋为NULL
delete root;
root=NULL;
return;
}
if (q->lChild == p)
q->lChild = NULL;
else
q->rChild = NULL;
delete p;
}
else
{
if (p->lChild != NULL && p->rChild != NULL) {
//情况2:删除的结点左右结点都存在
BSTNode* q = preNode_1(p); //用直接前驱的值取代当前值,转换为删除直接前驱
p->data = q->data;
rmvRecursion(q);
}
else { //用左节点/右节点 取代当前结点
//情况3:删除的结点只存在左/右结点
if (p->lChild != NULL) {
p->data = p->lChild->data;
BSTNode* temp = p->lChild;
p->lChild = temp->lChild;
p->rChild = temp->rChild;
delete temp;
}
else {
p->data = p->rChild->data;
BSTNode* temp = p->rChild;
p->lChild = temp->lChild;
p->rChild = temp->rChild;
delete temp;
}
}
}
}
2.4 完整代码
/*******二叉排序树***********/
#pragma once
#include<iostream>
#include<Stack>
#include<Queue>
using namespace std;
struct BSTNode {
int data;
BSTNode *lChild, *rChild;
BSTNode(int a) {
data = a;
lChild = NULL;
rChild = NULL;
}
};
class BSTree {
BSTNode *root;
public:
BSTree() {
root = NULL;
}
~BSTree() {
destroy(root);
}
///创建二叉排序树
void create() {
int temp;
cout << "please input: " << endl;
while (cin >> temp) {
insert(temp);
}
}
///获取根节点
BSTNode *getRoot() {
return root;
}
///输出该排序树
void print() {
cout << "中序遍历为: ";
midOrder(root);
cout << endl;
cout << "先序遍历为: ";
preOrder(root);
cout << endl;
}
///先序遍历输出。如需了解先序遍历的非递归实现,请查看:
void preOrder(BSTNode *p) {
if (p != NULL) {
cout << p->data << " ";
preOrder(p->lChild);
preOrder(p->rChild);
}
}
///中序遍历输出。如需了解中序遍历的非递归实现,请查看:
void midOrder(BSTNode *p) {
if (p != NULL) {
midOrder(p->lChild);
cout << p->data << " ";
midOrder(p->rChild);
}
}
///插入值x
void insert(int x) {
if (root==NULL) {
root = new BSTNode(x);
return;
}
BSTNode *p = root;
while (p)
{
if (x > p->data) {
if (p->rChild == NULL) {
p->rChild = new BSTNode(x);
return;
}
p = p->rChild;
}
else {
if (p->lChild == NULL) {
p->lChild = new BSTNode(x);
return;
}
p = p->lChild;
}
}
}
///销毁二叉树
void destroy(BSTNode* p)
{
if(p){
destroy(p->lChild);
destroy(p->rChild);
delete p;
}
}
///搜索值为x的结点并返回结点指针
BSTNode* search(int x) {
BSTNode* p = root;
while (p) {
if (p->data == x)
return p;
if (x > p->data)
p = p->rChild;
else
p = p->lChild;
}
return NULL;
}
///返回直接前驱--方法1
BSTNode* preNode_1(BSTNode* p) {
if (p->lChild != NULL)
return subTreeMax(p->lChild); //若左子树不为空,则返回左子树最大节点
else
{
BSTNode *parentOfp = parent(root, p); //返回第一个右节点为p(或p的祖先节点)的祖先节点
while (parentOfp!=NULL && parentOfp->lChild == p)
{
BSTNode * temp = parentOfp;
parentOfp = parent(root,parentOfp);
p = temp;
}
return parentOfp;
}
}
///返回直接前驱--方法2
BSTNode* preNode_2(BSTNode* p) { //使用中序遍历,将x之前的结点都保存在栈中
stack<BSTNode*> s;
preNodeStack(root, s, p);
if (s.size() < 2)
return NULL;
s.pop();
return s.top();
}
void preNodeStack(BSTNode* subTree, stack<BSTNode*> &s, BSTNode* p) {
if (subTree != NULL)
{
preNodeStack(subTree->lChild,s,p);
if (!s.empty() && s.top() == p)
return;
s.push(subTree);
preNodeStack(subTree->rChild,s,p);
}
}
///返回直接后继--方法1:
BSTNode* postNode_1(BSTNode* p){
if (p->rChild != nullptr) //如果该节点的右节点不为空,直接返回右子树的最小值,其就是直接后继
return subTreeMin(p->rChild);
else {
BSTNode *parentOfp = parent(root, p); //返回第一个左节点为p(或迭代后的p的祖先节点)的祖先节点
while (parentOfp != NULL && parentOfp->rChild == p)
{
BSTNode * temp = parentOfp;
parentOfp = parent(root, parentOfp);
p = temp;
}
return parentOfp;
}
}
///返回直接后继--方法2:使用栈将中序遍历中结点x和其后的一个结点压入栈中
BSTNode* postNode_2(BSTNode* x) {
stack<BSTNode*> s;
int num = 0;
bool open = false;
postNodeStack(root, s, x, num, open);
if (open == true && s.top()!=x)
return s.top();
else
return NULL;
}
void postNodeStack(BSTNode* p, stack<BSTNode*> &s, BSTNode* x,int &num,bool &open) {
//当该节点入栈后,open置为true,只允许num累加一次(栈只能再压栈一次,从而获得了直接后继)
if (p != NULL)
{
postNodeStack(p->lChild, s, x,num,open);
if (num >= 1)
return; //num从0->1之后,之后都直接返回,不再进行压栈
s.push(p);
if (open)
num++;
if (s.top() == x)
open=true;
postNodeStack(p->rChild, s, x, num, open);
}
}
///返回子树最大值:一直遍历所给结点的右子树
BSTNode* subTreeMax(BSTNode* p) {
while (p) {
if (p->rChild == NULL)
return p;
else {
p = p->rChild;
}
}
return NULL;
}
///返回子树最小值:一直遍历所给结点的左子树
BSTNode* subTreeMin(BSTNode* p) {
while (p) {
if (p->lChild == NULL)
return p;
else {
p = p->lChild;
}
}
return NULL;
}
///返回某个节点的父节点
BSTNode* parent(BSTNode* subTree, BSTNode* current) {
if (subTree == NULL)
return NULL;
if (subTree->lChild == current || subTree->rChild == current)
return subTree;
BSTNode* p;
if ((p = parent(subTree->lChild, current)) != NULL)
return p;
else
return parent(subTree->rChild, current);
}
///删除值为x的结点
void remove(int x) {
rmvRecursion(search(x));
}
void rmvRecursion(BSTNode* p) {
if (p->lChild == NULL && p->rChild == NULL) {
BSTNode* q = parent(root, p);
if (!q) { //无父节点时为根节点,直接删除
delete root;
root=NULL;
return;
}
if (q->lChild == p)
q->lChild = NULL;
else
q->rChild = NULL;
delete p;
}
else
{
if (p->lChild != NULL && p->rChild != NULL) {
BSTNode* q = preNode_1(p); //用直接前驱的值取代当前值,转换为删除直接前驱
p->data = q->data;
rmvRecursion(q);
}
else { //用左节点/右节点 取代当前结点
if (p->lChild != NULL) {
p->data = p->lChild->data;
BSTNode* temp = p->lChild;
p->lChild = temp->lChild;
p->rChild = temp->rChild;
delete temp;
}
else {
p->data = p->rChild->data;
BSTNode* temp = p->rChild;
p->lChild = temp->lChild;
p->rChild = temp->rChild;
delete temp;
}
}
}
}
};
3 测试代码(main.cpp)
///二叉排序树(BST)
BSTree bst;
BSTNode *p=NULL;
bst.create();
bst.print();
cout << "直接前驱:" << endl;
int iterTimes = 7; //输入数字为1-6
for (int i = 1; i < iterTimes; i++) {
p = bst.preNode_1(bst.search(i));
if (p)
cout << i << " : " << p->data << endl;
}
cout << "直接后继:" << endl;
for (int i = 1; i < iterTimes; i++) {
// p = bst.postNode_1(bst.search(i));
p = bst.postNode_2(bst.search(i));
if (p)
cout <<i<<" : "<< p->data << endl;
}
cout << "子树最大值:" << endl;
for (int i = 1; i < iterTimes; i++) {
cout << i<<" :"<<bst.subTreeMax(bst.search(i))->data << endl;
}
cout << "子树最小值:" << endl;
for (int i = 1; i < iterTimes; i++) {
cout << i << " :" << bst.subTreeMin(bst.search(i))->data << endl;
}
cout << "父节点:" << endl;
for (int i = 1; i < iterTimes; i++) {
p = bst.parent(bst.getRoot(), bst.search(i));
if (p)
cout << i << " : " << p->data << endl;
}
// cout << bst.search(4)->rChild->data;
cout << "删除结点:" << endl;
for (int i = 1; i < iterTimes; i++) {
cout << "删除" << i << " 后: " << endl;
bst.remove(i);
if (bst.getRoot()) {
cout << "根节点为" << bst.getRoot()->data << endl;
bst.print();
}
cout << endl;
}
命令行输入:4 2 1 3 5 6 ^Z //^Z为ctrl + z,表示输入终止
输出 :
4 参考资料
1.月雲之霄.二叉排序树:https://blog.csdn.net/isunbin/article/details/81703762
2.百度百科.二叉排序树:https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%8E%92%E5%BA%8F%E6%A0%91/10905079?fr=aladdin
欢迎批评指正!!