二叉树结点的抽象数据类型:
template<class T>
class BTreeNode
{
friend class BTree<T>;//声明二叉树类为结点类的友元类,以便访问私有数据
private:
T info;//二叉树结点数据域
public:
BTreeNode(); //默认构造函数
BTreeNode(const T& ele);//给定数据的构造函数
BTreeNode(const T& ele, BTreeNode<T>* l, BTreeNode<T>* r);//子树构造结点
T value() const; //返回当前结点的数据
BTreeNode<T>* lchild()const;//返回当前结点的左子树
BTreeNode<T>* rchild()const;//返回当前结点的右子树
void setLchild(BTreeNode<T>*);//设置当前结点的左子树
void setRchild(BTreeNode<T>*);//设置当前结点的右子树
void setValue(const T& val);//设置当前结点的数据域
bool isLeaf()const; //判断当前结点是否为叶结点
BTreeNode<T>& operator=(const BTreeNode<T>& Node);//重载赋值操作符
};
template<class T>
class BTree {
private:
BTreeNode<T>* root; //二叉树根节点
public:
BTree() {
root = NULL; }//构造函数
~BTree() {
DeleteBTree(root); }//析构函数
bool isEmpty()const;//判断二叉树是否为空树
BTreeNode<T>* Root() {
return root; };//返回二叉树根结点
BTreeNode<T>* Parent(BTreeNode<T>* current);//返回当前结点的父结点
BTreeNode<T>* Lsibling(BTreeNode<T>* current);//返回当前结点的左兄弟
BTreeNode<T>* Rsibling(BTreeNode<T>* current);//返回当前结点的右兄弟
void CreatTree(const T& info, BTree<T>& Ltree, BTree<T>& Rtree);//构造新树
void PreOrder(BTreeNode<T>* root);//前序周游给定二叉树
void InOrder(BTreeNode<T>* root);//中序周游给定二叉树
void PostOrder(BTreeNode<T>* root);//后序周游给定二叉树
void LevelOrder(BTreeNode<T>* root);//按层次周游给定二叉树
void DeleteBTree(BTreeNode<T>* root);//删除给定的二叉树
};
template <class T>
void BTree<T>::PreOrder(BTreeNode<T>* root) {
//前序周游二叉树或其子树
if (root != NULL) {
Visit(root->value());//访问当前结点
PreOrder(root->lchild());//前序周游左子树
PreOrder(root->rchild());//前序周游右子树
}
}
template<class T>
void BTree<T>::InOrder(BTreeNode<T>* root) {
//中序周游二叉树或其子树
if (root != NULL) {
InOrder(root->lchild());//中序周游左子树
Visit(root->value());//访问当前结点
InOrder(root->rchild());//中序周游右子树
}
}
template<class T>
void BTree<T>::PostOrder(BTreeNode<T>* root) {
//后序周游二叉树或其子树
if (root != NULL) {
PostOrder(root->lchild());//后序周游左子树
PostOrder(root->rchild());//后序周游右子树
Visit(root->value());//访问当前结点
}
}
深度优先周游二叉树的非递归算法
如何把递归程序转化成等价的非递归算法?解决这个问题的关键就是设置一个栈结构。按照递归算法执行过程中编译栈的工作原理,可以写出非递归周游二叉树的算法。
非递归前序周游算法的主要思想是:每遇到一个结点,先访问该结点,并把该结点的非空右子节点推入栈中,然后周游其左子树;左子树周游不下去时,从栈顶弹出待访问的结点,继续周游。 算法执行过程中,只有非空结点入栈。为了算法简洁,最开始压入一个空指针作为监视哨;当这个空指针被弹出来时,则周游结束。
非递归前序周游二叉树或其子树
template<class T>
void BTree<T>::PreOrderWithoutRecursion(BTreeNode<T>* root){
using std::stack; //使用STL中的栈
stack<BTreeNode<T>*>aStack;
BTreeNode<T>* pointer = root;
aStack.push(NULL);//栈底监视哨
while(pointer) //或者!aStack.empty()
{
Visit(pointer->value());//访问当前结点
if (pointer->rchild() != NULL)
aStack.push(pointer->rchild());
if (pointer->lchild() != NULL)
pointer = pointer->lchild();//左路下降
else {
//左子树访问完毕,转向访问右子树
pointer = aStack.top();//获得栈顶元素
aStack.pop(); //栈顶元素退栈
}
}
}
非递归中序周游二叉树算法的主要思想是:每遇到一个结点就把它压入栈,然后去周游其左子树;周游完左子树后,从栈顶弹出并访问这个结点,然后按照其右链接指示的地址再去周游该结点的右子树
非递归中序周游二叉树的非递归算法
template<class T>
void BTree<T>::InOrderWithoutRecursion(BTreeNode<T>* root) {
using std::stack;//使用STL中的栈
stack<BTreeNode<T>*>aStack;
BTreeNode<T>* pointer = root;
while (!aStack.empty() || pointer) {
if (pointer) {
aStack.push(pointer);//当前指针入栈
pointer = pointer->lchild();//左路下降
}
else {
//左子树访问完毕,转向访问右子树
pointer = aStack.top();//获得栈顶元素
aStack.pop();//栈顶元素退栈
Visit(pointer->value());//访问当前结点
pointer = pointer->rchild();//指针指向右孩子
}
}
}
后序周游二叉树时最先处理左子树,然后是右子树,最后才访问当前结点。
在非递归的后序周游算法中,先把它压入栈中,去周游它的左子树;周游完它的左子树后,应继续周游该结点的右子树;周游完右子树之后,才从栈顶弹出该结点并访问它。由于访问某个节点前需要知道是否已经访问该结点的右子树,因此需要给栈中每个元素加一个标志位tag。标志位用枚举类型Tags表示:Left表示已进入该结点的左子树;Right表示已进入该结点的右子树。
enum Tags {
Left, Right };//定义枚举类型标志位
template<class T>
class StackElement {
//栈元素的定义
public:
BTreeNode<T>* pointer;//指向二叉树结点的指针
Tags tag;//标志位
};
template<class T>
void BTree<T>::PostOrderWithoutRecursion(BTreeNode<T>* root) {
using std::stack;//使用STL的栈
StackElement<T>element;
stack<StackElement<T>>aStack;
BTreeNode<T>* pointer;
if (root == NULL)//如果是空树则返回
return;
else pointer = root;
while (!aStack.empty() || pointer) {
while (pointer != NULL) {
//如果当前指针非空则压栈并下降到最左子节点
element.pointer = pointer;
element.tag = Left;//置标志位为Left,表示进入左子树
aStack.push(element);
pointer = pointer->lchild();
}element = aStack.top();//获得栈顶元素
aStack.pop();//栈顶元素退栈
pointer = element.pointer;
if (element.tag == Left) {
//如果从左子树返回
element.tag = Right;//置标志位为Right,表示进入右子树
aStack.push(element);
pointer = pointer->rchild();
}
else {
//如果从右子树返回
Visit(pointer->value());//访问当前结点
pointer = NULL;//置point指针为空,以继续弹栈
}
}
}
不管采用哪种周游方式,对于有n个结点的二叉树,周游完树的所有元素都需要O(n)时间。只要对每个结点的处理(函数Visit的执行)时间是一个常数,那么,周游二叉树就可以在线性时间内完成。所需要的辅助空间为周游过程中栈的最大容量,即树的高度。最坏情况下,具有n个结点的二叉树高度为n,所需要的空间复杂度为O(n)。
根据层次周游二叉树的性质,这里需要使用一个队列作为辅助的存储结构。层次周游过程的实现就是从根结点开始逐层逐个地访问各个节点。
**在周游开始的时候,首先将根结点放入队列;然后每次从队列中取出队头元素进行处理,每处理一个结点时,按从左至右的顺序把它的所有子节点放入队列。这样,上层结点总是排在下一层结点的前面,从而实现了二叉树的广度优先周游**
利用队列实现广度优先周游二叉树。初始化时,根结点插入到空队列中,周游的每一步,算法都将从队列头上删除一个结点,并将其子结点插入队尾。
template<class T>
void BTree<T>::LevelOrder(BinaryTreeNode<T>* root) {
void BTree<T>::LevelOrder(BTreeNode<T> * root) {
usint std::queue; //使用STL的队列
queue<BTreeNode<T>*>aQueue;
BTreeNode<T>* pointer = root;
if (pointer)
aQueue.push(pointer);//根节点入队列
while (!aQueue.empty()) {
//队列非空
pointer = aQueue.front();//获得队列首节点
Visit(pointer->value());//访问当前结点
if (pointer->lchild() != NULL)
aQueue.push(pointer->lchild());//左子树进队列
if (pointer->rchild() != NULL)
aQueue.push(pointer->rchild());//右子树进队列
}
}
}
广度优先周有一颗具有n个结点的二叉树,其时间复杂度也是O(n)。队列所需要的最大存储空间由二叉树中具有最多结点数目的那一层上的结点个数来决定。因此,周游一科满的完全二叉树所需要的队列空间最大,最大长度为(n+1)/2.
template<class T>
bool BTree<T>::isEmpty()const {
//判定二叉树是否为空树
return (root != NULL ? false : true);
}
template<class T>
BTreeNode<T>* BTree<T>::Parent(BTreeNode<T>* current) {
using std::stack;//使用STL中的栈
stack<BTreeNode<T>*>aStack;
BTreeNode<T>* pointer = root;
if (root != NULL && current != NULL) {
while (!aStack.empty() || pointer) {
if (pointer != NULL) {
if (current == pointer->lchild() || current == pointer->rchild())
return pointer;//如果pointer的孩子是current则返回parent
aStack.push(pointer);//当前指针入栈
pointer = pointer->lchild();//当前指针指向左孩子
}
else {
//左子树访问完毕,访问右子树
pointer = aStack.top();//获得栈顶元素
aStack.pop();//栈顶元素退栈
pointer = pointer->rchild();//当前指针指向右孩子
}
}
}
}
//创建一棵新树,参数info为根结点元素,lTree和rTree是不同的两棵树
template<class T>
void BTree<T>::CreateTree(const T& info, BTree<T>& lTree, BTree<T>& rTree) {
root = new BTreeNode<T>(info, lTree.root, rTree.root);//创建新树
lTree.root = rTree.root = NULL;//原来两棵子树的根结点置为空,避免非法访问
}
template<class T>
void BTree<T>::DeleteBTree(BTreeNode<T>* root) {
//后序周游删除二叉树
if (root != NULL) {
DeleteBTree(root->left);//递归删除左子树
DeleteBTree(root->right);//递归删除右子树
delete root;
}
}
二叉搜索树(binary search tree,BST,也可称为二叉排序树、二叉查找树等)。
假设要在二叉搜索树中检索关键码key,则从根结点开始,
如果根结点存储的值为key,则返回检索结果,检索结束。
如果不是,则必须检索树的更深层。
将给定值key与根结点的关键码比较,如果key小于根结点的值,则只需要检索左子树;如果key大于根结点的值,则只检索右子树。
这个过程一直持续到key被匹配成功或者遇到叶结点为止。如果遇到叶结点仍没有发现key,则说明key不在这棵二叉搜索树中。
二叉搜索树插入操作:
将待插入节点的关键码与根结点的关键码相比较,若待插入的关键码小于根结点的关键码,则进入左子树,否则进入右子树。
按照同样的方式沿检索路径直到叶结点,确定插入位置,把待插入结点作为一个新叶结点插入到二叉搜索树中。
template<class T>
void BSTree<T>::InsertNode(BTreeNode<T>* root, BTreeNode<T>* newpointer) {
//root指向二叉搜索树的根,newpointer指向待插入的新结点
BTreeNode<T>* pointer = NULL;
if (root == NULL) {
//如果是空树
Initialize(newpointer);//则用指针newpointer作为树根
return;
}
else pointer = root;
while (pointer != NULL) {
if (newpointer->value() == pointer->value())//如果存在相等的元素则不用插入
return;
else if (newpointer->value() < pointer->value()) {
//如果待插入结点小于pointer的关键码值
if (pointer->lchild() == NULL) {
//如果pointer没有左孩子
pointer->left = newpointer;//newpointer作为pointer的左子树
return;
}
else pointer = pointer->leftchild();//向左下降
}
else {
//若待插入结点大于pointer的关键码值
if (pointer->rchild() == NULL)//如果pointer没有右孩子
{
pointer->right = newpointer;//newpointer作为pointer的右子树
return;
}
else pointer = pointer->rchild();//向右下降
}
}
}
要保持二叉搜索树的性质,就不能在二叉搜索树中留下一个空位置,因此需要用另一个结点来填充这个位置并且保持性质。
设pointer、temppointer是指针变量,其中pointer表示要删除的结点。首先,找到待删除的结点pointer,删除该结点的过程如下:
改进的二叉搜索树结点删除算法的思想: