关于有序二叉树的构建,请看我之前发布的博文:
二叉树的构建
文章目录
1. 什么是有序二叉树
简而言之就是已经排好顺序的二叉树。这种二叉树中的数据按照一定顺序,从外界插入进来的数据不需要使用者自己排序,在插入数据的时候就已经自动排好序。这里实现的默认使用从小到大的排序方式。即从左到根到右数据是按照从小到大依次排序好的,有序二叉树又称为二叉搜索树。
2. 有序二叉树的结构
结构如图所示
可以看到根节点为5,则按照规则,下一个插入的数据如果比5要小,则插入到左子树,如果插入的数据比5大,则插入到右子树,同时子树的每个节点又按照相同的规则进行插入,1比3小插左边,6比3大插右边
3. 有序二叉树的构建准备代码
这里使用C++模板的知识封装一个有序二叉树,实际上原理是相同的,相信大家能够理解:
- 树的节点类型:数据 左孩子 右孩子
- 二叉树的类结构:根节点指针 插入 遍历 删除 查找等操作
- 同时注意把实现的成员函数放在类的私有里,以提高封装性与安全性
- 这棵树我们采用二级指针的形式,也可以使用引用,使用引用会更简单。
//二叉树节点类型
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. 二叉树的插入
二叉树的数据是按照规律进行插入的,我们一步一步进行插入
- 首先我们假定根节点为 3,直接插入
- 然后我们插入节点为 2,比较2比3小,所以我们找到根节点的左孩子,插入
- 然后我们插入1,发现1比3小,所以我们找到根节点的第一个左孩子2,发现1还是比2小,然后我们再找到根节点的左孩子,这时没有节点,插入
- 然后我们插入5,5比3大,找到根节点的右孩子,插入
- 然后我们插入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
删除根节点 3:
可以注意到根节点没有右孩子,只有左孩子,可以直接删除根节点3即可,然后让左孩子成为新的根节点。
伪代码描述:
- 根节点的左孩子直接成为新的根节点
- 释放原根节点
代码描述:
//没有右孩子
//pDel->pLeft 成为新的根节点
pRoot = pDel->pLeft;
delete pDel;
return;
比如我们删除这个树的根节点 3: 该怎么删呢?
首先我们找到根节点pRoot的右孩子5,再找到5的最小左孩子4,我们把要删除节点的左孩子2,连接到4的左边,即成为4的左孩子,根节点的右孩子5成为新的根节点。
伪代码描述:
- 找到根节点的右孩子的最小左孩子(找到5,再找到4的位置)
- 让根节点的左孩子(2所在的位置及他的孩子)成为上面到达的位置的左孩子
- 根节点的右孩子成为新的根节点
- 释放原根节点
代码描述:
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;
}
}
要删除节点没有右孩子
我们要删除的节点pDel是 4 ,可以知道其父节点为5,4没有右孩子,所以4的左孩子直接成为pDel的父节点的左孩子
伪代码描述:
- pDel的左孩子直接成为pDel的父节点的孩子
注意前提:pDel是父节点的左孩子并且没有右孩子
代码描述:
pDelParent->pLeft = pDel->pLeft;
要删除节点有右孩子
要删除的节点是4,其父节点是5,所以:找到4的右孩子9,再找到9的最小左孩子7,让4的左孩子们成为7的左孩子,4的父亲连接9,删除4
伪代码描述:
- 找到pDel的右孩子的最小左孩子(节点7)
- 让pDel的左孩子们成为上面找到的位置的最小左孩子
- 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;
}
要删除节点没有右孩子
要删除的节点是6,它没有右孩子,其父节点是4,且是4的右孩子,则让4的右孩子直接连接6的左孩子9即可
伪代码描述:
- pDel的左孩子成为其父节点的右孩子
注意前提:pDel是其父节点的右孩子且pDel无右孩子
代码描述:
pDelParent->pRight = pDel->pLeft;
要删除节点有右孩子
要删除的节点是6,父节点是4,6有右孩子和左孩子:先找到6的右孩子的最小左孩子8,让6的左孩子们连接8,6的右孩子成为父节点的右孩子
伪代码描述:
- 找到pDel的右孩子的最小左孩子(8节点)
- 让pDel 的左孩子成为上面找到位置的最小左孩子(5与8连接)
- 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;
}