有序二叉树的遍历查找与删除(超级详细)

关于有序二叉树的构建,请看我之前发布的博文:
二叉树的构建

1. 什么是有序二叉树

简而言之就是已经排好顺序的二叉树。这种二叉树中的数据按照一定顺序,从外界插入进来的数据不需要使用者自己排序,在插入数据的时候就已经自动排好序。这里实现的默认使用从小到大的排序方式。即从左到根到右数据是按照从小到大依次排序好的,有序二叉树又称为二叉搜索树。

2. 有序二叉树的结构

结构如图所示
在这里插入图片描述
可以看到根节点为5,则按照规则,下一个插入的数据如果比5要小,则插入到左子树,如果插入的数据比5大,则插入到右子树,同时子树的每个节点又按照相同的规则进行插入,1比3小插左边,6比3大插右边

3. 有序二叉树的构建准备代码

这里使用C++模板的知识封装一个有序二叉树,实际上原理是相同的,相信大家能够理解:

  1. 树的节点类型:数据 左孩子 右孩子
  2. 二叉树的类结构:根节点指针 插入 遍历 删除 查找等操作
  3. 同时注意把实现的成员函数放在类的私有里,以提高封装性与安全性
  4. 这棵树我们采用二级指针的形式,也可以使用引用,使用引用会更简单。
//二叉树节点类型
template <class T>
struct TreeNode{
	T			data;	//数据
	TreeNode*	pLeft;	//左孩子
	TreeNode*	pRight;	//右孩子

	TreeNode(const T& data){
		this->data = data;
		pLeft = pRight = NULL;
	}
};

//二叉树类型
template <class T>
class MyTree{
	TreeNode<T>*	pRoot;	//指向根节点的指针
public:
	MyTree(){ pRoot = NULL; }
	~MyTree(){ _clear(pRoot); }

	
	//遍历  type为-1 先序 type为0 中序 type为1 后序
	void travel(int type);
	void clear(TreeNode<T>* pDel){
		_clear(pDel);
	}

	//插入
	void insertNode(const T& data){
		_insertNode(&pRoot, data);
	}
	//查找
	TreeNode<T>* findNode(const T& data){
		return _findNode(pRoot, data);
	}

	//删除
	void deleteNode(const T& data);
private:
	//插入
	void _insertNode(TreeNode<T>** root, const T& data);
	//找树root中数据为data的第一个节点
	TreeNode<T>* _findNode(TreeNode<T>* root, const T& data);


	void _clear(TreeNode<T>* pDel);
	//先序
	void _preTravel(TreeNode<T>* root);
	//中序
	void _midTravel(TreeNode<T>* root);
	//后序
	void _lstTravel(TreeNode<T>* root);
};

4. 二叉树的插入

二叉树的数据是按照规律进行插入的,我们一步一步进行插入

  1. 首先我们假定根节点为 3,直接插入
  2. 然后我们插入节点为 2,比较2比3小,所以我们找到根节点的左孩子,插入
  3. 然后我们插入1,发现1比3小,所以我们找到根节点的第一个左孩子2,发现1还是比2小,然后我们再找到根节点的左孩子,这时没有节点,插入
  4. 然后我们插入5,5比3大,找到根节点的右孩子,插入
  5. 然后我们插入4,发现4比根节点3大,所以我们找到根节点的第一个右孩子5,发现此时4比5小,所以我们找到5的左孩子,为空,所以直接插入

可以看到,我们每次插入的时候都要从根节点重新开始遍历,直到找到合适的位置,然后进行插入,可以想到利用递归的特性。
在这里插入图片描述
代码示例:

//插入
template <class T>
void MyTree<T>::_insertNode(TreeNode<T>** root, const T& data){
	if (NULL == *root){
		*root = new TreeNode<T>(data);
		return;
	}

	if (data < (*root)->data){//往左
		_insertNode(&((*root)->pLeft), data);
	}
	else{//往右
		_insertNode(&((*root)->pRight), data);
	}
}

