数据结构——树(8)——二叉搜索树的插入和删除操作

二叉搜索树的插入操作

我们要在二叉搜索树中执行各种操作的前提就是,我们首先要有一棵二叉搜索树。那么,如何创建一棵二叉搜索树呢?最简单的方法就是我们可以从一棵空树开始,每次调用一个addNode函数,将一个新的值插入二叉搜索树中。但是在每次插入的时候我们都要保持树的一个排序关系,因此我们要做的就是在插入的时候,找到我们要插入的值应该在的位置。
因此和遍历的代码一样,addNode的代码可以从树根开始递归地进行。在每个节点上,addNode必须将要插入的值与当前节点中的值进行比较。 如果要插入的值小于当前的值,则该值属于左子树。相反,如果要插入的值大于当前的值,则属于右子树。最终,该进程将遇到一个NULL子树,该子树表示需要添加新节点的树中的点。此时,addNode用一个初始化为包含新的值的新节点替换NULL指针。(也就是说这样的做法是,先找到要插入的点,然后再生成新的节点,随后插入树中)
看起来挺简单吧,但是实现的代码却并非如此简单,难点在于,我们插入值后,改变了树的结构,因此我们的参数就要用我们的引用参数。我们最终返回的是一个指向树的指针,因此返回的类型是指针。我们的函数就是:

方法一,利用引用参数传递

//第一个辅助函数
void stringSet::add(string s, Node *&node){
    if(node = NULL){                   //基础事件(base case)
        node = new Node(s);
        count++;
    }else if(node -> str > s){        //用else if是因为这样的情况只会出现某一种,提高效率
        add(s, node -> left);
    }else if (node -> str < s){
        add(s,node -> right);
    }
}

我们先来详细分析一下这个函数。原型的话我们看的很特别:

void stringSet::add(string s, Node *&node)

特点在于第二个参数Node *&node。如果树为空,则addNode将创建一个新节点,初始化其字段,然后用指向新节点的指针替换现有结构中的NULL指针。 如果树不为空,则addNode将新数值与树根进行比较。如果数值相等,则说明这个值已经在树中,不需要进一步的操作。如果不等,则addNode使用比较结果来确定是将数值插入左边还是右边的子树,然后进行适当的递归调用。
为了更好的理解第二个参数的原理,我们试着画出整个过程的图解,假设我们现在新初始化了一棵新树:

stringSet *dwarfTree = NULL;

我们使用之前提到过的堆栈模式来分析,在调用上述的语句后,我们可以得到如图所示的图解:
这里写图片描述
那当我们调用以下语句的时候会发生什么呢?

addNode("Grumpy", dwarfTree);

在addNode的参数中,我们建立一个数值为Grumpy的节点,那么&node就是dwarfTree(树根)的一个引用,或者说是它的地址。
这里写图片描述
从上到下的白色区域分别对应的是,数值Grumpy,&node,dwarfTree。
第一句if代码检测&node是否为空树,此时为true,那么我们就执行:

node = new Node(s); //Node中有三个变量,value,left,right

这一行在堆上分配一个含值为S的新节点,并将其分配给引用参数node,从而改变调用者中的指针值,如下所示:
这里写图片描述
上图的结构代表了只含有一个节点Grumpy时的存储情况。此时树不在为空,变量dwarfTree现在包含了指向节点Grumpy的地址了。假设Sleep在Grumpy的后面,那么程序就会调用:

add(s,node -> right);

这个调用过程跟刚刚的基本相同,唯一不同就是,引用参数node现在指向的不再是空节点,而是有某个节点地址的变量了:
这里写图片描述
新的节点被分配到Grumpy的右指针

这里写图片描述
程序返回的时候应该是这样的:
这里写图片描述
依次插入其他的数据,最终就变成了一棵二叉搜索树
这里写图片描述

方法二利用指向指针的指针

先看代码:

void StringSet::add(string s, Node **node) {
   if (*node == nullptr) {
       *node = new Node(s);
       count++;
   } else if ((*node)->str > s) {
       add(s, &((*node)->left));
   } else if ((*node)->str < s) {
       add(s, &((*node)->right));
   }
}

同样的,我们用图解来解读这一段代码:
这里写图片描述
首先我们的node为变量存着树根的地址,** *node为指向树根。如图所示。当根为空的时候,赋值。
当为非空的时候,比较值,然后递归调用add方法。**
注意,此过程中node*的指向一直都在变化的!
(假设我们要插入5)指向依次为6的左边,2的右边,4的右边,找到了null指针,此时生成一个含5的节点,然后再将其地址赋给空节点:
这里写图片描述

二叉搜索树的移除操作

前面我们提到过,移除二叉搜索树的时候有三种情况。我们先逐个讨论(假设我们有如下的一棵树):
这里写图片描述
1. 当要删除的节点是叶子的时候,我们直接删除,因为并不影响二叉搜索树的结构
这里写图片描述
2. 当要删除的节点含有一个孩子的时候,先将节点删除,再将指向该节点的指针,指向该节点的孩子。(如删除sleep操作)
这里写图片描述
3. 当要删除的节点含有两个孩子的时候,我们先将节点删除,然后在节点的左子树寻找最左的值(最小值)或者在节点右子树的最右边寻找一个值代替此时的空节点,然后重复此操作(如删除此时的树根):
这里写图片描述

猜你喜欢

转载自blog.csdn.net/redrnt/article/details/79231848
今日推荐