二叉树的遍历以及线索化

(持续更新中…

利用栈实现二叉树的前序遍历

访问二叉树一个结点后,打印其数据后,先判断其是否有右子女,有的话则入栈,没有的话则判断其是否有左子女,有的话则左进到左子女(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&&current->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;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值