索引二叉搜索树是在二叉搜索树的基础上,给每个节点添加了一个leftSize属性,用于记录该节点左子树的节点个数。基于leftSize属性,搜索树可以按照名次快速找到该名次所对应的节点,从而扩展出了按名次查找节点和删除指定名次的节点的操作。
名次查找
读入所需查找的名次index(从0开始),先确定该名次是否超出范围。然后指针从根节点出发,循环操作,比较根节点的leftSize与index大小,若index更小,则指针指向左子树;若index更大,则指针指向右子树。每当指针前往右子树时,index都需要减去(leftSize+1),即减去左子树和根节点的数量;当指针前往左子树时,index不发生改变。最终当index=leftSize时,即查找完成。
leftSize维护
由于插入和删除操作会改变节点个数从而影响一部分节点的leftSize,所以在插入和删除的过程中需要及时修改受到影响的节点的leftSize。
若插入未成功(关键字已被占用)或删除未成功(没有关键字的元素),则不需要改变任何leftSize。
若插入成功,则需要在指针找到插入位置之前,修改指针路径上的一部分节点的leftSize:若指针要向左子节点走,则当前指针指向节点的leftSize+1;若指针要向右子节点走,则当前指针指向节点的leftSize不变。
删除与插入同理。若需要删除的节点存在,则需要在指针找到删除位置之前,修改指针路径上的一部分节点的leftSize:若指针要向左子节点走,则当前指针指向节点的leftSize-1;若指针要向右子节点走,则当前指针指向节点的leftSize不变。
代码
#include<iostream>
using namespace std;
template<class T, class E>
struct bsNode//定义索引二叉搜索树节点
{
pair<T, E> element;//元素为数对,前项表示关键字,后项表示数值
bsNode<T, E>* leftChild;
bsNode<T, E>* rightChild;
int leftSize;//左子树的节点个数
bsNode(const pair<T, E>& theElement)
{
element = theElement;
leftChild = NULL;
rightChild = NULL;
leftSize = 0;
}
bsNode(const pair<T, E>& theElement, bsNode<T, E>* LEFT, bsNode<T, E>* RIGHT,int theLeftSize)
{
element = theElement;
leftChild = LEFT;
rightChild = RIGHT;
leftSize = theLeftSize;
}
};
template<class T, class E>
class bsTree
{
public:
bsTree()
{
size = 0;
root = NULL;
}
void ascend() { inOrder(root); }//按关键字顺序输出元素数值
void find_by_key(const T& theKey)//按关键字查找元素
{
bsNode<T, E>* p = root;
while (p != NULL)
{
if (theKey < p->element.first)
p = p->leftChild;
else if (theKey > p->element.first)
p = p->rightChild;
else
{
cout << p->element.second << endl;
return;
}
}
cout << "Not find" << endl;
}
void find_by_index(int& theIndex)//按名次查找元素,输入的名次是从1开始的
{
if (theIndex > size)//名次大于元素总个数,无法查找
{
cout << "Out of range" << endl;
return;
}
theIndex--;//把名次化为从0开始
bsNode<T, E>* p = root;
while (p != NULL&& p->leftSize != theIndex)
{
if (theIndex<p->leftSize )//向左子树走不需要改变theIndex
p = p->leftChild;
else//向右子树走需要将theIndex减掉(leftSize+1)
{
theIndex = theIndex - 1 - p->leftSize;
p = p->rightChild;
}
}
//循环结束之后p指向所需名次的节点
cout << p->element.second << endl;
}
void insert(const pair<T, E>& theElement)//插入元素
{
//先寻找元素位置,根据关键字判断元素是否已经存在
bsNode<T, E>* p = root;//用于寻找安放空位
bsNode<T, E>* pp = NULL;//p节点的父节点
while (p != NULL)//寻找
{
pp = p;
if (theElement.first < p->element.first)
p = p->leftChild;
else if (theElement.first > p->element.first)
p = p->rightChild;
else//若已经存在,则退出函数
{
cout << "Has existed" << endl;
return;
}
}
bsNode<T, E>* newNode = new bsNode<T, E>(theElement);
if (root != NULL)//树非空
{
if (theElement.first < pp->element.first)
pp->leftChild = newNode;
else
pp->rightChild = newNode;
}
else//树空
root = newNode;
size++;
//修改路径上的leftSize
bsNode<T, E>* q = root;
while (q != NULL&&q->element.first!=theElement.first)
{
if (theElement.first < q->element.first)//只有当指针向左子树走的时候,根节点的leftSize需要加1
{
q->leftSize++;
q = q->leftChild;
}
else if (theElement.first > q->element.first)
{
q = q->rightChild;
}
}
}
void erase_by_key(const T& theKey)//按关键字删除元素
{
//先确定元素是否存在
bsNode<T, E>* p = root;//用于寻找需要删除的节点
bsNode<T, E>* pp = NULL;//p节点的父节点
while (p != NULL && p->element.first != theKey)//把p定位到删除节点上
{
pp = p;
if (theKey < p->element.first)
p = p->leftChild;
else
p = p->rightChild;
}
if (p == NULL)//若p为空,则说明删除节点不存在,退出函数
{
cout << "Not exist" << endl;
return;
}
//再次寻找,修改路径上的leftSize
p = root;//用于寻找需要删除的节点
pp = NULL;//p节点的父节点
while (p != NULL && p->element.first != theKey)//把p重新定位到删除节点上
{
pp = p;
if (theKey < p->element.first)
{
p->leftSize--;
p = p->leftChild;
}
else
p = p->rightChild;
}
if (p->leftChild != NULL && p->rightChild != NULL)//若删除节点有两个子树,则需要将右子树最大值(或左子树最小值)复制到删除节点上,然后转化为删除右子树最大值(或左子树最小值)节点
{
bsNode<T, E>* s = p->rightChild;//寻找p右子树的最小值
bsNode<T, E>* ps = p;//s节点的父节点
while (s->leftChild != NULL)//循环结束后,s指向p的右子树的最小值
{
s->leftSize--;//寻找右子树最小值的过程中,需要修改路径上的leftSize
ps = s;
s = s->leftChild;
}
p->element = s->element;
bsNode<T, E>* q = new bsNode<T, E>(s->element, p->leftChild, p->rightChild,p->leftSize);//q节点的元素是p节点右子树的最小值,q节点的位置是p
if (pp == NULL)//以q节点替换p节点
root = q;
else if (p == pp->leftChild)
pp->leftChild = q;
else
pp->rightChild = q;
if (ps == p)//把pp移动到s节点的父节点
pp = q;
else
pp = ps;
delete p;
p = s;//把p移动到s节点,之后的操作相当于删除s节点
}
//之后删除节点至多只有一个子树
bsNode<T, E>* c;
if (p->leftChild != NULL)
c = p->leftChild;
else
c = p->rightChild;
if (p == root)
root = c;
else
{
if (p == pp->leftChild)
pp->leftChild = c;
else
pp->rightChild = c;
}
size--;
delete p;
}
void erase_by_index(int& theIndex)//按名次删除元素(相当于按名次查找和按关键字删除的综合)
{
if (theIndex > size)
{
cout << "Out of range" << endl;
return;
}
theIndex--;//把名次化为从0开始
bsNode<T, E>* p = root;//用于寻找需要删除的节点
bsNode<T, E>* pp = NULL;//p节点的父节点
while (p != NULL && p->leftSize != theIndex)
{
pp = p;
if (theIndex < p->leftSize)//向左子树走不需要改变theIndex
{
p->leftSize--;
p = p->leftChild;
}
else//向右子树走需要将theIndex减掉(leftSize+1)
{
theIndex = theIndex - (1 + p->leftSize);
p = p->rightChild;
}
}
//此时已找到需要删除的元素,剩余代码和按关键字删除相同
if (p != NULL && p->leftChild != NULL && p->rightChild != NULL)
{
bsNode<T, E>* s = p->rightChild;
bsNode<T, E>* ps = p;
while (s->leftChild != NULL)
{
s->leftSize--;
ps = s;
s = s->leftChild;
}
p->element = s->element;
bsNode<T, E>* q = new bsNode<T, E>(s->element, p->leftChild, p->rightChild, p->leftSize);
if (pp == NULL)
root = q;
else if (p == pp->leftChild)
pp->leftChild = q;
else
pp->rightChild = q;
if (ps == p)
pp = q;
else
pp = ps;
delete p;
p = s;
}
bsNode<T, E>* c;
if (p->leftChild != NULL)
c = p->leftChild;
else
c = p->rightChild;
if (p == root)
root = c;
else
{
if (p == pp->leftChild)
pp->leftChild = c;
else
pp->rightChild = c;
}
size--;
delete p;
}
private:
bsNode<T, E>* root;
int size;
void inOrder(bsNode<T, E>* node)//中序遍历
{
if (node != nullptr)
{
inOrder(node->leftChild);
cout << node->element.second <<endl;
inOrder(node->rightChild);
}
}
};