数据结构 — 二叉树的线索化

二叉树的线索化




以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直 接得到结点在任一序列(先序、中序或后序序列)中的前 和后继信息,这

种信息只有在遍历的动态过程中才能得到。为了保存这种在遍历过程中得到的信息,我们利用二叉树表中的空树 ( 由于结点没有左子树或右子树),

来存放结点的前驱和后继信息。

通俗一点来说,就是为了方便遍历而形成的,其实我们更多的是学习线索化的构建思想,看似简单的将空树域的的左孩子指针和右 子指针改成指向该

种遍历方式中的该节点的前驱结点和后驱结点,其实实际实现的时候可能就会出现各种各样的问题, 我随便画 一个树。



这个问题我们可以通过,在结点里面添加成员,用来判断和记录该结点是否被线索化过。

enum PointerType //枚举类型
{
	THREAD,  //已线索化的
	LINK    //连接节点,未线索化.
};
template<class T>
struct BinaryTreeNodeThd
{
	BinaryTreeNodeThd<T>* _letf;
	BinaryTreeNodeThd<T>* _right;
	T _data;
	PointerType _leftType;
	PointerType _rightType;

	BinaryTreeNodeThd(const T& x)
		:_data(x)
		, _letf(NULL)
		, _right(NULL)
		, _leftType(LINK)
		, _rightType(LINK)
	{}
};




前序线索化




好的我们开始思考,前序线索化有一个小问题,就是当你访问到一个需要线索化的结点,你怎么知道它将要指向哪里? 它的下一个结点是谁?

我现在怎 么会知道下一刻我发生什么?这个问题该如何解决呢? 来集中精力! 开始穿越剧.

我们唯一的办法就是带你去未来,让你知道你下一步应该做什么,所以这里我们不得不创建一个参数,用来传递上一个结点,我把一个结点带到我这

里来,然后我让他需要线索化的地方指向我,而且我需要线索化的地方正好可以指向它(寻找 前驱结点)。 这样就完美的解决掉这个问题.

所以我们添加一个_prev参数用来记录上一个节点的地址,让我们可以访问并操作它。(注意,这里必须_prev的引用,要不然在函数 里面操作无效)


具体的代码:


         //重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	 //重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	 //重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	 //MD害老子调半天

	void _PreOrderTheeady(Node* cur, Node* & prev)
	{
		if (cur == NULL)
		{
			return;
		}
		//如果是空树链,它的线索化_left指向上一个结点,并标记。
		if (cur->_letf == NULL)
		{
			cur->_letf = prev;
			cur->_leftType = THREAD;
		}
		//如果是空树链,上一个结点的线索化_right指向我,并标记。
		if (prev&&prev->_right == NULL)
		{
			prev->_right = cur;
			prev->_rightType = THREAD;
		}
		prev = cur;
		//if里的条件 防止重复访问结点
		if (cur->_leftType == LINK)
		{
			_PreOrderTheeady(cur->_letf, prev);
		}
		//if里的条件 防止重复访问结点
		if (cur->_rightType == LINK)
		{
			_PreOrderTheeady(cur->_right, prev);
		}
	}

这个过程画图不好描述,大家只能参照一棵树自己走一走,体会一下为什么里面会有这多的判断条件。




线索化后的结果




中序线索化





其实意思跟前序线索化很相似,只是有的地方顺序换了一下而已。 我们来看具体的代码实现:


//中序线索化
	void _InOrderThreading(Node* cur,Node* & prev)
	{
		if (cur)
		{
			_InOrderThreading(cur->_letf, prev);
			//如果是空树链,它的线索化_left指向上一个结点,并标记。
			if (cur->_letf == NULL)
			{
				cur->_letf = prev;
				cur->_leftType = THREAD;
			}
			//如果是空树链,上一个结点的线索化_right指向我,并标记。
			if (prev&&prev->_right == NULL)
			{
				prev->_right = cur;
				prev->_rightType = THREAD;
			}
			prev = cur;
			_InOrderThreading(cur->_right, prev);
		}
	}


如果你看懂了前序线索化,那么中序我相信你都可以直接写出来了.






我们来看看效果:






后序线索化




这里是有难度的以后补充吧...








线索化的遍历:



现在线索化的实现我们已经完成了,接下来我们遍历出结果看一看,那么我们就需要实现前序遍历的函数,和中序遍历的函数,记

住对于前序线索化的二叉树只能使用对应的前序遍历函数,中序遍历也一样。

好的我们从前序开始:如果你认为跟数组遍历一样,那就翻车了,因为这里的指向有点混乱如果单纯地_right的往后走很容易出错

前序遍历的实现代码:


//前序线索化遍历
	void prevOrder()
	{
		Node* cur = _root;
		while (cur)
		{
			//找到最左结点
			while (cur->_leftType == LINK)
			{
				cout << cur->_data << " ";
				cur = cur->_letf;
			}
			cout << cur->_data << " ";
			cur = cur->_right;	
		}
	}



再来看看中序遍历的实现代码:

//针对中序线索化以后的遍历
	void inorderThd()
	{
		Node* cur = _root;
		while (cur)
		{
			//找到最左结点,开始遍历.
			while (cur->_leftType == LINK)
			{
				cur = cur->_letf;
			}
			cout << cur->_data << " ";
			//原始版本,放在这里有助于理解
	        /*if (cur->_rightType == LINK)
			{
				cur = cur->_right;
			}
			else
			{
				while (cur->_rightType == THREAD)
				{
					cur = cur->_right;
					cout << cur->_data << " ";
				}
				cur = cur->_right;
			}*/

			//****优化后****
			while (cur->_rightType == THREAD)
			{
				cur = cur->_right;
				cout << cur->_data << " ";
			}
			cur = cur->_right;

		}
	}

