阅读 100

二叉排序树 成员函数实现详解(C++)

1 相关概念

1.1 定义

一棵空树,或者是具有下列性质的二叉树:

  • 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 左、右子树也分别为二叉排序树;
  • 没有键值相等的结点。

1.2 性质

  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]构建二叉排序树,插入过程是这样的:

  1. 在初始化状态下我们二叉排序树的根节点为空,我们依次将集合中的元素通过搜索插入到二叉排序树中合适的位置。
  2. 首先在二叉排序中进行搜索62的位置,树为空,所以将62存入到二叉排序树的根节点中,及root=(62)。
  3. 从集合中取出88,然后查找我们的二叉排序树,发现88大于我们的根节点62,所以将88插入到62节点的右子树中,即(62)->rchild=(88)。
  4. 从集合中取出58,然后从根节点开始查找我们现有的二叉排序树,发现55<62,将55作为62的左结点,即(62)->lchild=(55)。
  5. 从集合中取出47,然后对二叉排序树进行搜索,发现47<55, 所以(55)->leftChild=(47)。
  6. 从集合中取出35,再次对二叉排序树进行搜索,发现35又小于47,所以(47)->leftChild=(35)。
  7. 从集合中取出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.月雲之霄.二叉排序树:blog.csdn.net/isunbin/art…
2.百度百科.二叉排序树:baike.baidu.com/item/二叉排序树/…

            欢迎批评指正!!