二叉搜索树的应用不是很多,但他是重要的数据结构AVL树和红黑树的基础。
二叉搜索树
二叉搜索树是一棵二叉树,可能为空;一棵非空的二叉搜索树满足以下特征:
- 每个元素有一个关键字,并且任意两个关键字都不同;因此,所有的关键字都是唯一的;
- 在根节点的左子树中,元素的关键字(如果有的话)都小于根节点的关键字;
- 在根节点的右子树中,元素的关键字(如果有的话)都大于根节点的关键字;
- 根节点的左、右子树也都是二叉搜索树。
若是有重复值的二叉搜索树则把大于改成大于等于,小于改成小于等于。
索引二叉搜索树源于普通二叉搜索树,只是在每个节点总添加了leftSize域,这个域的值是该节点左子树的元素个数。
二叉搜索树的操作和实现
二叉搜索树的操作有:搜索、插入、删除。
搜索是从根开始查找,如果根为空,那么搜索树不包含任何元素,查找失败;如果不空,则将theKey与根的关键字相比较。如果theKey小,那么就不必在右子树中查找,只要查找左子树。
template<class K, class E>
pair<const K, E>* binarySearchTree<K,E>::find(const K& theKey) const
{//返回值是匹配数对的指针
// 如果没有匹配的数对,返回值为NULL
// p从根节点开始搜索,寻找关键字等于theKey的一个元素
binaryTreeNode<pair<const K, E> > *p = root;
while (p != NULL)
// 检查元素p->element
if (theKey < p->element.first)
p = p->leftChild;
else
if (theKey > p->element.first)
p = p->rightChild;
else // 找到匹配的元素
return &p->element;
// 没有匹配的数对
return NULL;
}
插入要先通过查找来确定是否存在某个元素,其关键字与thePair.first相同,如果搜索成功,那么就用thePair.second替代该元素的值。如果搜索不成功,那么就将新元素作为搜索中断节点的孩子插入二叉搜索树。
template<class K, class E>
void binarySearchTree<K,E>::insert(const pair<const K, E>& thePair)
{// 插入thePair。如果存在与其关键字相同的数对,则覆盖
// 寻找插入位置
binaryTreeNode<pair<const K, E> > *p = root,
*pp = NULL;
while (p != NULL)
{// 检查元素p->element
pp = p;
// p移到它的一个孩子节点
if (thePair.first < p->element.first)
p = p->leftChild;
else
if (thePair.first > p->element.first)
p = p->rightChild;
else
{// 覆盖旧的值
p->element.second = thePair.second;
return;
}
}
// 为thePair建立一个节点,然后与pp链接
binaryTreeNode<pair<const K, E> > *newNode
= new binaryTreeNode<pair<const K, E> > (thePair);
if (root != NULL) // 树不空
if (thePair.first < pp->element.first)
pp->leftChild = newNode;
else
pp->rightChild = newNode;
else
root = newNode; // 插入空树
treeSize++;
}
删除,假设要删除的节点是p,需要考虑三种情况:1)p是树叶;2)p只有一棵非空子树;3)p有两棵非空子树。
template<class K, class E>
void binarySearchTree<K,E>::erase(const K& theKey)
{// 删除关键字等于theKey的数对
// 查找关键字为theKey的节点
binaryTreeNode<pair<const K, E> > *p = root,
*pp = NULL;
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)
return; // 不存在与关键字theKey匹配的数对
// 重新组织树结构
// 当p有两个孩子时处理
if (p->leftChild != NULL && p->rightChild != NULL)
{// 两个孩子
// 转化成空或只有一个孩子
// 在p的左子树中寻找最大元素
binaryTreeNode<pair<const K, E> > *s = p->leftChild,
*ps = p; // parent of s
while (s->rightChild != NULL)
{// 移到最大的元素
ps = s;
s = s->rightChild;
}
// 将最大元素s移到p,但不是简单的移动
// p->element = s->element,因为key是常量
binaryTreeNode<pair<const K, E> > *q =
new binaryTreeNode<pair<const K, E> >
(s->element, p->leftChild, p->rightChild);
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;
}
// p最多有一个孩子
// 把孩子指针存放在c
binaryTreeNode<pair<const K, E> > *c;
if (p->leftChild != NULL)
c = p->leftChild;
else
c = p->rightChild;
// 删除p
if (p == root)
root = c;
else
{// p是pp的左孩子还是右孩子?
if (p == pp->leftChild)
pp->leftChild = c;
else pp->rightChild = c;
}
treeSize--;
delete p;
}
应用
交叉分布
通道布线与交叉:在交叉分布问题中,从一个布线通道开始,在通道的顶部和底部各有n个针脚。布线区域是带阴影的长方形区域。针脚从左到右从1到n编号,另外,对[1,2,3,…,n]的一个排列C,用一条线路将顶部的针脚i与底部的针脚j连接起来,如图的实例中:C=[8,7,4,2,5,1,9,3,10,6]。实现这些连接的n条线路从1到n编号,线路i连接顶部的针脚i和底部的针脚j,当且仅当i<j时,线路i在线路j的左边
每一个交叉用一个数对(i,j)表示,其中i和j是两条交叉的线路。为了避免一个交叉提及两次,要求i<j(注意,交叉(10,9)(9,10)是一样的)。线路i和j交叉(i<j)当且仅当
C
i
>
C
j
C_i>C_j
Ci>Cj。令
k
i
k_i
ki表示这种数对(i,j)的数量。
分布交叉:为了使通道的上半部和下半部的布线平衡,我们要求每一部分含有数量大致相同的交叉(上半部应该有
⌊
k
/
2
⌋
\lfloor k/2\rfloor
⌊k/2⌋下半部应有
⌊
k
/
2
⌋
\lfloor k/2\rfloor
⌊k/2⌋)
void main(void)
{
//定义要解决的问题实例
//在通道底部的连接点theC[1:10]
int theC[] = {0, 8, 7, 4, 2, 5, 1, 9, 3, 10, 6};
//交叉数量,k[1:10]
int k[] = {0, 7, 6, 3, 1, 2, 0, 2, 0, 1, 0};
int n = 10; //在通道每一遍的针脚数量
int theK = 22; //交叉总数量
//生成数据结构
arrayList<int> theList(n);
int *theA = new int[n + 1], //顶端的排列
*theB = new int[n + 1], //底端的排列
*theX = new int[n + 1]; //中间的排列
int crossingsNeeded = theK / 2; //需要在上半部分保留的交叉数
// 从右到左扫描线路
int currentWire = n;
while(crossingsNeeded > 0)
{//在上半部需要更多的交叉
if (k[currentWire] < crossingsNeeded)
{//使用来自currentWire的所有交叉
theList.insert(k[currentWire],currentWire);
crossingsNeeded -= k[currentWire];
}
else
{//使用来自currentWire的crossingsNeeded
theList.insert(crossingsNeeded, currentWire);
crossingsNeeded = 0;
}
currentWire--;
}
//确定中间的线路排列
//第一个线路currentWire次数相同
for(int i = 1;i <= currentWire; i++)
theX[i] = i;
//剩余线路的次序来自表
for (int i = currentWire + 1; i <= n; i++)
theX[i] = theList.get(i - currentWire - 1);
//计算上半部的排列
for(int i = 1;i <= n;i++)
theA[theX[i]] = i;
//计算下半部的排列
for(int i = 1;i <= n;i++)
theB[i] = theC[theX[i]];
cout << "A is ";
for(int i = 1;i <= n;i++)
cout << theA[i] << " ";
cout << endl;
cout << "B is ";
for(int i = 1;i <= n;i++)
cout << theB[i] <<" ";
cout << endl;
}