线索二叉树的建立与遍历
线索二叉树利用二叉树空余的指针域,来实现二叉树的链式化。然后,就可以通过前驱,后继像双向链表一样根据某种遍历次序对树的结点进行访问。
数据结构:
1 struct node{ 2 int data; 3 struct node* left,*right; 4 int ltag,rtag; //=0时,表明指向子结点;=1时,表示指向前驱/后继 5 }
线索二叉树的操作:
建立线索二叉树:
- 不同的遍历顺序,会得到不同的线索二叉树。
- 一般使第线索链表的头和尾指向NULL(也可以加入一个头指针)
不同的遍历顺序形成的二叉树还是具有不同的特点,因此,对每一个建立完整的建树和操作过程来查看性质。
中序遍历建立二叉树:
- 在最初中序遍历的基础上(左子树,根结点,右子树),从处理一个结点变为处理一对结点。
- 建树的时候根据left/right==nullptr; 当建立完成后根据ltag/lright进行判断;
1 #include<bits\stdc++.h> 2 using namespace std; 3 4 struct node { 5 int data; 6 int ltag=0, rtag=0; //=0,表示指向子树. 7 node* left=nullptr, * right=nullptr; 8 }; 9 10 typedef struct node Node; 11 12 Node* CreateTree() { 13 Node* root=new Node; 14 root->data = 1; 15 root->left = new Node; 16 root->left->data = 2; 17 root->right = new Node; 18 root->right->data = 3; 19 root->left->left = new Node; 20 root->left->left->data = 4; 21 root->left->right = new Node; 22 root->left->right->data = 5; 23 root->right->left = new Node; 24 root->right->left->data = 6; 25 root->right->right = new Node; 26 root->right->right->data = 7; 27 return root; 28 } 29 30 // 建立cur和pre两个结点的线索关系,注意:pre的类型是Node* &(如果是Node*是不能正确形成right线索的) 31 void CreateClueInOrder(Node* cur, Node* &pre) { 32 if (cur != nullptr) { 33 // 递归思想:左子树,根结点,右子树 34 // 左子树 35 CreateClueInOrder(cur->left, pre); 36 // 根结点,在建立线索的时候根据left/right==nullptr进行判断; 37 if (cur->left == nullptr) { 38 cur->left = pre; 39 cur->ltag = 1; 40 } 41 if (pre != nullptr && pre->right == nullptr) { 42 pre->right = cur; 43 pre->rtag = 1; 44 } 45 pre = cur; 46 // 右子树 47 CreateClueInOrder(cur->right, pre); 48 } 49 } 50 51 // 以中序遍历方式建立线索二叉树 52 void CreateClueTreeInOrder(Node* root) { 53 if (root != nullptr) { 54 Node* pre = nullptr; 55 CreateClueInOrder(root, pre); 56 pre->ltag = 1; // pre此时为中序遍历最后一个结点,设置ltag 57 } 58 } 59 60 int main() { 61 Node* root = CreateTree(); 62 CreateClueTreeInOrder(root); 63 }
中序线索二叉树的线索特性:
- 如果一个结点有左子树,那么其前驱就是左子树的最右结点。 如果没有左子树,通过线索指明前驱。
- 如果一个结点有右子树,那么其后继就是右子树的最左结点。 如果没有右子树,通过线索指明后继。
- 因此对于二叉树的任意一个顶点都无需通过栈来实现寻找前驱和后继。 (可以直接寻找到,只有中序才有该种性质,先序和后序是没有的,可以往下观看)
1 #include<bits\stdc++.h> 2 using namespace std; 3 4 struct node { 5 int data; 6 int ltag=0, rtag=0; //=0,表示指向子树. 7 node* left=nullptr, * right=nullptr; 8 }; 9 10 typedef struct node Node; 11 12 Node* CreateTree() { 13 Node* root=new Node; 14 root->data = 1; 15 root->left = new Node; 16 root->left->data = 2; 17 root->right = new Node; 18 root->right->data = 3; 19 root->left->left = new Node; 20 root->left->left->data = 4; 21 root->left->right = new Node; 22 root->left->right->data = 5; 23 root->right->left = new Node; 24 root->right->left->data = 6; 25 root->right->right = new Node; 26 root->right->right->data = 7; 27 return root; 28 } 29 30 // 建立cur和pre两个结点的线索关系,注意:pre的类型是Node* &(如果是Node*是不能正确形成right线索的) 31 void CreateClueInOrder(Node* cur, Node* &pre) { 32 if (cur != nullptr) { 33 // 递归思想:左子树,根结点,右子树 34 // 左子树 35 CreateClueInOrder(cur->left, pre); 36 // 根结点,在建立线索的时候根据left/right==nullptr进行判断; 37 if (cur->left == nullptr) { 38 cur->left = pre; 39 cur->ltag = 1; 40 } 41 if (pre != nullptr && pre->right == nullptr) { 42 pre->right = cur; 43 pre->rtag = 1; 44 } 45 pre = cur; 46 // 右子树 47 CreateClueInOrder(cur->right, pre); 48 } 49 } 50 51 // 以中序遍历方式建立线索二叉树 52 void CreateClueTreeInOrder(Node* root) { 53 if (root != nullptr) { 54 Node* pre = nullptr; 55 CreateClueInOrder(root, pre); 56 pre->ltag = 1; // pre此时为中序遍历最后一个结点,设置ltag 57 } 58 } 59 60 // 获得线索二叉树的第一个结点 61 Node* GetFirstNodeInOrder(Node* root) { 62 // 注意:这里需要使用root->ltag==0而不能使用root->left!=nullptr 63 // 因为除了首结点,原来的叶子节点的前驱变为了线索;而且这个函数是复用在查找中途; 64 while (root!=nullptr && root->ltag==0) { 65 root = root->left; 66 } 67 return root; 68 } 69 70 // 根据当前结点获得下一个结点 71 Node* GetNextNodeInOrder(Node* p) { 72 // 有右子树,获得右子树的最左结点; 73 if (p->rtag == 0) { 74 p=GetFirstNodeInOrder(p->right); 75 } 76 else { 77 p = p->right; 78 } 79 return p; 80 } 81 82 void TraverseInOrder(Node* root) { 83 root = GetFirstNodeInOrder(root); // 获得起始结点 84 while (root != nullptr) { 85 cout << root->data << " "; 86 root = GetNextNodeInOrder(root); 87 } 88 } 89 90 int main() { 91 Node* root = CreateTree(); 92 CreateClueTreeInOrder(root); 93 TraverseInOrder(root); 94 }
先序遍历建立二叉树:
- 在建立的时候,可能会遇到之前建立的线索,因此需要进行判断,防止进入死循环。
1 // 先序遍历建立线索二叉树 2 void CreateCluePreOrder(Node* cur, Node* &pre) { 3 if (cur != nullptr) { 4 // 根结点,在建立线索的时候根据left/right==nullptr进行判断; 5 if (cur->left == nullptr) { 6 cur->left = pre; 7 cur->ltag = 1; 8 } 9 if (pre != nullptr && pre->right == nullptr) { 10 pre->right = cur; 11 pre->rtag = 1; 12 } 13 pre = cur; 14 // 这里和中序有点区别! 15 if (pre->ltag == 0) { 16 CreateCluePreOrder(cur->left, pre); 17 } 18 if (pre->rtag == 0) { 19 CreateCluePreOrder(cur->right, pre); 20 } 21 } 22 } 23 24 // 建立先序线索二叉树 25 void CreateClueTreePreOrder(Node* root) { 26 Node* pre = nullptr; 27 if (root != nullptr) { 28 CreateCluePreOrder(root, pre); 29 pre->rtag = 1; 30 } 31 }
先序线索二叉树的特性:
- 对于前驱而言:如果左子树为空,那么可以通过线索指出; 如果左子树不为空,那么必须通过先序遍历的递归序列指出。(必须依靠栈/递归来实现前驱的寻找)
- 对于后序而言:如果左子树为空,那么通过线索指出; 如果不为空,就是左子树的根结点。
- 因为对于先序遍历的左孩子和右孩子指向的都是后继信息。因此就无法寻找前驱信息了。 (先序遍历线索二叉树无法直接寻找某些结点的前驱,但还是能够直接实现遍历)
- 同样,对于后序遍历,左孩子和右孩子指向的都是前驱信息,因此无法寻找后继信息。 (后序遍历线索二叉树无法直接寻找某些结点的后继,无法直接实现遍历)