算法笔记十一:二叉树之搜索二叉树

二叉树

每个节点,除了key之外,还有3个属性:父亲、左孩子、右孩子,其中,父亲一定大于左孩子,一定小于(等于)右孩子

搜索二叉树

利用二叉树的这些性质,我们可以得出,在这样一个结构下,插入、删除、查找的步骤,最多不会超过树的高度。注意哈,这里的二叉树,并不是平衡的,所以,其树的高度并不等于lgN,所以,最坏情况下,这个树的插入、查找、删除的最差性能,等于树的节点树N,跟链表的性能相等

空间上,每个数据,都会多一个父亲、左右孩子这三个数据的指针空间

哨兵节点

就是定义一个特殊的节点,这个节点用来表示NULL节点,其目的是,能够大大简化我们代码的简洁度(NULL不是一个节点,而哨兵节点是一个节点,其也有父亲、孩子)

插入和查找操作

每次插入,都将数据插入到叶子节点上,方法就是:从根节点开始,如果比当前值大,就比较其右孩子,否则,比较其左孩子,直到遇到空节点(卫星节点),那么这个卫星节点的位置,就是需要插入的位置。这种情况有一个例外,那就是如果当前节点的值等于待插入的值,这时候就不要再和孩子们比较,而是做统一处理,加入到这个节点的右孩子节点上,查找操作,思路也一样的,唯一需要约定的就是,总是返回第一个被找到的值相等的节点,而不是返回其右孩子节点(如果右孩子节点的值也相等的话)

删除操作:

删除操作,分为3种情况:

1、待删除的节点,没有左孩子
这种情况,简单的将右孩子替换掉当前节点的位置,就可以了(不会破坏“父亲一定大于左孩子,一定小于(等于)右孩子”的性质)

2、待删除的节点,没有右孩子
跟第一种情况一样,简单的将左孩子替换掉当前节点的位置就可以了

3、又有左孩子,又有右孩子
这种情况是最复杂的一种,这时需要我们好好想下,我们的目标是什么:节点删除后,不破坏二叉树的性质,即“父亲一定大于左孩子,一定小于(等于)右孩子”
由于右孩子可能与当前待删除节点的值相等,情况更复杂,所以,我们考虑从左孩子中,找出一个节点,替换掉待删除节点。
那么找哪个节点比较合适呢?前驱!原因很简单,前驱是最接近待删除节点的那个节点,也就是在左树中,这个节点的值是最大的,而且这个值一定没有右孩子(它最大嘛),所以将这个值放到待删除节点的位置上,一来可以保证其左孩子都比它小(不破坏二叉树的性质),二来前驱节点的右孩子一定为空,方便直接将其右孩子节点指向待删除节点的右孩子!
这里需要注意的是,前驱不一定就是待删除节点的左孩子哦,简单的例子:如果左孩子还有一个右孩子,显然其前驱是这个右孩子,待删除节点是前驱的父亲的父亲

中序遍历:
先输出左孩子,在输出节点本身,再输出右孩子(节点本身的输出位置位于左右孩子的中间,这个就叫做中序遍历,很容易就得出什么是前序遍历和后续遍历)。
由二叉树的性质:“父亲一定大于左孩子,一定小于(等于)右孩子”,得出中序遍历出来的结果,就是一个从小到大的已排好序的数组


废话就说到这些,下面上代码(友情提醒下,请好好理解代码中的注释,然后结合上面的文字,我想可以很容易的理解二叉树上的这些操作):

先给出节点类的定义,该类在后续都会用到,以后将不会再重复帖代码:

//
//  TreeNode.h
//  p1
//
//  Created by MinerGuo on 14/11/25.
//  Copyright (c) 2014年 MinerGuo. All rights reserved.
//

#ifndef __p1__TreeNode__
#define __p1__TreeNode__

#include <stdio.h>

class TreeNode;

class TreeNode {
    
public:
    
    TreeNode * _parent;
    TreeNode * _leftChild;
    TreeNode * _rightChild;
    int _key;
    
    TreeNode(int key){
        _parent = NULL;
        _parent = NULL;
        _rightChild = NULL;
        _key = key;
        
    }
    
};

//哨兵,表示空元素
static TreeNode * treeNodeNil = new TreeNode(-1);

#endif /* defined(__p1__TreeNode__) */


再给出二叉树的代码实现:
//
//  SearchTree.h
//  p1
//二叉搜索树
//
//  Created by MinerGuo on 14/11/24.
//  Copyright (c) 2014年 MinerGuo. All rights reserved.
//

#ifndef __p1__SearchTree__
#define __p1__SearchTree__

#include <stdio.h>
#include "TreeNode.h"
#include "ArrayGenerater.h"
#include <iostream>

class SearchTree {
    ArrayGenerater _ArrayGenerater;
    TreeNode * _root;
    int _itemCount;
    int _readIndex;
public:
    SearchTree(){
        _root = treeNodeNil;
        _root->_leftChild = treeNodeNil;
        _root->_rightChild = treeNodeNil;
        _root->_parent = treeNodeNil;
        _itemCount = 0;
    }
    
