二叉搜索树的插入,删除,遍历操作详解

来源:互联网 发布:至此终年网络番外怀孕 编辑:程序博客网 时间:2024/04/28 14:35
今天来详细介绍下二叉搜索树的一些操作以及代码实现;参考书籍:《算法导论第三版》(二叉搜索树的相关章节),《数据结构与算法》


一,二叉搜索树的定义

一颗二叉搜索树是以一颗二叉树来组织的,这样的一棵树可以使用一个链表数据结构来表示:

struct TreeNode{TreeNode* le;   //需要注意,C语言中需要这样写:struct TreeNode* le,下同,写惯了C++的人这里是一个小坑TreeNode* rh;int value;
//有些时候会加上父亲节点   TreeNode* pa;}


现在我们有了树的数据结构,可以给出二叉树的定义了: 设 X是二叉搜索树中的一个节点。如果Y是 X左子树中的一个节点,那么 必定有: Y.value<= X.value。如果Y是X右子树中的一个节点,那么,必定有Y.value>=X.value ;

这是算法导论上的定义,翻译一下就是对于任意一个二叉搜索树中的节点,左子树中的任意一个节点的值都会小于它的值,右子树中的任意一个节点的值都会大于它的

二叉搜索树本身是自排序的,看定义就明白了,一直往右是最大值,一直往左是最小值,因此二叉搜索树可以作为一个优先级队列,这个以后介绍。
二叉搜索树的基本操作所花费的时间和这个树的高度是成正比的,对于一个有N个节点的二叉搜索树的各种操作的平均时间是O(logn); 当极度不平衡时,即任意节点没
有左子树或者右子树时,退化为链表;

二,二叉搜索树的插入操作

插入操作挺好理解的,就是从根节点开始往下走,如果插入的值大于等于该节点的值就往右子树走,否则就往左子树走,这里我就直接贴代码了,代码中有详细的注释
#include<iostream>using namespace std;struct TreeNode{TreeNode* le;TreeNode* rh;int value;};void addNodeToSearchBT(TreeNode** ptrPtrRoot, int k){TreeNode* pa = nullptr;  //用于保存插入位置的父节点,因为下面循环中出来的节点是nullptrauto root = *ptrPtrRoot;while (root != nullptr){//循环遍历比较,判断该往左走还是往右走pa = root;if (k >= root->value)root = root->rh;else if (k < root->value)root = root->le;}TreeNode* newNode = new TreeNode;newNode->le = nullptr;newNode->rh = nullptr;newNode->value = k;//这儿就是简单的判断大小然后插入就可以了if (pa == nullptr){*ptrPtrRoot = newNode;   //二叉树为空return;}if (k >= pa->value)pa->rh = newNode;else if (k < pa->value)pa->le = newNode;}


三,二叉搜索树的删除操作

这里感觉第一次看还是比较复杂的,会详细的介绍,一般来说都是使用被删除节点的右子树中的最小值作为替换节点,所以下面就不讨论使用左子树的最大值作为替换
节点的情况了;首先第一步当然是找到这个要被删除的节点,假设这个节点是D, 需要注意的是,我们这里并没有使用双向链,所需需要保存D节点的父亲节点,因为,拼接子树的时候
需要用到父亲节点,这里假设为Dpa;,被用来替换的节点假设为X;1.如果D是叶子节点,直接删除就OK,见图一2.如果D有子树,并且只有右子树,那也就是可以直接将D的右子树接到Dpa上就可以,见图二3.如果D有子树,并且只有左子树,那么直接将D的左子树接到Dpa上就可以,见图三4.如果D有两个子树,那么就找到右子树中的最小值,也就是从D开始一直往左走,直到走到叶子左子树为空的节点,那么我们就找到可以被用来替换的节点了,见图四

图一                              图二

             图三图四


接下来只需要把X接到D的位置上,并且把X的右子树接到它的父节点的左子树上就可以了;下面把代码贴上来,代码中注释的很详细

TreeNode* findNodeInSearchBT(TreeNode*root, int k, TreeNode**pa = nullptr){//这里是不需要进行null判断的,因为后面隐式的判断了是否为nullwhile (root != nullptr){if (k > root->value){*pa = root;root = root->rh;}else if (k < root->value){*pa = root;root = root->le;}elsebreak;  //找到了相应的节点}//找到了相应的节点就返回该节点,若不存在该节点的父节点则返回nullptrreturn root;}bool deleteNodeInSearchBT(TreeNode*root, int k){TreeNode*PatoDelete = nullptr;     //用于保存D的父节点TreeNode* toDelete = findNodeInSearchBT(root, k, &PatoDelete);    //找到D节点//需要先判断一下是否找到了这个节点,没找到就直接返回falseif (nullptr == toDelete)return false;//D节点没有左子树,或者左右子树都没有  第一第二中情况if (toDelete->le == nullptr){if (k < PatoDelete->value)PatoDelete->le = toDelete->rh;   //需要把这个父节点的左节点替换为用来替换的节点elsePatoDelete->rh = toDelete->rh;    //这里就是右子树需要被替换了delete toDelete;return true;}//D节点没有右子树第三种情况if (toDelete->rh == nullptr){if (k < PatoDelete->value)PatoDelete->le = toDelete->le;   //需要把这个父节点的左节点替换为用来替换的节点elsePatoDelete->rh = toDelete->le;    //这里就是右子树需要被替换了delete toDelete;return true;}//D节点左右子树都有,第四种情况auto minInRh = toDelete;TreeNode* minInRhpa = nullptr;    //这里用来保存用来替换的节点的父节点while (minInRh->le != nullptr){minInRhpa = minInRh;minInRh = minInRh->le;}   //找到X节点,保存了其父节点//其实这里就是对X做了一次删除操作,只不过这个X一定是第一种或者第二种情况minInRhpa->le = minInRh->rh;  //这里右子树是否非空是没有关系的;直接对X使用第一或者第二中方法删除,然后用X替换D //需要把用来替换的节点的左右子树换成被删除子树的左右子树,不然就把树给弄断了minInRh->le = toDelete->le;minInRh->rh = toDelete->rh;//一切准备就绪,可以替换了if (k < PatoDelete->value)PatoDelete->le = minInRh;   //需要把这个父节点的左节点替换为用来替换的节点elsePatoDelete->rh = minInRh;    //这里就是右子树需要被替换了//注意不要忘记释放空间了delete toDelete;//走到了这里说明一切都很顺利,可以返回true了return true;}

三,遍历操作

中序遍历:左子节点--父节点--右子节点

void midTraverse(TreeNode*root){if (root == nullptr) return;midTraverse(root->le);        cout << root->value;midTraverse(root->rh);}



前序遍历:父节点--左子节点--右子节点

void midTraverse(TreeNode*root){if (root == nullptr) return;        cout << root->value;midTraverse(root->le);midTraverse(root->rh);}

后序遍历:左子节点--右子节点--父节点

void midTraverse(TreeNode*root){if (root == nullptr) return;midTraverse(root->le);midTraverse(root->rh);        cout << root->value;}


四,二叉树的重建---通过三种遍历方式中的两种重建二叉树


待续。。。。。。




0 0
原创粉丝点击