数据结构——树和二叉树(递归函数的详细总结)
因为做树这一章的题的时候网上题解太多,然后写的思路方法都不一样,然后就自己想了一种不管是遍历还是建树都能一劳永逸的一种统一格式,这样记忆理解起来也相对容易了很多。因为在递归的时候如果没有一种更方便自己理解记忆的办法是很容易出错的
然后为了统一规范,我们把起点和终点统一为**L和**R,如中序序列就是inL和inR,然后再把递归函数的形式统一成 f(int inL, int inR, int postL/preL, int postR/preR),即当前的函数的中序的起点和终点和后序或先序的起点和终点。规范统一完了之后我们就说说统一的思路:
首先思路就是现在已知中序序列和后序或先序序列,然后思路就是先定义一下当前中序序列的根in_root,然后遍历确定这个根的位置,例如如果已知后序就是in[in_root] == post[postR]。同样,如果已知先序序列就是in[in_root] == pre[preL]。
然后为了方便计算我们把当前根的左子树的个数记为lcnt = in_root - inL
然后就是递归调用了,不管是怎样我们肯定是先递归左子树然后再右子树的。
最关键的点就是确定左右子树的起点和终点,不管是遍历还是建树都可以按照以下的起点终点来,所以直接背过也是可以的
左子树当前的中序起点肯定还是inL这是毋庸置疑的,然后终点由于在中序根的左边,所以中序终点就是in_root - 1。然后如果是后序的话,起点肯定也还是postL,终点就是起点加上左子树的个数然后减一,如果是前序的话,起点就是preL +1,因为preL是根结点,所以起点要+1,终点就是preL + lcnt。
右子树当前的中序起点就是in_root + 1,终点就是inR。然后如果是后序的话,起点就是postL+lcnt,终点就是postR - 1,因为postR是根结点,所以要-1。如果是前序的话,就是preL + lcnt + 1。终点就是preR
所以只要记住左子树,右子树的起点就是左子树的终点加一,然后右子树的终点分前序和后序记
总结一下就是两种形式:
//中序遍历和后序遍历 (inL, in_root - 1, postL, postL + lcnt - 1); (in_root + 1, inR, postL + lcnt, postR - 1);
//中序遍历和前序遍历 (inL, in_root - 1, preL + 1, preL + lcnt); (in_root + 1, inR, preL + lcnt + 1, preR);
如果还没理解为什么这么写我们举个例子:
后序:2 3 1 5 7 6 4 中序:1 2 3 4 5 6 7
在后序遍历的最后一个数字4是根结点,在中序遍历中找到根结点4,4左边的序列1 2 3共3个结点是左子树的中序遍历,4右边的序列5 6 7共3个结点是右子树的中序遍历。在后序遍历中前3个结点2 3 1是左子树的后序遍历,紧接着的3个结点5 7 6是右子树的后序遍历。即变成了两个子树的后序遍历和中序遍历序列以及根结点。
然后我们带入式子理解一下,1 2 3三个结点是lcnt,所以左子树终点就是postL + lcnt - 1,前序也是一样的,终点本来应该是preL + 1 + lcnt - 1,最后化简即为preL + lcnt
然后理解之后做两个题顺一下
遍历:遍历的时候我们就按照遍历的定义来就好了,由于是先序遍历,所以先输出然后再遍历左右子树
#include <bits/stdc++.h> using namespace std; const int N = 35; int n; vector<int>post, in; void pre(int inL, int inR, int postL, int postR) { if(inL > inR) return ; int in_root = 0; for(in_root = inL; in_root <= inR; in_root ++ ) if(in[in_root] == post[postR]) break; int lcnt = in_root - inL; //先序遍历 cout << ' ' << post[postR]; pre(inL, in_root - 1, postL, postL + lcnt - 1); pre(in_root + 1, inR, postL + lcnt, postR - 1); } int main() { cin >> n; for(int i = 0; i < n; i ++ ) { int x; cin >> x; post.push_back(x); } for(int i = 0; i < n; i ++ ) { int x; cin >> x; in.push_back(x); } cout << "Preorder:"; pre(0, n - 1, 0, n - 1); return 0; }
建树:建树我们就开一个结构体来定义树,然后递归函数返回值是结构体指针类型表示一棵树
这题顺便说下思路吧,在我们建完树之后,就可以找公共祖先了,我们可以写一个函数LCA,来找两个值的公共祖先,具体方法就是按照二叉排序树的性质,小于就在左子树找,大于就在右子树找
#include <bits/stdc++.h> using namespace std; const int N = 10010; int n, m; vector<int>pre, in; struct Tnode{ int data; Tnode *lchild, *rchild; }; Tnode *build(int inL, int inR, int preL, int preR) { if(inL > inR) return NULL; Tnode *root = new Tnode(); root->data = pre[preL]; int in_root = 0; for(in_root = inL; in_root <= inR; in_root ++ ) if(in[in_root] == pre[preL]) break; int lcnt = in_root - inL;//左子树结点个数 root->lchild = build(inL, in_root - 1, preL + 1, preL + lcnt); root->rchild = build(in_root + 1, inR, preL + lcnt + 1, preR); return root; }int LCA(Tnode *root, int u, int v) { if(root->data <= u && root->data >= v || root->data >= u && root->data <= v) return root->data; if(u > root->data && v > root->data) return LCA(root->rchild, u, v); if(u < root->data && v < root->data) return LCA(root->lchild, u, v); } int main() { cin >> m >> n; set<int>s; for(int i = 0; i < n; i ++ ) { int x; cin >> x; pre.push_back(x); in.push_back(x); s.insert(x); } sort(in.begin(), in.end());//二叉排序树的中序遍历是递增的 Tnode *root = build(0, n - 1, 0, n - 1); while(m -- ) { int u, v; cin >> u >> v; if(!s.count(u) && !s.count(v)) printf("ERROR: %d and %d are not found.\n", u, v); else if(!s.count(u)) printf("ERROR: %d is not found.\n", u); else if(!s.count(v)) printf("ERROR: %d is not found.\n", v); else { int res = LCA(root, u, v); if(res == u) printf("%d is an ancestor of %d.\n", u, v); else if(res == v) printf("%d is an ancestor of %d.\n", v, u); else printf("LCA of %d and %d is %d.\n", u, v, res); } } return 0; }

浙公网安备 33010602011771号