二叉樹在連接存儲表示中,空鏈的數目是大於非空鏈的數目,即在2n個空鏈中,有n+1個是空鏈,如果利用這些空鏈來指向二叉樹其他結點的指針,這結點稱爲線索,具體建立線索樹過程:
- 如果結點的左兒子ptr->left_child爲空,則在中序遍歷中,用指向在ptr之前訪問的結點的指針代替ptr->left_child,即用指向ptr的中序遍歷的前驅結點的指針代替空鏈。
- 如果結點的右兒子ptr->right_child爲空,則在中序遍歷中,用指向在ptr之後訪問的結點的指針代替ptr->right_child,即用指向ptr的中序遍歷的後驅結點的指針代替空鏈。
現在有一顆二叉樹,其結構如圖:
如果中序遍歷這顆二叉樹,訪問結點的順序是H,D,I,B,E,A,F,C,G,這裏以結點E爲例,來說明線索建立的過程吧:
由於結點E的左兒子是一個空鏈,所以,用指向在結點之前訪問的結點(即B結點)的指針代替這個空鏈,類似地,由於結點E的右兒子也爲空,所以,用在中序遍歷中指向E後面結點(即結點A)的指針代替這個空鏈。
該二叉樹轉換爲線索二叉樹後,該結構如圖:
爲了能在內存表示線索二叉樹,我們需要增加兩個附加域:left_thread和right_thread來區分指針是線索指針還是正常指針,假設ptr是任意一個結點,如果ptr->left_thread=TRUE,那麼ptr->left_child是一個線索,否則它就是指向其左兒子的正常指針,right_threadl也類似。構造線索樹還加一個額外的頭結點,其頭結點結構爲:
現在假設已經構建好了上面的中序線索二叉樹,那麼我們就可以使用該線索二叉樹實現中序遍歷,其思路:
對每一個結點ptr來說,如果ptr->right_thread=TRUE,那麼根據線索定義,結點ptr的中序後繼結點,是ptr->right_child,否則,結點ptr的中序後繼結點是從其右兒子開始,沿着左兒子鏈到達left->thread=TRUE的結點。整個算法時間 複雜度還是O(n)。
代碼實現:
typedef struct thread_tree *thread_pointer;
//線索二叉樹結構體
struct thread_tree
{
short left_thread,right_thread;
int data;
thread_pointer left_child,right_child;
};
//尋找該結點的後驅結點
thread_pointer insucc(thread_pointer tree)
{
thread_pointer temp=tree->right_child;
if(!tree->right_thread)// 若果不是線索,則沿左兒子查找
while(!temp->left_thread)
temp=temp->left_child;
return temp;
}
//中序線索樹遍歷輸出
void tinorder(thread_pointer tree)
{
for(;;)
{
thread_pointer temp=insucc(tree);
if(temp==tree) break;
printf("%d",temp->data);
}
}
向上面線索二叉樹中插入結點,假設我們只考慮插入一個新結點作爲結點parent的右兒子情況,那麼我們就有以下兩種情況要考慮:
若parent的右兒子爲空,則插入新結點後,線索二叉樹變化如下圖:
若parent的右兒子存在右子樹,則插入新結點後,線索二叉樹變化如下圖:
代碼實現:
//在線索二叉樹中,向父結點插入新的右兒子
void insert_right(thread_pointer parent,thread_pointer child)
{
thread_pointer temp;
child->right_child=parent->right_child;
child->right_thread=parent->right_thread;
child->left_child=parent;
child->left_thread=TRUE;
parent->right_child=child;
parent->right_thread=child->right_thread;
//當時第二種情況時,需查找新插入的結點的後驅結點,
//並改變其前驅結點爲該新的兒子結點
if(!child->right_thread)
{
temp=insucc(child);
temp->left_child=child;
}
}