public:
void print()
{
queue< BTNode<T>* > a; queue<bool> flag; ofstream outfile("out.txt");
BTNode<T>* p = root; BTNode<T> zero; bool v = true;
int i = 1, level = 0, h = height();
while (i < 2<<h)
{
if (i == 1<<level)
{
cout << endl << setw(2 <<(h - level)); level++;
if (v) cout << p->data;
else cout << ' ';
}
else
{
cout << setw(4 <<(h - level + 1));
if (v) cout << p->data;
else cout << " ";
}
if (p->left) { a.push(p->left); flag.push(true); }
else { a.push(&zero); flag.push(false); }
if (p->right) { a.push(p->right); flag.push(true); }
else { a.push(&zero); flag.push(false); }
p = a.front(); a.pop(); v = flag.front(); flag.pop(); i++;
}
cout << endl;
} 打印树状结构的核心是按层次遍历二叉树,但是,二叉树有许多节点缺左或右子树,连带的越到下面空隙越大。为了按照树的结构打印,必须把二叉树补成完全二叉树,这样下面的节点就知道放在什么位置了——a.push(&zero);但是这样的节点不能让它打印出来,所以对应每个节点,有一个是否打印的标志,按理说pair结构很合适,为了简单我用了并列的两个队列,一个放节点指针——a,一个放打印标志——flag。这样一来,循环结束的标志就不能是队列空——永远都不可能空,碰到NULL就补一个节点——而是变成了到了满二叉树的最后一个节点2^(height+1)-1。——黄皮书对于树高的定义是,空树为的高度为-1。
对于输出格式,注意的是到了第1、2、4、8号节点要换行,并且在同一行中,第一个节点的域宽是后序节点的一半。上面的函数在树的层次少于等于5(height<=4)的时候能正常显示,再多的话就必须输出到文件中去ofstream outfile("out.txt");——如果层次再多的话,打印出来也没什么意义了。 更多内容请看C/C++技术专题 数据结构 数据结构教程专题,或
二叉搜索树的实现
实际上就是在二叉树的基础上增加了插入、删除、查找。
#include "BaseTree.h"
template <class T>
class BSTree : public BTree<T>
{
public:
BTNode<T>* &find(const T &data)
{
BTNode<T>** p = &root; current = NULL;
while(*p)
{
if ((*p)->data == data) break;
if ((*p)->data < data) { current = *p; p = &((*p)->right); }
else { current = *p; p = &((*p)->left); }
}
return *p;
}
bool insert(const T &data)
{
BTNode<T>* &p = find(data); if (p) return false;
p = new BTNode<T>(data, NULL, NULL, current); return true;
}
bool remove(const T &data)
{
return remove(find(data));
}
private:
bool remove(BTNode<T>* &p)
{
if (!p) return false; BTNode<T>* t = p;
if (!p->left !p->right)
{
if (!p->left) p = p->right; else p = p->left;
if (p) p->parent = current;
delete t; return true;
}
t=p->right;while(t->left) t=t->left;p->data=t->data;current=t->parent;
return remove(current->left==t?current->left:current->right);
}
}; 以上代码有点费解,有必要说明一下——非线性链式结构操作的实现都是很让人费神。insert和remove都是以find为基础的,因此必须让find能最大限度的被这两个操作利用。
1、对于insert,需要修改查找失败时的指针内容,显然这是个内部指针(在双亲节点的内部,而不是象root和current那样在节点外面指向节点),这就要求find返回一个内部指针的引用。但是C++的引用绑定到一个对象之后,就不能再改变了,因此在find内部的实现是一个二重指针。insert操作还需要修改插入的新节点的parent指针域,因此在find中要产生一个能被insert访问的指向find返回值所在节点的指针,这里用的是current。实际上find返回的指针引用不是current->left就是current->right。这样一来,insert的实现就非常简单了。
2、对于remove,需要修改查找成功时的指针内容,同样是个内部指针。在find的基础上,很容易就能得到这个内部指针的引用(BTNode<T>* &p = find(data)。
在p->left和p->right中至少有一个为NULL的情况下,如果p->left ==NULL,那么就重连右子树p = p->right,反之,重连左子树p = p->left。注意,左右子树全空的情况也包含在这两个操作中了——在p->left ==NULL的时候重连右子树,而这时p->right也是NULL——因此不必列出来。如果重连后p不为空,需要修改p->parent = current。
若p->left和p->right都不为空,可以转化为有一个为空。例如一个中序有序序列[1,2,3,4,5],假设3既有左子树又有右子树,那么它的前驱2一定缺右子树,后继4一定缺少左子树。【注1】这样一来删除节点3就等效成从[1,2,3(4),4,5]删除节点4。这样就可以利用上面的在p->left和p->right中至少有一个为NULL的情况下的方法了。还是由于C++的引用不能改变绑定对象,这里是用利用递归来解决的,还好最多只递归一次。如果用二重指针又是满天星星了,这就是明明是尾递归却没有消去的原因。
【注1】这是因为,如果3既有左子树又有右子树,那么2一定在3的左子树上,4一定在3的右子树上;如果2有右子树,那么在2和3之间还应该有一个节点;如果4有左子树,那么3和4之间也应该还有一个节点。
【闲话】上面关于remove操作p->left和p->right都不为空的处理方法的讲解,源于严蔚敏老师的课件,看完后我豁然开朗,真不知道为什么她自己那本《数据结构(C语言版)》这里写的那么难懂,我是死活没看明白。 更多内容请看C/C++技术专题 数据结构 数据结构教程专题,或
上一篇:用游戏串起程序员的基本功之四
下一篇:Bjarne:必须在类声明处赋予数据吗?
|