資料結構之線索二叉樹的前序,中序和後序遍歷
BinaryTree線索化二叉樹>
二叉樹是一種非線性結構,在之前實現的二叉樹遍歷中不管是遞迴還是非遞迴用二叉樹作為儲存結構時只能取到該結點的左孩子和右孩子,不能得到該結點的前驅和後繼。為了儲存這種在遍歷中需要的資訊,同時也為了充分利用結點中的空指標域,我們利用二叉樹中指向左右子樹的空指標來存放結點的前驅和後繼.同時在有n個結點的二叉連結串列中必定存在n+1個空鏈域.
那仫問題來了如何充分的利用這些空鏈域來實現線索化呢?
假設做如下規定:若結點有左子樹則其左孩子域指向該結點的左孩子,否則另左孩子指向其前驅;若結點有右子樹則其右孩子域指向該結點的右孩子,否則另右孩子域指向其後繼.
一.線索化及中序遍歷一棵樹
要線索化一顆二叉樹,最重要的就是在訪問了當前結點並確定當前結點的左孩子域或者右孩子域為空後如何找到當前結點的前一個結點也就是它的前驅,和當前結點的後繼.在這裡我們用到了一個_prev指標用來儲存你訪問結點的前一個結點--如果當前結點的左子樹域為空則使當前結點的左孩子域指向_prev所指向的結點也就是當前結點的前驅;我們知道中序是按照左-根-右的順序遍歷的,如果當前結點的左訪問過了,根也訪問過了按照中序遍歷的順序接下來就應該訪問右了-_prev指標指向的是當前結點的前一個結點要訪問右則只需判斷_prev的右孩子域是否為空就可以了,如果為空則使_prev的右孩子域指向它的後繼.
void _InOrderThread(Node *root) { if(root == NULL) return ; //左子樹 _InOrderThread(root->_left); //根 if (root->_left == NULL) { root->_leftTag=THREAD; root->_left=_prev; } if (_prev && _prev->_right == NULL) { _prev->_rightTag=THREAD; _prev->_right=root; } _prev=root; //右子樹 _InOrderThread(root->_right); }
中序線索化一顆二叉樹後如何遍歷呢?和之前非遞迴的方法類似,使得cur指標指向該樹的左路結點,如果該結點有右子樹則訪問右子樹,如果沒有右子樹則繼續修改cur的值,使其指向當前結點的後繼.
void _InOrder(Node *root)
{
if(root == NULL)
return ;
Node *cur=root;
while (cur)
{
//一路向左找到最左的結點.
while (cur->_leftTag == LINK)
{
cur=cur->_left;
}
cout<<cur->_data<<" ";
//有後繼
while (cur && cur->_rightTag == THREAD)
{
cur=cur->_right;
cout<<cur->_data<<" ";
}
//沒有後繼有右子樹
cur=cur->_right;
}
}
二.線索化及前序遍歷一棵樹>
前序遍歷的線索化類似中序遍歷的線索化,但是因為前序的順序是根-左-右,會因為遞迴太深導致棧溢位的問題,所以在遞迴線索化左子樹和右子樹的時候需要判斷當前結點的左右標誌是否為LINK型別-是才遞迴線索化左子樹和右子樹.
void _PrevOrderThread(Node *root)
{
if(root == NULL)
return ;
if (root->_left == NULL)
{
root->_leftTag=THREAD;
root->_left=_prev;
}
if (_prev && _prev->_right == NULL)
{
_prev->_rightTag=THREAD;
_prev->_right=root;
}
_prev=root;
if(root->_leftTag == LINK) //防止棧溢位
_PrevOrderThread(root->_left);
if(root->_rightTag == LINK)
_PrevOrderThread(root->_right);
}
void _PrevOrder(Node *root)
{
if(root == NULL)
return ;
Node *cur=root;
while (cur)
{
while (cur && cur->_leftTag == LINK)
{
cout<<cur->_data<<" ";
cur=cur->_left;
}
cout<<cur->_data<<" ";
//將該樹的後繼當成子樹來訪問
cur=cur->_right;
//while (cur && cur->_rightTag == THREAD)
//{
// cur=cur->_right;
// cout<<cur->_data<<" ";
//}
//cur=cur->_right;
}
}
三.線索化及後序遍歷一棵樹>
在後序線索樹中找一個結點的前驅很容易直接用_prev就可以找到,那仫如何找一個結點的後繼呢?這時就比較棘手了
也就是說,在後序線索化樹上找後繼時需要知道結點雙親,此時我們可以使用三叉連結串列做儲存結構找到其雙親結點,其實還有一種思路就是逆向思維的方式----我們知道在後序遍歷線索二叉樹時有的時候是不容易找到一個結點的後繼的,如果我們按照根-右-左的順序逆序遍歷一顆線索二叉樹就會變的非常簡單了.
在實現中我只實現第二種思路即倒著訪問一顆後序線索化的二叉樹.
程式原始碼>
BinaryTreeThd.h
#pragma once
enum PointerTag
{
THREAD, //0:線索
LINK //1:指標
};
template<typename T>
struct BinaryTreeThdNode
{
BinaryTreeThdNode(const T& x=T())
:_data(x)
,_left(NULL)
,_right(NULL)
,_leftTag(LINK)
,_rightTag(LINK)
{}
T _data;
BinaryTreeThdNode<T> *_left;
BinaryTreeThdNode<T> *_right;
PointerTag _leftTag; //左孩子線索化標誌
PointerTag _rightTag; //右孩子線索化標誌
};
template<typename T>
class BinaryTreeThd
{
typedef BinaryTreeThdNode<T> Node;
public:
BinaryTreeThd(const T *a,size_t size,const T& invalid)
:_root(NULL)
,_prev(NULL)
{
size_t index=0;
_root=_CreatTreeThd(a,size,index,invalid);
}
public:
void PrevOrderThread() //前序線索化二叉樹
{
_PrevOrderThread(_root);
}
void PrevOrder() //前序遍歷二叉樹
{
_PrevOrder(_root);
cout<<endl;
}
void InOrderThread() //中序線索化二叉樹
{
_InOrderThread(_root);
}
void InOrder() //中序遍歷二叉樹
{
_InOrder(_root);
cout<<endl;
}
void PostOrderThread() //後序線索化二叉樹
{
_PostOrderThread(_root);
}
void PostOrder()
{
_PostOrder(_root);
cout<<endl;
}
protected:
Node *_CreatTreeThd(const T *a,size_t size,size_t& index,const T& invalid)
{
assert(a);
Node *root=NULL;
if (index < size && a[index] != invalid)
{
root=new Node(a[index]);
root->_left=_CreatTreeThd(a,size,++index,invalid);
root->_right=_CreatTreeThd(a,size,++index,invalid);
}
return root;
}
protected:
void _PrevOrderThread(Node *root)
{
if(root == NULL)
return ;
if (root->_left == NULL)
{
root->_leftTag=THREAD;
root->_left=_prev;
}
if (_prev && _prev->_right == NULL)
{
_prev->_rightTag=THREAD;
_prev->_right=root;
}
_prev=root;
if(root->_leftTag == LINK) //防止棧溢位
_PrevOrderThread(root->_left);
if(root->_rightTag == LINK)
_PrevOrderThread(root->_right);
}
void _InOrderThread(Node *root)
{
if(root == NULL)
return ;
//左子樹
_InOrderThread(root->_left);
//根
if (root->_left == NULL)
{
root->_leftTag=THREAD;
root->_left=_prev;
}
if (_prev && _prev->_right == NULL)
{
_prev->_rightTag=THREAD;
_prev->_right=root;
}
_prev=root;
//右子樹
_InOrderThread(root->_right);
}
void _PostOrderThread(Node *root)
{
if(root == NULL)
return ;
_PostOrderThread(root->_left);
_PostOrderThread(root->_right);
if (root->_left == NULL)
{
root->_leftTag=THREAD;
root->_left=_prev;
}
if (_prev && _prev->_right == NULL)
{
_prev->_rightTag=THREAD;
_prev->_right=root;
}
_prev=root;
}
void _PrevOrder(Node *root)
{
if(root == NULL)
return ;
Node *cur=root;
while (cur)
{
while (cur && cur->_leftTag == LINK)
{
cout<<cur->_data<<" ";
cur=cur->_left;
}
cout<<cur->_data<<" ";
//將該樹的後繼當成子樹來訪問
cur=cur->_right;
//while (cur && cur->_rightTag == THREAD)
//{
// cur=cur->_right;
// cout<<cur->_data<<" ";
//}
//cur=cur->_right;
}
}
void _InOrder(Node *root)
{
if(root == NULL)
return ;
Node *cur=root;
while (cur)
{
//一路向左找到最左的結點.
while (cur && cur->_leftTag == LINK)
{
cur=cur->_left;
}
cout<<cur->_data<<" ";
//有後繼
while (cur && cur->_rightTag == THREAD)
{
cur=cur->_right;
cout<<cur->_data<<" ";
}
//沒有後繼有右子樹
cur=cur->_right;
}
}
//倒著遍歷後序線索化二叉樹
//根-右-左
void _PostOrder(Node *root)
{
if(root == NULL)
return ;
Node *cur=root;
while (cur)
{
while (cur && cur->_rightTag == LINK)
{
cout<<cur->_data<<" ";
cur=cur->_right;
}
cout<<cur->_data<<" ";
//有前驅
while (cur->_left && cur->_leftTag == THREAD)
{
cur=cur->_left;
cout<<cur->_data<<" ";
}
//有左子樹
cur=cur->_left;
}
}
protected:
Node *_root;
Node *_prev; //儲存當前結點的前一個結點
};
test.cpp
void testBinaryTreeThd()
{
int array[15] = {1,2,'#',3,'#','#',4,5,'#',6,'#',7,'#','#',8};
size_t size=sizeof(array)/sizeof(array[0]);
BinaryTreeThd<int> bt1(array,size,'#');
cout<<"線索化中序遍歷>";
bt1.InOrderThread();
bt1.InOrder(); //3 2 1 5 6 7 4 8
BinaryTreeThd<int> bt2(array,size,'#');
cout<<"線索化前序遍歷>";
bt2.PrevOrderThread();
bt2.PrevOrder(); //1 2 3 4 5 6 7 8
BinaryTreeThd<int> bt3(array,size,'#');
cout<<"線索化後序遍歷>";
bt3.PostOrderThread();
bt3.PostOrder(); //1 4 8 5 6 7 2 3
}