二叉搜索树:是一颗二叉树,可能为空;一颗非空的二叉搜索树满足以下特征:
1.每个元素有一个关键字,并且任意两个元素的关键字都不同;因此所有的关键字都是唯一的。
2.在根节点的左子树中,元素的关键字(如果有的话)都小于根节点的关键字。
3.在根节点的右子树中,元素的关键字(如果有的话)都大于根节点的关键字。
4.根节点的左右子树也是二叉搜索树。
有重复值的二叉搜索树:去除二叉搜索树中每个元素都必须唯一的要求,然后用小于等于代替特征2中的要求,大于等于代替特征3中的要求,就得到了有重复值的二叉搜索树。
索引二叉搜索树:源于普通二叉搜索树,只是在每个节点中添加一个leftSize域,这个域的值是该节点左子树的元素个数。
代码实现:
代码来源于:https://www.cise.ufl.edu/~sahni/dsaac/
#ifndef binarySearchTree_
#define binarySearchTree_
#include "bsTree.h"
#include "linkedBinaryTree.h"
using namespace std;
template<class K, class E>
class binarySearchTree : public bsTree<K,E>,
public linkedBinaryTree<pair<const K, E> >
{
public:
// methods of dictionary
bool empty() const {return treeSize == 0;}
int size() const {return treeSize;}
pair<const K, E>* find(const K& theKey) const;
void insert(const pair<const K, E>& thePair);
void erase(const K& theKey);
// additional method of bsTree
void ascend() {inOrderOutput();}
};
由于搜索二叉树不是完全二叉树,所以这里我们采用链表的描述方式即每个节点有两个指针域分别指向左孩子和右孩子。这里直接继承于之前的链表描述。
template<class K, class E>
pair<const K, E>* binarySearchTree<K,E>::find(const K& theKey) const
{// Return pointer to matching pair.
// Return NULL if no matching pair.
// p starts at the root and moves through
// the tree looking for an element with key theKey
binaryTreeNode<pair<const K, E> > *p = root;//获取搜索树的节点指针
while (p != NULL)
// examine p->element
if (theKey < p->element.first)//当搜索关键字小于根节点关键字,根据搜索树的规则,该节点可定在左子树
p = p->leftChild;//搜索左子树
else
if (theKey > p->element.first)//如果大于则在右子树
p = p->rightChild;//搜索右子树
else // found matching pair
return &p->element;//相等则说明找到了
// no matching pair
return NULL;
}
以上代码为查找代码,在搜索二叉树中找到键值,从节点开始查找,比节点大就往右子树查找,比节点小就往左子树查找,直到找到相等的然后返回,找不到就返回NULL。
template<class K, class E>
void binarySearchTree<K,E>::insert(const pair<const K, E>& thePair)
{// Insert thePair into the tree. Overwrite existing
// pair, if any, with same key.
// find place to insert
binaryTreeNode<pair<const K, E> > *p = root,//获取根节点的值
*pp = NULL;
while (p != NULL)
{// examine p->element
pp = p;//注意这个赋值在循环里面,pp得到的是最后循环结束前的那个不为空的p值。
// move p to a child
if (thePair.first < p->element.first)//下面的代码就是一个简单的查找代码,之前已经实现过了。
p = p->leftChild;
else
if (thePair.first > p->element.first)
p = p->rightChild;
else
{// replace old value
p->element.second = thePair.second;
return;
}
}
// get a node for thePair and attach to pp
binaryTreeNode<pair<const K, E> > *newNode
= new binaryTreeNode<pair<const K, E> > (thePair);//创建一个新节点初始化为要插入的节点
if (root != NULL) // the tree is not empty
if (thePair.first < pp->element.first)
pp->leftChild = newNode;//如果插入节点的键小于中断节点的键则插入其左孩子
else
pp->rightChild = newNode;//否则右孩子
else
root = newNode; // insertion into empty tree,如果树为空则变为新的根
treeSize++;
}
以上为插入函数,该类的初始化函数和析构函数都是使用的默认函数,搜索树的构建全靠插入函数,首先搜索树找到重复键则更新键值,否则在查找中断点处插入新值。
template<class K, class E>
void binarySearchTree<K,E>::erase(const K& theKey)
{// Delete the pair, if any, whose key equals theKey.
// search for node with key theKey
binaryTreeNode<pair<const K, E> > *p = root,
*pp = NULL;
while (p != NULL && p->element.first != theKey)//如果根节点不为空,且根节点的键不等于要删除的键
{// move to a child of p
pp = p;
if (theKey < p->element.first)//然后左右搜索寻找要删除的节点,并用pp捕获该节点的父节点。
p = p->leftChild;
else
p = p->rightChild;
}
if (p == NULL)//如果没有搜索到要删除的节点则直接返回
return; // no pair with key theKey
// restructure tree
// handle case when p has two children
if (p->leftChild != NULL && p->rightChild != NULL)//第一种情况,如果p的左右节点都不为空。
{// two children
// convert to zero or one child case
// find largest element in left subtree of p
binaryTreeNode<pair<const K, E> > *s = p->leftChild,
*ps = p; // parent of s
while (s->rightChild != NULL)//获取p的左孩子中最大的那个节点并将其父节点赋值给ps,策略是不断搜索p的左子树的右节点
{// move to larger element
ps = s;
s = s->rightChild;
}
// move largest from s to p, can't do a simple move
// p->element = s->element as key is const
binaryTreeNode<pair<const K, E> > *q =
new binaryTreeNode<pair<const K, E> >
(s->element, p->leftChild, p->rightChild);//创建一个新节点其值为p的左子树的最大值,左右孩子指向p的左右孩子,即用该节点代替p节点
if (pp == NULL)//如果p为根节点,则将q设为新节点
root = q;
else if (p == pp->leftChild)//否则将p的父节点变为指向q,即用q节点代替了p节点
pp->leftChild = q;
else
pp->rightChild = q;
if (ps == p) pp = q;//pp为要删除节点的父节点,这里是如果s节点没有右子树的情况下的赋值操作。
else pp = ps;
delete p;//删除p
p = s;//将p指向值最大的那个节点,方便函数尾部进行删除。
}
// p has at most one child
// save child pointer in c
binaryTreeNode<pair<const K, E> > *c;
if (p->leftChild != NULL)//这是第二种情况即要删除的节点只有一个子树,获取唯一的子树。
c = p->leftChild;//第三种情况及删除的节点为叶节点也能用以下程序进行处理。
else
c = p->rightChild;
// delete p
if (p == root)//如果p节点为根则将该子节点设为根。
root = c;
else//
{// is p left or right child of pp?
if (p == pp->leftChild)//不然将p节点的父节点直接指向该节点唯一的子树根节点
pp->leftChild = c;
else pp->rightChild = c;
}
treeSize--;
delete p;//删除p的内存。
}
以上为删除代码,稍微有点复杂,上面的删除分三种情况。
第一种:要删除的节点是叶节点,处理的方法是释放该叶节点空间,若是根节点则另根为NULL。
第二种:要删除的节点只有一颗子树,如果p没有父节点(即p是根节点),则p的唯一子树的根节点称为新的搜索树的根节点。如果p有父节点pp,则修改pp的指针域使其指向p的唯一孩子。然后释放p节点。
第三种:要删除的节p有两颗非空子树。这种情况我们有两种方法来进行删除。
1:用p节点左子树的最大节点s数据来代替p节点,然后删除原来的p节点,在删除s节点。(上诉代码就是使用的该种方法)
2:用p节点的右子树的最小节点来代替p并删除p和右子树最小节点。
在第一种方法中我们要特别注意,当我们删除了p并用s对p进行替换后,我们仍然要删除s,因为s为最大的节点,所以s最多只有可能有左子树。这就退化成了前两种情况的删除方法。所以第三种情况的删除方法分两步进行。
接下来对上述的树功能进行一些延伸:
// extension of binarySearchTree to include method to insert with visit
#ifndef binarySearchTreeWithVisit_
#define binarySearchTreeWithVisit_
#include "binarySearchTree.h"
using namespace std;
template<class K, class E>
class binarySearchTreeWithVisit : public binarySearchTree<K,E>
{
public:
void insert(const pair<const K, E>&, void(*visit)(E& u));
};
template<class K, class E>
void binarySearchTreeWithVisit<K,E>::insert
(const pair<const K, E>& thePair, void(*visit)(E& u))
{// Insert thePair into the tree if no matching pair present.
// Visit existing pair, if any, with same key.
// search for a matching element
binaryTreeNode<pair<const K, E> > *p = root, // search pointer
*pp = 0; // parent of p
while (p != NULL)//循环查找匹配的键,如果找到则返回键值。
{// examine p->element
pp = p;
// move p to a child
if (thePair.first < p->element.first)
p = p->leftChild;
else
if (thePair.first > p->element.first)
p = p->rightChild;
else
{// visit the element
visit(p->element.second);
return;
}
}
// get a node for thePair and attach to pp
binaryTreeNode<pair<const K, E> > *newNode
= new binaryTreeNode<pair<const K, E> > (thePair);
if (root != NULL) // the tree is not empty,如果没有找到,则将该键值对插入树。
if (thePair.first < pp->element.first)
pp->leftChild = newNode;
else
pp->rightChild = newNode;
else
root = newNode; // insertion into empty tree
treeSize++;
}
#endif
以上代码在树里面找到搜寻的键,并返回键值如果没找到则将其插入到树里面。
之前的树结构不允许有相同的键值,如果有相同的键值那么之前的键值将会被取代,接下来我们介绍允许存在相同键值的树结构。
要允许相同的键值能够存储在一棵树里面只需修改上诉insert代码的一个地方即可:
将
if(thePair.first<p->element.first)
改为
if(thePair.first<=p->element.first)
这样一来当插入相同键时,旧键不会被取代而会被插到旧键的左孩子处,如果把n
个元素插入到一个初始为空的二叉搜索树中,而且n个元素的关键字都相同,那么结果将是一颗高度为n的左偏树。
下面简单介绍一下,索引二叉搜所树:
索引二叉搜索树:只是在每个节点中添加了一个leftSize域,这个域的值是该节点左子树的元素个数。
由于左子树的元素大小都小于根的元素大小,所以根的leftSize值就代表在树的左边有leftSize个值小于根元素,所以根的索引值为leftSize。如果我们要任意寻找一个索引为n的值,如果n=根的leftSize那么根就是我们要找的值,如果n<根的leftSize,那么我们就要到根的左子树里面去寻找,然后和左子树的根进行比较然后执行相同的操作,如果n大于左子树的根的leftSize那么证明n在左子树根的右子树里面,但是这时我们就不能直接拿n去和右子树根的leftSize大小进行对比,因为右子树的leftSize和根没有直接关系,我们要将n-(根的leftsize+1)得到的值n’去到右子树中进行相同的搜索操作。