5. 二叉树的遍历

遍历分为前序 中序 和 后序
以此图为例:
在这里插入图片描述
前序遍历: 遍历顺序:根节点 左孩子 右孩子

先遍历根节点,再找到根节点左孩子,把左孩子再看做新的树,在遍历根节点,再找到左孩子,直到到达最小左孩子,接着逐渐返回查找右孩子。

先序结果:3 2 1 5 4

中序遍历:遍历顺序:左孩子 根节点 右孩子

先遍历根节点的左孩子,再找到根节点左孩子,把左孩子再看做新的树,在遍历左孩子,直到到达最小左孩子,接着逐级返回查找根节点,然后再逐级返回查找右孩子

中序结果:1 2 3 4 5 (中序遍历一定是有序的

后序遍历:遍历顺序:左孩子 右孩子 根节点

先遍历根节点的左孩子,再找到根节点左孩子,把左孩子再看做新的树,在遍历左孩子,直到到达最小左孩子,接着逐级返回查找右孩子,然后再逐级返回查找根节点

后序结果:1 2 4 5 3

利用递归特性,如果到达了空节点则此次递归结束,开始新一轮递归,直到左右的递归均已调用,则递归遍历结束

实现代码:

template <class T>
//先序
void MyTree<T>::_preTravel(TreeNode<T>* root){
	if (NULL == root)
		 return;
	cout << root->data << " ";
	_preTravel(root->pLeft);
	_preTravel(root->pRight);
}
template <class T>
//中序
void MyTree<T>::_midTravel(TreeNode<T>* root){
	if (NULL == root)
		 return;
	_midTravel(root->pLeft);
	cout << root->data << " ";
	_midTravel(root->pRight);
}
template <class T>
//后序
void MyTree<T>::_lstTravel(TreeNode<T>* root){
	if (NULL == root) 
		return;
	_lstTravel(root->pLeft);
	_lstTravel(root->pRight);
	cout << root->data << " ";
}
template <class T>
//遍历  type为-1 先序 type为0 中序 type为1 后序
void MyTree<T>::travel(int type){
	if (-1 == type){
		cout << "先序遍历:";
		_preTravel(pRoot);
		cout << endl;
	}
	else if (0 == type){
		cout << "中序遍历:";
		_midTravel(pRoot);
		cout << endl;
	}
	else if (1 == type){
		cout << "后序遍历:";
		_lstTravel(pRoot);
		cout << endl;
	}
}

6. 有序二叉树的查找

在这里插入图片描述
查找实现原理很简单,还是这个图,比如我们要查找2节点:从根节点开始,2比根节点3小,所以直接进入左孩子,此时元素等于2,返回;要查找4,首先从根节点开始,4比3大,进入右孩子,4比5小,所以进入左孩子,找到元素4;遍历完树了都没有找到则返回空。

实现代码:
注意:只找到第一个符合的节点,如果有多个相同元素节点**,哪个距离近,优先查找哪个**

template <class T>
//找树root中数据为data的第一个节点
TreeNode<T>* MyTree<T>::_findNode(TreeNode<T>* root, const T& data){
	TreeNode<T>* pTemp = root;
	while (pTemp){
		if (data == pTemp->data)
			 return pTemp;	//找打了节点
		if (data < pTemp->data){//往左
			pTemp = pTemp->pLeft;
		}
		else{//往右
			pTemp = pTemp->pRight;
		}
	}
	return NULL;	//没有找到返回空
}

7. 有序二叉树的删除:(重要)

有序二叉树的删除操作较复杂,可以先看以下我发布的一篇博文:
点这里!!三指针描述一棵树结构,可以先看此删除操作,来为接下来的有序二叉树删除做准备

删除情况列举:

7.1 删除根节点

规定:要删除的节点为pDel ;根节点为pRoot;要删除节点的父节点为pDelParent

  1. 如果根节点没有右孩子:

删除根节点 3:
在这里插入图片描述
可以注意到根节点没有右孩子,只有左孩子,可以直接删除根节点3即可,然后让左孩子成为新的根节点

伪代码描述:

  1. 根节点的左孩子直接成为新的根节点
  2. 释放原根节点

代码描述:

		//没有右孩子
		//pDel->pLeft 成为新的根节点
		pRoot = pDel->pLeft;
		delete pDel;
		return;
  1. 如果根节点有右孩子:

比如我们删除这个树的根节点 3: 该怎么删呢?
在这里插入图片描述
首先我们找到根节点pRoot的右孩子5,再找到5的最小左孩子4,我们把要删除节点的左孩子2,连接到4的左边,即成为4的左孩子,根节点的右孩子5成为新的根节点。
在这里插入图片描述
伪代码描述:

  1. 找到根节点的右孩子的最小左孩子(找到5,再找到4的位置)
  2. 让根节点的左孩子(2所在的位置及他的孩子)成为上面到达的位置的左孩子
  3. 根节点的右孩子成为新的根节点
  4. 释放原根节点

代码描述:

		if (pDel->pRight){//有右孩子
			//找到 pDel->pRight 的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}
			//pDel的左孩子成为 pDel->pRight 的 最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为新的根节点
			pRoot = pDel->pRight;
			//释放pDel
			delete pDel;
			return;
		}

7.2 删除非根节点

首先我们要找到要删除节点的父节点,因为在我们删除非根节点的时候,我们与上一个的连接会消失,所以我们要先利用其父亲来保存我们待删除的位置

找到待删除节点的父节点:
循环一次,我们的pDel指向下一个,我们的pDelParent指向其上一个,pDel找到了则结束,而pDelParent为pDel的上一个,这样就找到了待删除节点的父节点

//找pDel的父节点
	TreeNode<T>* pDelParent = NULL;

	pDel = pRoot;
	while (pDel){
		if (data == pDel->data)
			 break;
		pDelParent = pDel;	//父节点指向pDel

		if (data < pDel->data){//往左
			pDel = pDel->pLeft;
		}
		else{//往右
			pDel = pDel->pRight;
		}
	}
  1. 要删除的节点是其父节点左孩子
要删除节点没有右孩子

在这里插入图片描述
我们要删除的节点pDel是 4 ,可以知道其父节点为5,4没有右孩子,所以4的左孩子直接成为pDel的父节点的左孩子
在这里插入图片描述
伪代码描述:

  1. pDel的左孩子直接成为pDel的父节点的孩子
    注意前提:pDel是父节点的左孩子并且没有右孩子

代码描述:

	pDelParent->pLeft = pDel->pLeft;
要删除节点有右孩子

在这里插入图片描述
要删除的节点是4,其父节点是5,所以:找到4的右孩子9,再找到9的最小左孩子7让4的左孩子们成为7的左孩子,4的父亲连接9,删除4
在这里插入图片描述
伪代码描述:

  1. 找到pDel的右孩子的最小左孩子(节点7)
  2. 让pDel的左孩子们成为上面找到的位置的最小左孩子
  3. pDel的右孩子成为pDel的父节点的左孩子
    注意前提:pDel是父节点的左孩子并且pDel有右孩子

代码描述:

	if (pDel->pRight){//要删除的节点 有右孩子
			//要删除的节点的左孩子成为 pDel->pRight的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}//pTemp已经指向了 pDel->pRight的最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为 pDelParent的左孩子
			pDelParent->pLeft = pDel->pRight;
		}
  1. 要删除的节点是其父节点右孩子
要删除节点没有右孩子

在这里插入图片描述
删除的节点是6,它没有右孩子,其父节点是4,且是4的右孩子,则让4的右孩子直接连接6的左孩子9即可
在这里插入图片描述
伪代码描述:

  1. pDel的左孩子成为其父节点的右孩子
    注意前提:pDel是其父节点的右孩子且pDel无右孩子

代码描述:

	pDelParent->pRight = pDel->pLeft;
要删除节点有右孩子

在这里插入图片描述
删除的节点是6,父节点是4,6有右孩子和左孩子:先找到6的右孩子的最小左孩子8,让6的左孩子们连接8,6的右孩子成为父节点的右孩子
在这里插入图片描述
伪代码描述:

  1. 找到pDel的右孩子的最小左孩子(8节点)
  2. 让pDel 的左孩子成为上面找到位置的最小左孩子(5与8连接)
  3. pDel的右孩子成为pDel的父节点的右孩子
    注意前提:pDel是其父节点的右孩子并且pDel有右孩子

代码描述:

	if (pDel->pRight){//要删除的节点 有右孩子
			//要删除的节点的左孩子成为 pDel->pRight的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}//pTemp已经指向了 pDel->pRight的最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为 pDelParent的右孩子
			pDelParent->pRight = pDel->pRight;
		}

8. 代码测试

头文件:

#pragma once
#include <iostream>
using namespace std;


//二叉树节点类型
template <class T>
struct TreeNode{
	T			data;	//数据
	TreeNode*	pLeft;	//左孩子
	TreeNode*	pRight;	//右孩子

	TreeNode(const T& data){
		this->data = data;
		pLeft = pRight = NULL;
	}
};

//二叉树类型
template <class T>
class MyTree{
	TreeNode<T>*	pRoot;	//指向根节点的指针
public:
	MyTree(){ pRoot = NULL; }
	~MyTree()
	{
		_clear(pRoot); 
		cout << "释放成功" << endl;
	}


	
	//遍历  type为-1 先序 type为0 中序 type为1 后序
	void travel(int type);
	void clear(TreeNode<T>* pDel){
		_clear(pDel);
	}

	//插入
	void insertNode(const T& data){
		_insertNode(&pRoot, data);
	}
	//查找
	TreeNode<T>* findNode(const T& data){
		return _findNode(pRoot, data);
	}

	//删除
	void deleteNode(const T& data);
private:
	//插入
	void _insertNode(TreeNode<T>** root, const T& data);
	//找树root中数据为data的第一个节点
	TreeNode<T>* _findNode(TreeNode<T>* root, const T& data);


	void _clear(TreeNode<T>* pDel);
	//先序
	void _preTravel(TreeNode<T>* root);
	//中序
	void _midTravel(TreeNode<T>* root);
	//后序
	void _lstTravel(TreeNode<T>* root);
};

template <class T>
//找树root中数据为data的第一个节点
TreeNode<T>* MyTree<T>::_findNode(TreeNode<T>* root, const T& data){
	TreeNode<T>* pTemp = root;
	while (pTemp){
		if (data == pTemp->data) return pTemp;
		if (data < pTemp->data){//往左
			pTemp = pTemp->pLeft;
		}
		else{//往右
			pTemp = pTemp->pRight;
		}
	}
	return NULL;
}


template <class T>
//删除
void MyTree<T>::deleteNode(const T& data){
	TreeNode<T>* pDel = _findNode(pRoot, data);
	if (NULL == pDel){
		cout << "没找到,删除失败!" << endl;
		return;
	}
	TreeNode<T>* pTemp = NULL;
	if (pDel == pRoot){//要删除的节点是根节点
		if (pDel->pRight){//有右孩子
			//找到 pDel->pRight 的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}
			//pDel的左孩子成为 pDel->pRight 的 最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为新的根节点
			pRoot = pDel->pRight;
			//释放pDel
			delete pDel;
			//返回
			return;
		}
		//没有右孩子
		//pDel->pLeft 成为新的根节点
		pRoot = pDel->pLeft;
		//释放pDel
		delete pDel;
		return;
	}
	//要删除的节点不是根节点

	//找pDel的父节点
	TreeNode<T>* pDelParent = NULL;

	pDel = pRoot;
	while (pDel){
		if (data == pDel->data) break;
		pDelParent = pDel;

		if (data < pDel->data){//往左
			pDel = pDel->pLeft;
		}
		else{//往右
			pDel = pDel->pRight;
		}
	}

//	cout << "pDel:" << pDel->data << ",pDelParent:" << pDelParent->data << endl;
	if (pDel == pDelParent->pLeft){//要删除的节点是其父节点的左孩子
		if (pDel->pRight){//要删除的节点 有右孩子
			//要删除的节点的左孩子成为 pDel->pRight的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}//pTemp已经指向了 pDel->pRight的最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为 pDelParent的左孩子
			pDelParent->pLeft = pDel->pRight;
		}
		else{//要删除的节点木有右孩子
			//要删除的节点的左孩子 成为 pDelParent的左孩子
			pDelParent->pLeft = pDel->pLeft;
		}
	}
	else{//要删除的节点是其父节点的右孩子
		if (pDel->pRight){//要删除的节点 有右孩子
			//要删除的节点的左孩子成为 pDel->pRight的最左孩子
			pTemp = pDel->pRight;
			while (pTemp->pLeft){
				pTemp = pTemp->pLeft;
			}//pTemp已经指向了 pDel->pRight的最左孩子
			pTemp->pLeft = pDel->pLeft;
			//pDel->pRight 成为 pDelParent的右孩子
			pDelParent->pRight = pDel->pRight;
		}
		else{//要删除的节点木有右孩子
			//要删除的节点的左孩子 成为 pDelParent的右孩子
			pDelParent->pRight = pDel->pLeft;
		}
	}

	//释放
	delete pDel;
	return;
}


//插入
template <class T>
void MyTree<T>::_insertNode(TreeNode<T>** root, const T& data){
	if (NULL == *root){
		*root = new TreeNode<T>(data);
		return;
	}

	if (data < (*root)->data){//往左
		_insertNode(&((*root)->pLeft), data);
	}
	else{//往右
		_insertNode(&((*root)->pRight), data);
	}
}






template <class T>
//先序
void MyTree<T>::_preTravel(TreeNode<T>* root){
	if (NULL == root) return;
	cout << root->data << " ";
	_preTravel(root->pLeft);
	_preTravel(root->pRight);
}
template <class T>
//中序
void MyTree<T>::_midTravel(TreeNode<T>* root){
	if (NULL == root) return;
	_midTravel(root->pLeft);
	cout << root->data << " ";
	_midTravel(root->pRight);
}
template <class T>
//后序
void MyTree<T>::_lstTravel(TreeNode<T>* root){
	if (NULL == root) return;
	_lstTravel(root->pLeft);
	_lstTravel(root->pRight);
	cout << root->data << " ";
}
template <class T>
//遍历  type为-1 先序 type为0 中序 type为1 后序
void MyTree<T>::travel(int type){
	if (-1 == type){
		cout << "先序遍历:";
		_preTravel(pRoot);
		cout << endl;
	}
	else if (0 == type){
		cout << "中序遍历:";
		_midTravel(pRoot);
		cout << endl;
	}
	else if (1 == type){
		cout << "后序遍历:";
		_lstTravel(pRoot);
		cout << endl;
	}
}


template <class T>
void MyTree<T>::_clear(TreeNode<T>* pDel){
	
}

测试代码:

#include "MyTree.h"

int main(){
	int arr[] = { 1, 9, 7, 8, 2, 4, 3, 5, 6, 1, 9, 7, 23, 666 };
	MyTree<int> t;
	//插入
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		t.insertNode(arr[i]);
	}
	//遍历
	t.travel(-1);
	t.travel(0);
	t.travel(1);
	//删除
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
		t.deleteNode(arr[i]);
		t.travel(0);
	}

	return 0;
}

在这里插入图片描述

posted @ 2022-08-05 14:21  hugeYlh  阅读(75)  评论(0编辑  收藏  举报  来源