为什么中序遍历为什么会多几句判断条件,大家自己走一遍过程,就会理解到了呢。

具体这里最后为什么是while(cur->_rightType == THREAD)而不是if()呢?




当二叉树为这样的时候,你觉得使用if()合适吗? 这里如果用if()会造成数据遍历缺失.



线索化全部代码实现与测试:

#include<iostream>
#include<Windows.h>
using namespace std;

enum PointerType //枚举类型
{
	THREAD,  //已线索化的
	LINK    //连接节点,未线索化.
};
template<class T>
struct BinaryTreeNodeThd
{
	BinaryTreeNodeThd<T>* _letf;
	BinaryTreeNodeThd<T>* _right;
	T _data;
	PointerType _leftType;
	PointerType _rightType;

	BinaryTreeNodeThd(const T& x)
		:_data(x)
		, _letf(NULL)
		, _right(NULL)
		, _leftType(LINK)
		, _rightType(LINK)
	{}
};

template<class T>
class BinaryTreeThd
{
	typedef BinaryTreeNodeThd<T> Node;

public:
	BinaryTreeThd(T* a, size_t n, const T& invalid)
	{
		size_t index = 0;
		_root = CreateTree(a, n, invalid, index);
	}
	void InOrderThreading()
	{
		Node* prev = NULL;
		_InOrderThreading(_root, prev);
	}
	void PreOrderTheeady()
	{
		Node* prev = NULL;
		_PreOrderTheeady(_root, prev);
	}

	//前序线索化遍历
	void prevOrder()
	{
		Node* cur = _root;
		while (cur)
		{
			//找到最左结点
			while (cur->_leftType == LINK)
			{
				cout << cur->_data << " ";
				cur = cur->_letf;
			}
			cout << cur->_data << " ";
			cur = cur->_right;	
		}
	}
	/*if (cur->_leftType == THREAD || cur->_rightType == THREAD)
	{
	cout << cur->_data << " ";
	cur = cur->_right;
	}*/



	//针对中序线索化以后的遍历
	void inorderThd()
	{
		Node* cur = _root;
		while (cur)
		{
			//找到最左结点,开始遍历.
			while (cur->_leftType == LINK)
			{
				cur = cur->_letf;
			}
			cout << cur->_data << " ";
			//原始版本,放在这里有助于理解
	        /*if (cur->_rightType == LINK)
			{
				cur = cur->_right;
			}
			else
			{
				while (cur->_rightType == THREAD)
				{
					cur = cur->_right;
					cout << cur->_data << " ";
				}
				cur = cur->_right;
			}*/

			//****优化后****
			while (cur->_rightType == THREAD)
			{
				cur = cur->_right;
				cout << cur->_data << " ";
			}
			cur = cur->_right;

		}
	}

protected:

	//中序线索化
	void _InOrderThreading(Node* cur,Node* & prev)
	{
		if (cur)
		{
			_InOrderThreading(cur->_letf, prev);
			//如果是空树链,它的线索化_left指向上一个结点,并标记。
			if (cur->_letf == NULL)
			{
				cur->_letf = prev;
				cur->_leftType = THREAD;
			}
			//如果是空树链,上一个结点的线索化_right指向我,并标记。
			if (prev&&prev->_right == NULL)
			{
				prev->_right = cur;
				prev->_rightType = THREAD;
			}
			prev = cur;
			_InOrderThreading(cur->_right, prev);
		}
	}


	//重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	//重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	//重要的事情说3遍,这里的Node* & 必须加上引用,要不然上层对prev的修改就是没有意义的.
	//MD害老子调半天

	void _PreOrderTheeady(Node* cur, Node* & prev)
	{
		if (cur == NULL)
		{
			return;
		}
		//如果是空树链,它的线索化_left指向上一个结点,并标记。
		if (cur->_letf == NULL)
		{
			cur->_letf = prev;
			cur->_leftType = THREAD;
		}
		//如果是空树链,上一个结点的线索化_right指向我,并标记。
		if (prev&&prev->_right == NULL)
		{
			prev->_right = cur;
			prev->_rightType = THREAD;
		}
		prev = cur;
		//if里的条件 防止重复访问结点
		if (cur->_leftType == LINK)
		{
			_PreOrderTheeady(cur->_letf, prev);
		}
		//if里的条件 防止重复访问结点
		if (cur->_rightType == LINK)
		{
			_PreOrderTheeady(cur->_right, prev);
		}
	}
	//注意这里的index 要使用引用, 若是没有使用 上一层递归里面的++index 对上一层没有任何影响.
	//构建二叉树表
	Node* CreateTree(T* a, size_t n, const T& invalid, size_t& index)
	{
		Node* root = NULL;
		if (index < n && a[index] != invalid)
		{
			root = new Node(a[index]);
			root->_letf = CreateTree(a, n, invalid, ++index);
			root->_right = CreateTree(a, n, invalid, ++index);
		}
		return root;
	}

protected:

	Node* _root;
	Node* _left;
	Node* _right;
};


void Test()
{
	int array[10] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, 6 };
	BinaryTreeThd<int> t1(array, sizeof(array) / sizeof(array[0]), '#');
	/*t1.InOrderThreading();
	t1.inorderThd();*/
	t1.PreOrderTheeady();
	t1.prevOrder();
}


  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值