線索二叉樹概述
二叉樹雖然是非線性結構,但二叉樹的遍歷卻為二又樹的結點集導出了一個線性序列。希望很快找到某一結點的前驅或后繼,但不希望每次都要對二叉樹遍歷一遍,這就需要把每個結點的前驅和后繼信息記錄下來。為了做到這一點,可在原來的二叉鏈表中增加一個前驅指針域(pred)和一個后繼指針域(succ),分別指向該結點在某種次序下的前驅結點和后繼結點。
以中序遍歷為例:
有許多指針是空指針又沒有利用。為了不浪費存儲空間,利用空的leftChild域存放結點的前驅結點指針,利用空的rightChild域存放結點的后繼結點指針。
為了區別線索和子女指針,在每個結點中設置兩個標志ltag和rtag。以中序線索二叉樹為例,如果ltag==0,標明leftChild域中存放的是指向左子女結點的指針,否則leftChild域中是指向該結點中序下的前驅的線索;如果rtag==0,標明rightChild域中存放的是指向右子女結點的指針,否則rightChild域中是指向該結點中序下的后繼的線索。
由於它們只需占用一個二進位,每個結點所需存儲空間節省得多。
尋找當前結點在中序下的后繼
尋找當前結點在中序序列下的前驅
線索二叉樹的結點類
1 //線索二叉樹結點類 2 template<typename T> 3 struct ThreadNode //結點類 4 { 5 int ltag, rtag; //左右子樹標志位 6 ThreadNode<T> *leftChild, *rightChild; //左孩子和右孩子 7 T data; //結點存儲的值 8 ThreadNode(const T item) :data(item), leftChild(NULL), rightChild(NULL), ltag(0), rtag(0) {} //結點類的構造函數 9 };
線索二叉樹的創建
對一個已存在的二又樹按中序遍歷進行線索化的算法中用到了一個指針pre,它在遍歷過程中總是指向遍歷指針p的中序下的前驅結點,即在中序遍歷過程中剛剛訪問過的結點。在做中序遍歷時,只要一遇到空指針域,立即填入前驅或后繼線索。
1 //使用前序遍歷創建二叉樹(未線索化) 2 void CreateTree(ThreadNode<T>* &subTree) 3 { 4 T item; 5 if (cin >> item) 6 { 7 if (item != RefValue) 8 { 9 subTree = new ThreadNode<T>(item); //構造結點 10 if (subTree == NULL) 11 { 12 cout << "空間分配錯誤!" << endl; 13 exit(1); 14 } 15 CreateTree(subTree->leftChild); //遞歸創建左子樹 16 CreateTree(subTree->rightChild); //遞歸創建右子樹 17 } 18 else 19 { 20 subTree == NULL; 21 } 22 } 23 } 24 25 //中序遍歷對二叉樹進行線索化 26 void createInThread(ThreadNode<T> *current, ThreadNode<T> * &pre) 27 { 28 if (current == NULL) 29 { 30 return; 31 } 32 createInThread(current->leftChild, pre); //遞歸左子樹的線索化 33 if (current->leftChild == NULL) //建立當前結點的前驅結點 34 { 35 current->leftChild = pre; 36 current->ltag = 1; 37 } 38 if (pre != NULL&&pre->rightChild == NULL) //建立當前結點的后繼結點 39 { 40 pre->rightChild = current; 41 pre->rtag = 1; 42 } 43 pre = current; //用前驅記住當前的結點 44 createInThread(current->rightChild, pre); //遞歸右子樹的線索化 45 } 46 47 //中序遍歷對創建好的普通二叉樹進行中序線索化 48 void CreateInThread() 49 { 50 ThreadNode<T> *pre = NULL; //第一個結點的左子樹置為NULL 51 if (root != NULL) { 52 createInThread(root, pre); 53 //處理中序遍歷的最后一個結點,最后一個結點的右子樹置為空 54 pre->rightChild = NULL; 55 pre->rtag = 1; 56 } 57 }
中序線索化二叉樹的成員函數
1 //尋找中序下第一個結點 2 ThreadNode<T> * First(ThreadNode<T> *current) 3 //返回以*current為根的中序線索二叉樹中序遍歷的第一個結點 4 { 5 ThreadNode<T> *p = current; 6 while (p->ltag == 0) 7 { 8 p = p->leftChild; //循環找到最左下角結點 9 } 10 return p; 11 } 12 13 //尋找中序下的后繼結點 14 ThreadNode<T>* Next(ThreadNode<T>* current) 15 { 16 ThreadNode<T>* p = current->rightChild; 17 if(current->rtag==0) 18 { 19 return First(p); 20 } 21 else 22 { 23 return p; 24 } 25 } 26 27 //尋找中序下最后一個結點 28 ThreadNode<T> * Last(ThreadNode<T> *current) 29 //返回以*current為根的中序線索二叉樹中序遍歷的最后一個結點 30 { 31 ThreadNode<T> *p = current; 32 while (p->rtag==0) 33 { 34 p = p->rightChild; 35 } 36 return p; 37 } 38 39 //尋找結點在中序下的前驅結點 40 ThreadNode<T>* Prior(ThreadNode<T>* current) 41 { 42 ThreadNode<T>* p = current->leftChild; 43 if (current->ltag==0) 44 { 45 return Last(p); 46 } 47 else 48 { 49 return p; 50 } 51 }
中序線索化二叉樹上執行中序遍歷的算法
先利用First()找到二又樹在中序序列下的第一個結占,抑它作為當前結點,然后利用求后繼結點的運算Next()按中序次序逐個訪問,直到二叉樹的最后一個結點。
1 //中序線索化二叉樹上執行中序遍歷的算法 2 void InOrder(ThreadNode<T>* p) 3 { 4 for (p=First(root);p!=NULL;p=Next(p)) 5 { 6 cout << p->data<<" "; 7 } 8 cout << endl; 9 }
中序線索化二叉樹上實現前序遍歷的算法
前序序列中的第一個結點即二又樹的根,因此從根結點開始前序遍歷二叉樹。若當前結點有左子女,則前序下的后繼結點即為左子女結點,否則,若當前結點有右子女,則前序后繼即為右子女結點。對於葉結點,則沿着中序后繼線索走到一個有右子女結點的結點,這個右子女結點就是當前結點的前序后繼結點。
1 void PreOrder(ThreadNode<T>* p) 2 { 3 while (p!=NULL) 4 { 5 cout << p->data<<" "; //先訪問根節點 6 if (p->ltag==0) 7 { 8 p = p->leftChild; //有左子樹,即為后繼 9 } 10 else if(p->rtag==0) //否則,有右子樹,即為后繼 11 { 12 p = p->rightChild; 13 } 14 else //無左右子樹 15 { 16 while (p!=NULL&&p->rtag==1) //檢測后繼線索 17 { 18 p = p->rightChild; //直到找到有右子樹的結點 19 } 20 if (p!=NULL) 21 { 22 p = p->rightChild; //該結點的右子樹為后繼 23 } 24 } 25 } 26 cout << endl; 27 }
中序線索化二叉樹后序遍歷的算法
首先從根結點出發,尋找在后序序列中的第一個結點。尋找的方法是從根出發,沿着左子女鏈一直找下去,找到左子女不再是左子女指針的結點,再找到該結點的右子女,在以此結點為根的子樹上再重復上述過程,直到葉結點為止。接着,從此結點開始后序遍歷中序線索二又樹。在遍歷過程中,每次都先找到當前結點的父結點,如果當前結點是父結點的右子女,或者雖然當前結點是父結點的左子女,但這個父結點沒有右子女,則后序下的后繼即為該父結點;否則,在當前結點的右子樹(如果存在)上重復執行上面的操作。這種后序遍歷過程必須搜尋父結點,並確定當前結點與其父結點的關系,即是左子女還是右子女。
1 //中序線索二叉樹的后序遍歷算法 2 void PostOrder(ThreadNode<T>* p) 3 { 4 ThreadNode<T>* t = p; 5 while (t->ltag==0||t->rtag==0) //尋找后續第一個結點 6 { 7 if(t->ltag==0) 8 { 9 t = t->leftChild; 10 } 11 else if(t->rtag==0) 12 { 13 t = t->rightChild; 14 } 15 } 16 cout << t->data<<" "; //訪問第一個結點 17 while ((p=Parent(t))!=NULL) //每次都先找到當前結點的父結點 18 { 19 //若當前結點是父節點的右子樹或者當前結點是左子樹,但是這個父節點沒有右子樹,則后續下的后繼為改父節點 20 if (p->rightChild==t||p->rtag==1) 21 { 22 t = p; 23 } 24 //否則,在當前結點的右子樹(如果存在)上重復執行上面的操作 25 else 26 { 27 t = p->rightChild; 28 while (t->ltag==0||t->rtag==0) 29 { 30 if (t->ltag==0) 31 { 32 t = t->leftChild; 33 } 34 else if (t->rtag==0) 35 { 36 t = t->rightChild; 37 } 38 } 39 } 40 cout << t->data << " "; 41 } 42 }
在中序線索二叉樹中求父節點
中序線索化二叉樹后序遍歷的算法中用到了求父節點的算法,程序中包括兩條查找父結點的路徑。第一種選擇是從當前結點走到樹上層的一個中序前驅(不一定是直接前驅),然后向右下找父結點。第二種選擇是從當前結點走到樹上層的一個中序后繼(不一定是直接后繼),然后向左下找父結點。以下通過一個具體的例子來說明為什么不可以只采用一種方法。
例如上圖尋找結點’*’的父結點的兩條路徑。一條路徑是從結點’*’沿左子女鏈走到’b',然后順中序前驅線索走到’+’,而’+”就是’*’的父結點。另一條路徑是從結點’*’沿右子女鏈走到’d',然后順中序后繼線索走到’一’,再向左子女方向走到結點’+’,找到結點’*’的父結點。對於此例,無論第一條路徑還是第二條路徑都可以找到父結點。但情況不總是這樣。例如:在找結點’+’的父結點,從’+’沿左子女鏈將走到結點’a',而’a'無中序前驅線索,因此這條路徑失敗,但通過另一條路徑找到了結點’+”的父結點’一’。說明了只采用一種方法是不行的。
程序實現是先試探第一條路徑,如果走到中序序列的第一個結點而告失敗,則改換后一條路徑尋找父結點。只有找根結點的父結點,這兩種方法才都會失敗。因為從根結點沿左子女鏈一定走到中序序列的第一個結點,沿右子女鏈一定走到中序序列的最后一個結點。然而,根結點根本就無父結點,所以這種特例在開始就排除了。
1 //在中序線索化二叉樹中求父節點 2 ThreadNode<T>* Parent(ThreadNode<T>* t) 3 { 4 ThreadNode<T>* p; 5 if(t==root) //根節點無父節點 6 { 7 return NULL; 8 } 9 for (p = t; p->ltag == 0; p = p->leftChild); //求*t為根的中序下的第一個結點p 10 //情況1 11 if (p->leftChild!=NULL) //當p左子樹指向不為空 12 { 13 //令p為p的左子樹指向的結點,判斷此結點是否並且此節點的左右子樹結點的指向都不為t,再將p為p的右孩子結點 14 for (p = p->leftChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->rightChild); 15 } 16 //情況2 17 //如果上面的循環完了,由於是p==NULL結束的循環,沒有找到與t相等的結點,就是一直找到了中序線索化的第一個結點了,這時候這種就要用到情況2的方法 18 if (p==NULL||p->leftChild==NULL) 19 { 20 //找到*t為根的中序下的最后一個結點 21 for (p = t; p->rtag == 0; p = p->rightChild); 22 //讓后讓他指向最后一個結點指向的結點,從這個結點開始,以此判斷它的左孩子孩子和右孩子是否和t相等 23 for (p = p->rightChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->leftChild); 24 } 25 return p; 26 }
完整代碼
線索二叉樹
1 //線索二叉樹 2 template<typename T> 3 struct ThreadNode //結點類 4 { 5 int ltag, rtag; //左右子樹標志位 6 ThreadNode<T> *leftChild, *rightChild; //左孩子和右孩子 7 T data; //結點存儲的值 8 ThreadNode(const T item) :data(item), leftChild(NULL), rightChild(NULL), ltag(0), rtag(0) {} //結點類的構造函數 9 }; 10 11 template<typename T> 12 class ThreadTree 13 { 14 15 public: 16 //構造函數(普通) 17 ThreadTree() :root(NULL) {} 18 19 //指定結束標志RefValue的構造函數 20 ThreadTree(T value) :RefValue(value), root(NULL) {} 21 22 //使用前序遍歷創建二叉樹(未線索化) 23 void CreateTree() { CreateTree(root); } 24 25 //中序遍歷對創建好的普通二叉樹進行中序線索化 26 void CreateInThread() 27 { 28 ThreadNode<T> *pre = NULL; //第一個結點的左子樹置為NULL 29 if (root != NULL) { 30 createInThread(root, pre); 31 //處理中序遍歷的最后一個結點,最后一個結點的右子樹置為空 32 pre->rightChild = NULL; 33 pre->rtag = 1; 34 } 35 } 36 //線索化二叉樹上執行中序遍歷的算法 37 void InOrder() { InOrder(root); } 38 //中序線索化二叉樹上實現前序遍歷的算法 39 void PreOrder() { PreOrder(root); } 40 //中序線索二叉樹的后序遍歷算法 41 void PostOrder() { PostOrder(root); } 42 private: 43 //使用前序遍歷創建二叉樹(未線索化) 44 void CreateTree(ThreadNode<T>* &subTree) 45 { 46 T item; 47 if (cin >> item) 48 { 49 if (item != RefValue) 50 { 51 subTree = new ThreadNode<T>(item); //構造結點 52 if (subTree == NULL) 53 { 54 cout << "空間分配錯誤!" << endl; 55 exit(1); 56 } 57 CreateTree(subTree->leftChild); //遞歸創建左子樹 58 CreateTree(subTree->rightChild); //遞歸創建右子樹 59 } 60 else 61 { 62 subTree == NULL; 63 } 64 } 65 } 66 //中序遍歷對二叉樹進行線索化 67 void createInThread(ThreadNode<T> *current, ThreadNode<T> * &pre) 68 { 69 if (current == NULL) 70 { 71 return; 72 } 73 createInThread(current->leftChild, pre); //遞歸左子樹的線索化 74 if (current->leftChild == NULL) //建立當前結點的前驅結點 75 { 76 current->leftChild = pre; 77 current->ltag = 1; 78 } 79 if (pre != NULL&&pre->rightChild == NULL) //建立當前結點的后繼結點 80 { 81 pre->rightChild = current; 82 pre->rtag = 1; 83 } 84 pre = current; //用前驅記住當前的結點 85 createInThread(current->rightChild, pre); //遞歸右子樹的線索化 86 } 87 88 89 //尋找中序下第一個結點 90 ThreadNode<T> * First(ThreadNode<T> *current) //返回以*current為根的中序線索二叉樹中序遍歷的第一個結點 91 { 92 ThreadNode<T> *p = current; 93 while (p->ltag == 0) 94 { 95 p = p->leftChild; //循環找到最左下角結點 96 } 97 return p; 98 } 99 100 //尋找中序下的后繼結點 101 ThreadNode<T>* Next(ThreadNode<T>* current) 102 { 103 ThreadNode<T>* p = current->rightChild; 104 if(current->rtag==0) 105 { 106 return First(p); 107 } 108 else 109 { 110 return p; 111 } 112 } 113 114 //尋找中序下最后一個結點 115 ThreadNode<T> * Last(ThreadNode<T> *current) //返回以*current為根的中序線索二叉樹中序遍歷的最后一個結點 116 { 117 ThreadNode<T> *p = current; 118 while (p->rtag==0) 119 { 120 p = p->rightChild; 121 } 122 return p; 123 } 124 //尋找結點在中序下的前驅結點 125 ThreadNode<T>* Prior(ThreadNode<T>* current) 126 { 127 ThreadNode<T>* p = current->leftChild; 128 if (current->ltag==0) 129 { 130 return Last(p); 131 } 132 else 133 { 134 return p; 135 } 136 } 137 //在中序線索化二叉樹中求父節點 138 ThreadNode<T>* Parent(ThreadNode<T>* t) 139 { 140 ThreadNode<T>* p; 141 if(t==root) //根節點無父節點 142 { 143 return NULL; 144 } 145 for (p = t; p->ltag == 0; p = p->leftChild); //求*t為根的中序下的第一個結點p 146 //情況1 147 if (p->leftChild!=NULL) //當p左子樹指向不為空 148 { 149 //令p為p的左子樹指向的結點,判斷此結點是否並且此節點的左右子樹結點的指向都不為t,再將p為p的右孩子結點 150 for (p = p->leftChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->rightChild); 151 } 152 //情況2 153 //如果上面的循環完了,由於是p==NULL結束的循環,沒有找到與t相等的結點,就是一直找到了中序線索化的第一個結點了,這時候這種就要用到情況2的方法 154 if (p==NULL||p->leftChild==NULL) 155 { 156 //找到*t為根的中序下的最后一個結點 157 for (p = t; p->rtag == 0; p = p->rightChild); 158 //讓后讓他指向最后一個結點指向的結點,從這個結點開始,以此判斷它的左孩子孩子和右孩子是否和t相等 159 for (p = p->rightChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->leftChild); 160 } 161 return p; 162 } 163 164 //中序線索化二叉樹上執行中序遍歷的算法 165 void InOrder(ThreadNode<T>* p) 166 { 167 for (p=First(root);p!=NULL;p=Next(p)) 168 { 169 cout << p->data<<" "; 170 } 171 cout << endl; 172 } 173 //中序線索化二叉樹上實現前序遍歷的算法 174 void PreOrder(ThreadNode<T>* p) 175 { 176 while (p!=NULL) 177 { 178 cout << p->data<<" "; //先訪問根節點 179 if (p->ltag==0) 180 { 181 p = p->leftChild; //有左子樹,即為后繼 182 } 183 else if(p->rtag==0) //否則,有右子樹,即為后繼 184 { 185 p = p->rightChild; 186 } 187 else //無左右子樹 188 { 189 while (p!=NULL&&p->rtag==1) //檢測后繼線索 190 { 191 p = p->rightChild; //直到找到有右子樹的結點 192 } 193 if (p!=NULL) 194 { 195 p = p->rightChild; //該結點的右子樹為后繼 196 } 197 } 198 } 199 cout << endl; 200 } 201 //中序線索二叉樹的后序遍歷算法 202 void PostOrder(ThreadNode<T>* p) 203 { 204 ThreadNode<T>* t = p; 205 while (t->ltag==0||t->rtag==0) //尋找后續第一個結點 206 { 207 if(t->ltag==0) 208 { 209 t = t->leftChild; 210 } 211 else if(t->rtag==0) 212 { 213 t = t->rightChild; 214 } 215 } 216 cout << t->data<<" "; //訪問第一個結點 217 while ((p=Parent(t))!=NULL) //每次都先找到當前結點的父結點 218 { 219 //若當前結點是父節點的右子樹或者當前結點是左子樹,但是這個父節點沒有右子樹,則后續下的后繼為改父節點 220 if (p->rightChild==t||p->rtag==1) 221 { 222 t = p; 223 } 224 //否則,在當前結點的右子樹(如果存在)上重復執行上面的操作 225 else 226 { 227 t = p->rightChild; 228 while (t->ltag==0||t->rtag==0) 229 { 230 if (t->ltag==0) 231 { 232 t = t->leftChild; 233 } 234 else if (t->rtag==0) 235 { 236 t = t->rightChild; 237 } 238 } 239 } 240 cout << t->data << " "; 241 } 242 } 243 244 private: 245 //樹的根節點 246 ThreadNode<T> *root; 247 T RefValue; 248 };
測試函數
主函數
1 int main(int argc, char* argv[]) 2 { 3 //abc##de#g##f### 4 ThreadTree<char> tree('#'); 5 tree.CreateTree(); 6 tree.CreateInThread(); 7 tree.InOrder(); 8 tree.PreOrder(); 9 tree.PostOrder(); 10 }
