(持续更新中…
利用栈实现二叉树的前序遍历
访问二叉树一个结点后,打印其数据后,先判断其是否有右子女,有的话则入栈,没有的话则判断其是否有左子女,有的话则左进到左子女(p=p->Leftchild),没有则弹栈,并访问。
BinTreeNode<T> *p = root;//初始化
stack.Push(NULL);
while (p != NULL) {
visit(p);
if (p->rightchild != NULL)stack.Push(p->rightchild);
if (p->leftchild != NULL)p = p->leftchild;
else stack.Pop(p);
}
利用队列实现二叉树的层次遍历
使用队列实现二叉树的层次遍历和上面不同。上面是先入右子女,而使用队列实现层次遍历,是先入左子女,再右子女入队。
BinTreeNode<T>*p = root;
Queue.EnQueue(p);
while (!Queue.IsEmpty()) {
Queue.DeQueue(p);
visit(p);
if (p->leftchild != NULL)Queue.EnQueue(p->leftchild);
if (p->childchild != NULL)Queue.EnQueue(p->rightchild);
}
利用栈实现二叉树的中序遍历
先访问根结点,再一直访问左子女并压入栈中,直至NULL,也就是访问到最左下的结点,然后再出栈并访问最左下结点,再遍历该结点右子树,算法结束条件为栈为空并且遍历的指针也要为空。首先栈肯定得为空,如果栈为空,而指针不为空,说明右子树不为空,很明显应该继续遍历右子树。
BinTreeNode<T>*p = root;
do {
while (p != NULL) {
stack.Push(p);
p = p->leftchild;
}
if (!stack.IsEmpty) {
stack.Pop(p);
visit(p);
p = p->rightchild;
}
} while (p != NULL || !stack.IsEmpty());//这里需要注意这里是只要指针
//不为空或栈不为空那么就会进入do-while循环里面
利用栈实现二叉树的后序遍历
比前序和中序都复杂,需要最后访问根结点,所以需要注明在左子树还是在右子树。给出栈的结点定义:
template<class T>
struct stkNode {
BinTreeNode<T> *ptr;
enum tag{L,R};
stkNode(BinTreeNode<T> *N=NULL):ptr(N),tag(L){}
};
先是根结点入栈,并且标记为L,然后访问左子树访问到底,然后这个时候最后入栈的结点是最左下的结点,然后改该结点标记为R,再右进,然后再访问其左子树、右子树,如果一个结点无左右子树,且为R的时候,则出栈。
template<class T>
void BinaryTree<T>::PostOrder(void(*visit)(BinTreeNode<T> *p)) {
stack<stkNode<T>> S; stkNode<T>w;
BinTreeNode<T> *p = root;
do {
while (p != NULL) {
w.ptr = p; w.tag = L; S.Push(w);
p = p->leftChild;
}
int continue1 = 1;
while (continue1 && !S.IsEmpty()) {
S.Pop(w); p = w.str;
switch (w.tag)
{
case L:
w.tag = R; S.Push(w);
continue1 = 0;
p = p->rightChild;
break;
case R:
visit(p); break;
}
}
} while (!S.IsEmpty());
}
线索二叉树
为了很快找到某一结点的前驱或后继而又不需要对二叉树遍历一次,于是诞生了线索二叉树。线索二叉树每个结点多出两个位置记录前驱和后继的指针。但是那样又会浪费空间,于是改为标记是指向前驱(后继)还是左子树(右子树)。
那么如何寻找中序线索二叉树当前结点的后继结点呢,见下图所示。
\rtag rightchild | == 0 (右子女指针) | == 1 (后继线索) |
---|---|---|
== NULL | 无此情况 | 无后继 |
!=NULL | 后继为当前结点右子树的中序下的第一个结点 | 后继为右子女结点 |
如果rtag为1,也就是rightchild放的就是后继结点,那么直接可以找到;否则需要一定运算才可以找到。
寻找结点的前驱的操作与寻找后继类似,见下图。
leftchild \ ltag | == 0 (左子女指针) | == 1 (前驱线索) |
---|---|---|
== NULL | 无此情况 | 无前驱 |
!=NULL | 前驱为当前结点左子树的中序下最后一个结点 | 前驱为左子女结点 |
中序线索化二叉树
中序线索化二叉树的操作 为递归的方法:
pre应传引用进去,才能记录下上次访问的节点,否则节点的右子树的线索化不能完成
void InOrder_Tree(Node* root, Node*& pre) {
if (root == NULL) {//如果为空则返回上一个结点
return;
}
else {
InOrder_Tree(root->lchild, pre);//先左子树搜索化
if (root->lchild == NULL) {//如果该结点没有左子树
root->lchild = pre;//指向前驱结点
root->ltag = 1;//将左标记设置为1表示指向前驱
}
if (pre&&pre->rchild == NULL) {//如果前驱不为空且前驱没有右子树
pre->rchild = root;//前驱指向它的后继
pre->rtag = 1;//将前驱右标记设为1表示指向后继
}
pre = root;//往下走,pre往下改
InOrder_Tree(root->rchild, pre);//再右子树搜索化
}
}
注:if (pre&&pre->rchild == NULL)
这里为什么没有考虑root的右子树为NULL的情况:因为就算root没有右子树,我们也不知道它的后继是什么,因为我们现在只遍历到root,还没有往下遍历,我们只知道root还有它的前驱pre,而前驱pre我们可以知道,是因为我们从前驱遍历过来的。就好像我们去探险,我们只知道我们走过的路怎么样,并不知道后面的路是什么样的。然后我们走过的路,我们为了可以指向前驱,我们也做了标记,也就是pre = root;
前序线索化二叉树与中序线索化类似:
void PreOrder_Tree(Node* root, Node*& pre) {
if (root == NULL) {//如果为空则返回上一个结点
return;
}
if (root->lchild == NULL) {//如果该结点没有左子树
root->lchild = pre;//指向前驱结点
root->ltag = 1;//将左标记设置为1表示指向前驱
}
if (pre&&pre->rchild == NULL) {//如果前驱不为空且前驱没有右子树
pre->rchild = root;//前驱指向它的后继
pre->rtag = 1;//将前驱右标记设为1表示指向后继
}
pre = root;//往下走,pre往下改
if (root->ltag == 0) {
PreOrder_Tree(root->lchild, pre);
}
if (root->rtag == 0) {
PreOrder_Tree(root->rchild, pre);
}
}
而中序线索化的遍历以及前序线索化的遍历同样是差不多的:
void InOrder() {//中序线索化的遍历
Node* current = root;
while (current) {
while (current->ltag == 0) {//需要一直找到最左下的结点
current = current->lchild;
}
cout << current->data << ' ';//找到了,输出其存储的数据
while (current&¤t->rtag == 1) {//因为已经线索化了,现在
current = current->rchild;//只需要一直输出后继就好
cout << current->data << ' ';
}
current = current->rchild;//右树不是线索树,查找其右结点
}
}
void PreOrder() {//前序线索化的遍历
Node* current = root;
while (current) {
while (current->ltag == 0) {//前序与后序不同,在寻找最左下的结点的路途中
cout << current->data << ' ';//需要我们一路输出我们走的结点的值
current = current->lchild;//因为前序是根左右,故需要输出
}
cout << current->data << ' ';//左子树最后一个结点记得输出
current = current->rchild;
}
}