二叉查找树和前面所过的二叉堆在实现存在很大区别,在二叉搜索树(Binary Search Tree或者简称BST)中,任意节点的左子树中的所有节点的值要小于其右子树中的所有节点的值。
二叉搜索树的性质:
- 任一当前节点的左子树包含的节点值小于当前节点。
- 任一当前节点的右子树包含的节点值大于当前节点。
- 任意两个节点不能有重复的值。(这个性质非常重要,它使得BST可以构架另外一个数据结构Set,Set中每个元素都是唯一的。)
- 除了叶子节点外,任意节点左子树和右子树也必须满足BST性质。
二叉搜索树的上述属性提供了节点值之间的排序关系,从而可以快速完成搜索、最小和最大等操作。但事实上,BST构建的排序逻辑事实上属于分治排序思想的其中一种,为什么这么说:
- 也就是说从BST的根节点为轴(Pivot)。左半分区就是根节点左子树,右半分区是根节点的右子树,这个操作我们叫分区(Partition).
- 分治排序的整个算法需要依次对左子数或右子树逐层递归执行。
但BST的不强制任意一个路径的所有节点的值都严格依照降序或升序排列,这有异于分治排序算法的。例如下图的完整二叉树,同时也是一个BST,我们看看具体的例子{32、16、11、23}和{32,16,11,31},这两条路径上的节点都不完全排序。
但我们换个角度去思考一下,如果我们设定两个参照条件。
- 条件1:锁定第h层对应的根节点。
- 条件2:限定每层遍历的方向:要么是一路按左节点遍历,要么一路按右节点遍历。
那么有趣的事情发生了
例如我以值为32的节点为根,一路left遍历,依次是降序排列的:
例如我以值为16的节点为根,一路right遍历,依次是升序排列的:
例如我以值为32的节点为根,一路right遍历,依次是升序排列的:
这里可以总结出BST的另外一些特性。
当明确第h层的参照节点为根
。
- 性质1:往左节点方向逐层遍历到叶子节点
- 性质2:往右节点方向逐层遍历到叶子节点
BST的类接口设计
我们知道BST构成的基本要素是节点,因此请查看如下BSNode类定义的代码清单
#ifndef __BSTREE_HH__
#define __BSTREE_HH__
#include <iostream>
//BSTree类模板声明
template<class T>
class BSTree;
//BST迭代器类模板声明
template<class T>
class BSTreeIterator;
template<class T>
class BNode{
friend class BSTree<T>;
private:
BNode<T>* d_parent; //父节点
BNode<T>* d_left; //左子节点
BNode<T>* d_right; //右子节点
T d_data; //数据域
public:
BNode(T val){
d_data=val;
d_parent=nullptr;
d_left=nullptr;
d_right=nullptr;
}
~BNode(){
d_parent=nullptr;
d_left=nullptr;
d_right=nullptr;
}
};
BST容器的类接口
在前面的ArrayList、LinkedList、Queue篇章,笔者多次强调过“容器”这个概念,这也是C++的std代码库组织内置所有数据结构的通用的编程风格。BST容器封装了BST的根、以及增、删、改、查等API的核心算法。
template<class T>
class BSTree{
private:
BNode<T>* d_root; //整个树的根
int d_height; //当前树的高度
int d_size; //当前元素的高度
//内部递归插入算法
BNode<T>* insertRcu(BNode<T>*,T);
void clear(BN