二叉搜索树
二叉搜索树是一颗二叉树,可能为空。一棵非空的二叉搜索树满足以下特征:
1).每个元素有一个关键字,并且任意两个元素的关键字都不同;因此,所有的关键字都是唯一的(如果有重复的话,这样的二叉树称为有重复的二叉搜索树)。
2).在根节点的左子树中,元素的关键字(如果有的话)都小于根节点的关键字。
3).在根节点的右子树中,元素的关键字(如果有的话)都大于根节点的关键字。
4).根节点的左右子树也是二叉搜索树。
二叉搜索树的渐近性能可以和跳表媲美,在查找、插入、删除操作的所需平均时间为O(log n),在最坏情况下则为O(n)。在连续有序的插入情况下即为最坏情况,这时搜索树的上述操作从O(log n)退化为O(n)。
具体实现
值得注意的是,插入操作中,新插入的节点永远都是叶节点。而对于删除操作,如果要删除的节点同时有左右子树,则需要重新调整搜索树,这里的做法是找到最接近要删除节点关键字的节点,也就是以该节点左孩子为根的搜索树中最大的元素或者是以该节点右孩子为根的搜索树中最小的元素(这里实现选择前者),用它的值代替要删除节点的值,然后删除这个最接近的节点;如果要删除的节点最多有一个孩子节点,则不必调整,只需要调整它的父节点的指针即可。详细思考和思想见注释。
#pragma once
#include "bsTreeADT.h"
#include "..//..//..//ch11/Tree/Tree/binaryTreeNode.h"
#include <iostream>
using std::cout; using std::ends; using std::endl;
//暂时只实现常用的核心功能 如插入 查找 删除等 其他拷贝控制成员可以参考Ch11二叉树的实现
//另外关于非递归拷贝构造一棵二叉树的方法--使用队列层次拷贝
template <typename K,typename V>
class binarySearchTree :public bsTree<K, V> {
public:
binarySearchTree():root(nullptr),treeSize(0){ }
~binarySearchTree() { destory(root); }
bool empty()const { return treeSize == 0; }
int size()const { return treeSize; }
std::pair<const K, V>* find(const K& _Key) const;
void insert(const std::pair<const K, V>& _pair);
void erase(const K& _Key);
void ascendingOutput()const { inOrderOutput(root); }//升序输出二叉搜索树 也就是中序遍历二叉树
private:
binaryTreeNode<std::pair<const K, V>>* root;
int treeSize;
private:
void destory(binaryTreeNode<std::pair<const K, V>>*);
void inOrderOutput(binaryTreeNode<std::pair<const K, V>>* _node)const;
};
//递归销毁给定节点及其子树
template <typename K,typename V>
void binarySearchTree<K, V>::destory(binaryTreeNode<std::pair<const K, V>>* _node) {
if (_node == nullptr)
return;
destory(_node->leftChild);
destory(_node->rightChild);
delete _node;
}
//查找 返回指定Key的数对的指针
template <typename K,typename V>
std::pair<const K, V>* binarySearchTree<K, V>::find(const K& _Key)const {
binaryTreeNode<std::pair<const K, V>>* nodePtr = root;
while (nodePtr!=nullptr) {
//小于则向左孩子遍历 大于则向右孩子遍历
if (nodePtr->element.first < _Key)
nodePtr = nodePtr->rightChild;
else if (nodePtr->element.first > _Key)
nodePtr = nodePtr->leftChild;
else
return &nodePtr->element;
}
//没有找到
return nullptr;
}
//插入 将给定的pair根据key的大小插入到合适的位置 如果存在key则更新其value
//插入的新节点总是在叶子节点
template <typename K,typename V>
void binarySearchTree<K, V>::insert(const std::pair<const K, V>& _pair) {
binaryTreeNode<std::pair<const K, V>>* nodePtr = root;
binaryTreeNode<std::pair<const K, V>>* lastPtr = nullptr;//记录nodePtr的父节点
while (nodePtr!=nullptr) {
lastPtr = nodePtr;
if (_pair.first < nodePtr->element.first)
nodePtr = nodePtr->leftChild;
else if (_pair.first > nodePtr->element.first)
nodePtr = nodePtr->rightChild;
else {//更新已有key值的value
nodePtr->element.second = _pair.second;
return;
}
}
//创建新节点 因为其一定为叶子节点 因此无序构造指针域
binaryTreeNode<std::pair<const K, V>>* newNode =
new binaryTreeNode<std::pair<const K, V>>(_pair);
if (lastPtr == nullptr)//如果是根节点
root = newNode;
else {//不是根节点,判断它应该插入lastPtr的左孩子还是右孩子
if (_pair.first < lastPtr->element.first)
lastPtr->leftChild = newNode;
else
lastPtr->rightChild = newNode;
}
++treeSize;
}
//删除 删除给定key对应的节点
//算法思想为 1.删除的时候需要分情况讨论 如果要删除的节点只有一个孩子节点或者没有孩子节点(自身为叶节点),
// 则只需要删除它并且更改它的父节点的指针即可,无需重新构造搜索树的结构;反之,如果要删除的节点
// 拥有2个孩子节点,则需要重新调整搜索树的结构。具体思想见2:
// 2.先找到最接近给定key值节点的节点,要么在给定key值节点左孩子的最右叶子节点(小于key的最大元素)
//要么在给定key值节点右孩子的最左叶子节点(大于key的最小元素),然后以该值代替给定key,最后将该叶子节点删除
//由于实现字典的类型pair的key为const,不能进行改动,因此我们新建一个节点,它的值为最接近的那个值,它的指针域
//和给定key值节点的指针域相同,当然如果要删除的叶子节点恰好是它的直系子节点那么我们有必要对它的指针域进行更新
template <typename K,typename V>
void binarySearchTree<K, V>::erase(const K& _Key) {
//先定位给定key值的节点
binaryTreeNode<std::pair<const K, V>>* nodePtr = root;
binaryTreeNode<std::pair<const K, V>>* lastPtr = nullptr;
while (nodePtr!=nullptr && nodePtr->element.first!=_Key) {
lastPtr = nodePtr;
if (nodePtr->element.first < _Key)
nodePtr = nodePtr->rightChild;
else
nodePtr = nodePtr->leftChild;
}
if (nodePtr == nullptr)//没找到给定key的数对
return;
//如果要删除节点有两个孩子节点
if (nodePtr->leftChild != nullptr && nodePtr->rightChild != nullptr) {
//我们选择找小于key的最大叶节点 也就是位于它左孩子的最有叶子节点
binaryTreeNode<std::pair<const K, V>>* leafPtr = nodePtr->leftChild;//指向小于key的最大叶子节点
binaryTreeNode<std::pair<const K, V>>* pLeafPtr = nodePtr;//指向它的父节点
while (leafPtr->rightChild!=nullptr) {
pLeafPtr = leafPtr;
leafPtr = leafPtr->rightChild;
}
//寻找完成 构建新节点
binaryTreeNode<std::pair<const K, V>>* adjustNode = new
binaryTreeNode<std::pair<const K, V>>(leafPtr->element, nodePtr->leftChild, nodePtr->rightChild);
//将该新节点代替原来的nodePtr
if (lastPtr == nullptr)//要代替根节点 则需要更新root指针
root = adjustNode;
else if (nodePtr == lastPtr->leftChild)
lastPtr->leftChild = adjustNode;
else
lastPtr->rightChild = adjustNode;
//完成了对搜索树的重构 接下来就是删除叶子节点即可 不过在这之前
//必须判断叶子节点是否是原来nodePtr指向节点的直属孩子节点 如果是的话需要将其指针域进行调整
if (pLeafPtr == nodePtr)
lastPtr = adjustNode;//是 需要对新节点的指针域进行调整
else
lastPtr = pLeafPtr;//否 不需要对新结点的指针域进行调整 只需要对叶子节点的父节点进行指针域的调整
delete nodePtr;//删除原来需要被替代的节点
nodePtr = leafPtr;//接下来要删除的叶子节点
}
//如果要删除的节点只有一个孩子节点或者就是叶子节点
if (lastPtr == nullptr) //如果要删除根节点
if (nodePtr->leftChild != nullptr)//根节点只有左子树
root = nodePtr->leftChild;
else //根节点只有右子树或者为叶子节点
root = nodePtr->rightChild;
else {
if (lastPtr->leftChild == nodePtr) {//如果要删除的节点只有左子树
if (nodePtr->leftChild != nullptr)
lastPtr->leftChild = nodePtr->leftChild;
else
lastPtr->leftChild = nodePtr->rightChild;
}
else { //如果要删除的节点只有右子树或者为叶子节点
if (nodePtr->leftChild != nullptr)
lastPtr->leftChild = nodePtr->leftChild;
else
lastPtr->leftChild = nodePtr->rightChild;
}
}
delete nodePtr;
--treeSize;
}
//对给定节点的中序遍历
template <typename K,typename V>
void binarySearchTree<K, V>::inOrderOutput(binaryTreeNode<std::pair<const K, V>>* _node)const {
if (_node == nullptr)
return;
inOrderOutput(_node->leftChild);
cout << " (" << _node->element.first << "," << _node->element.second << ") ";
inOrderOutput(_node->rightChild);
}