线索二叉树的详细实现(C++)
线索二叉树概述
二叉树虽然是非线性结构,但二叉树的遍历却为二又树的结点集导出了一个线性序列。希望很快找到某一结点的前驱或后继,但不希望每次都要对二叉树遍历一遍,这就需要把每个结点的前驱和后继信息记录下来。为了做到这一点,可在原来的二叉链表中增加一个前驱指针域(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 }