    TreeNode * getRoot(){
        return _root;
    }
    
    
    //插入一个元素
    void insert(int key){
        TreeNode * _item = new TreeNode(key);
        _item->_leftChild = treeNodeNil;
        _item->_rightChild = treeNodeNil;
        
        TreeNode * _tmp = treeNodeNil;
        TreeNode * _current_item = _root;
        
        while (_current_item != treeNodeNil) {
            _tmp = _current_item;
            
            if(_item->_key > _current_item->_key){
                _current_item = _current_item->_rightChild;
            }else if(_item->_key < _current_item->_key){
                _current_item = _current_item->_leftChild;
            }else{
                //如果相等,那就不要往下找了,立即在此结束,保证相等的元素,都在一起
                break;
            }
        }
        
        _itemCount++;
        _item->_parent = _tmp;
        
        if (_tmp == treeNodeNil) {
            //作为根节点插入
            _root = _item;
        }else if(_tmp ->_key == key){
            //相同节点,统一插入右子节点
            _item->_rightChild = _tmp->_rightChild;
            _item->_rightChild->_parent = _item;
            _tmp->_rightChild = _item;
        }else if (_item->_key > _tmp->_key){
            _tmp->_rightChild = _item;
        }else{
            _tmp->_leftChild = _item;
        }
    }
    
    //中序遍历:先遍历左子节点,再遍历当前节点,再遍历右子节点
    //这样遍历出来的结果,就是一个有序的数组
    int * readByMiddleOrder(int * result,TreeNode * currentItem){
        if (result == NULL) {
            result = new int [_itemCount];
            currentItem = _root;
            _readIndex = 0;
        }
        
        if(currentItem->_leftChild != treeNodeNil){
            readByMiddleOrder(result,currentItem->_leftChild);
        }
        
        *(result + _readIndex) = currentItem->_key;
        _readIndex++;
        
        if(currentItem->_rightChild != treeNodeNil){
            readByMiddleOrder(result,currentItem->_rightChild);
        }
        
        //释放资源
        delete currentItem;
        
        return result;
    }
    
    //搜索
    TreeNode * search(TreeNode * node,int key){
        if(node == treeNodeNil || key == node->_key){
            return node;
        }
        if(key < node->_key){
            return search(node->_leftChild,key);
        }
        return search(node->_rightChild,key);
    }
    
    //获取最小值
    TreeNode * getMinimun(TreeNode * node){
        //这里可以体现出哨兵节点的优势,可以简化这里while条件的写法
        while (node->_leftChild != treeNodeNil) {
            node = node->_leftChild;
        }
        return node;
    }
    
    //获取最大值
    TreeNode * getMaxmun(TreeNode * node){
        while (node->_rightChild != treeNodeNil) {
            node = node->_rightChild;
        }
        return node;
    }
    
    //辅助方法:用 X 替换 Y,这里只是将父节点的子节点的指针给改了,并未修改节点x的孩子节点,这个留给调用者处理
    void trasplant(TreeNode * root,TreeNode * x,TreeNode * y){
        if(y == y->_parent->_leftChild){
            y->_parent->_leftChild = x;
        }else if(y == y->_parent->_rightChild){
            y->_parent->_rightChild = x;
        }else{
            //y是根节点
             _root = x;
        }
        
        if(x != treeNodeNil){
            x->_parent = y->_parent;
        }
    }
    
    //删除一个节点
    void deleteNode(TreeNode * node){
        if(node->_leftChild == treeNodeNil){
            //如果要删除的节点,没有左子节点,则直接拿右子节点替换当前节点
            trasplant(_root,node->_rightChild,node);
        }else if (node->_rightChild == treeNodeNil){
            //如果要删除的节点,没有右子节点,则直接拿左子节点替换当前节点
            trasplant(_root,node->_leftChild,node);
        }else{
            //左右孩子都有,则统一选择从左子节点中,求出当前待删节点的前驱(最接近待删节点的数,也就是左边最大的那个数,如果拿这个数据替换当前待删节点,很显然,依然维持了搜索树的属性:左边都比当前节点小,右边都比当前节点大)
            TreeNode * firstSmallInChild = getMaxmun(node->_leftChild);
            if(firstSmallInChild->_parent != node){
                //前驱不是待删节点的左孩子,则需要先将前驱给抠出来:
                //第一步:将这个前驱的左孩子,放到前驱的位置上来
                trasplant(_root,firstSmallInChild->_leftChild,firstSmallInChild);
                //第二步:第一步结束时,前驱的左孩子的位置,空出来了,于是将待删除节点的左孩子,放到前驱的左孩子的位置上(我们的目标就是要将前驱放到待删除节点的位置上嘛)
                firstSmallInChild->_leftChild = node->_leftChild;
                //第三步:前驱的左孩子的父亲,设置为前驱(第二步的完善)
                firstSmallInChild->_leftChild->_parent = firstSmallInChild;
                //疑问:为啥这里不对前驱的右孩子进行处理呢?道理很简单,前驱是最大的,它不可能有比它还大的数字,所以右孩子位置肯定是空着的

            }
            //这时,我们已经将要替换到待删除节点位置的节点,给扣出来了,而且因为是前驱,其右孩子位置一定是空着的
            //将前驱放到带删除的位置上来
            trasplant(_root,firstSmallInChild,node);
            //将待删除节点的右孩子,放到前驱的右孩子位置上,替换完成
            firstSmallInChild->_rightChild = node->_rightChild;
            firstSmallInChild->_rightChild->_parent = firstSmallInChild;
        }
    }
    
    //测试删除元素,以下是测试代码,_ArrayGenerater类的实现,这里没给出,读者自己实现吧,其实就是生成随机的数而已
    void testDelete(int deleteCount,int maxNumber){
        while (deleteCount > 0) {
            int randomKey = _ArrayGenerater.generateOneNumber(maxNumber);
            TreeNode * _deleteNode = search(_root,randomKey);
            if (_deleteNode != treeNodeNil) {
                deleteNode(_deleteNode);
                deleteCount--;
                _itemCount--;
                //释放资源
                delete _deleteNode;
            }
        }
    }
};

#endif /* defined(__p1__SearchTree__) */




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值