1. 程式人生 > >【c++】二叉樹的線索化

【c++】二叉樹的線索化

什麼是二叉樹的線索化?或者問什麼是線索二叉樹?

按照某種遍歷方式對二叉樹進行遍歷,可以把二叉樹中所有結點排序為一個線性序列。在改序列中,除第一個結點外每個結點有且僅有一個直接前驅結點;除最後一個結點外每一個結點有且僅有一個直接後繼結點。這些指向直接前驅結點和指向直接後續結點的指標被稱為線索(Thread),加了線索的二叉樹稱為線索二叉樹。

以上是搜狗百科的一段文字,反正我是沒看太懂。簡單點,以我的理解,線索二叉樹就是充分利用了二叉樹結點中的空指標,讓它們分別指向本結點的前驅或者後繼。既充分利用了資源,又可以讓我們方便遍歷這棵樹。

什麼是二叉樹結點的空指標?

我們先看一棵樹。圖中的結點3,4,6的左右指標,結點5的右指標等類似指標都為空指標。

\

什麼又叫讓這些空指標指向本節點的前驅或者後繼呢?

這個問題分為3中情況,前序,中序,後序。例如上圖中的樹前序遍歷序列為1,2,3,4,5,6。這樣,我們就可以讓結點3的左指標指向它的前驅2,結點3的右指標指向它的後繼4。讓結點4的左指標指向4的前驅3,讓結點4的右指標指向4的後繼5。結點5的左不為空,所以不操作。結點5的右為空,讓結點5的右指向5的後繼6。結點6的左右都為空,讓結點6的左指標指向6的前驅5,因為6為最後一個元素,6的後繼為空,所以讓結點6的右指標指向空。

讓我們把指標重置後的圖畫出來:


類似的,後序與中序我就不再細說,這裡把圖解給大家,一看便知。


我建議大家親自動手把這3個圖畫一下,有奇效!!!!!!!

道理大家都懂了,那麼程式碼該如何寫呢? 答案:遞迴。

線索二叉樹的結點定義和普通二叉樹的結點定義不一樣,我們額外需要兩個標誌_leftTag和_rightTag。

enum Type
{
	THREAD,//表示指標被線索化
	LINK//表示指標未被線索化
};
template<typename T>
struct BinaryTreeNode
{
	T _data;
	BinaryTreeNode<T> *_left;
	BinaryTreeNode<T> *_right;
	Type _leftTag;//標識左指標
	Type _rightTag;//標識右指標
	BinaryTreeNode(const T& x)//建構函式
		:_data(x)
		, _left(NULL)
		, _right(NULL)
		, _leftTag(LINK)
		, _rightTag(LINK)
	{}

	BinaryTreeNode()//預設建構函式
	{}
};
我們用Type型別的_leftTag來標識左指標,當_leftTag等於THREAD時,表明這個指標已經被線索化(例圖中紫色指標)。當_leftTag等於LINK時,表明這個指標沒有被線索化,是普通的二叉樹指標(例圖中紅色指標)。

因為線上索化過程中,我們需要讓當前結點的前驅指向當前結點,所以我們需要設立一個prev來儲存上一次訪問的結點。這個prev要麼設定成全域性變數,要麼設定成區域性靜態變數,切記!

遞迴程式碼:

前序

//前序線索化
void _PrevOrder_Thd(Node* _root)
{
        static Node* prev = NULL;
	if (_root)
	{
		if (!_root->_left)
		{
			_root->_leftTag = THREAD;
			_root->_left = prev;
		}
		if (prev && !prev->_right)
		{
			prev->_rightTag = THREAD;
			prev->_right = _root;
		}
		prev = _root;
		if (_root->_leftTag == LINK)
			_PrevOrder_Thd(_root->_left);
		if (_root->_rightTag == LINK)
			_PrevOrder_Thd(_root->_right);
	}
}



中序
//中序線索化
void _InOrder_Thd(Node* _root)
{
	static Node* prev = NULL;
	if (_root)
	{
		if (_root->_leftTag == LINK)
		{
			_InOrder_Thd(_root->_left);
		}
		if (!_root->_left)
		{
			_root->_leftTag = THREAD;
			_root->_left = prev;
		}
		if (prev && !prev->_right)
		{
			prev->_rightTag = THREAD;
			prev->_right = _root;
		}
		prev = _root;
		if (_root->_rightTag == LINK)
		{
			_InOrder_Thd(_root->_right);
		}
	}
}


後序
	//後序線索化
void _PostOrder_Thd(Node* _root)
{
	if (_root == NULL)
	{
		return;
	}
	static Node* prev = NULL;
	_PostOrder_Thd(_root->_left);
	_PostOrder_Thd(_root->_right);
	if (!_root->_left)
	{
		_root->_leftTag = THREAD;
		_root->_left = prev;
	}
	if (prev && !prev->_right)
	{
		prev->_rightTag = THREAD;
		prev->_right = _root;
	}
	prev = _root;
}



最後,來說一下今年的一道面試題,如何將一個二叉樹轉化成一個有序的雙向連結串列?

在你沒學線索化之前,這道題可能很麻煩。但是現在不同了,利用中序線索化的思想可以很快將這道題解出來!

//利用中序線索化思想將搜尋二叉樹轉換成有序的雙向連結串列
void _TreeToList(Node* _root)
{
	static Node* prev = NULL;//設立prev儲存上一次訪問的結點
	if (_root == NULL)//如果根結點為空,表明樹空,直接返回.
	{
		return;
	}
	_TreeToList(_root->_left);//遞迴左子樹
	_root->_left = prev;//讓當前結點的左指標指向上一次訪問的結點,即前驅。
	if (prev)
	{
		prev->_right = _root;//讓prev指向當前結點,構成雙向
	}
	prev = _root;//更新prev
	_TreeToList(_root->_right);//遞迴右子樹
}

有哪裡寫的不對請大家指出來,不明白的也可以留言問我,互相學習!