有序二叉树的遍历查找与删除(超级详细)
关于有序二叉树的构建,请看我之前发布的博文:
二叉树的构建
文章目录
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; }
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209747.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)