PAT第九章专题复习
-
二叉树遍历与树的遍历
-
二叉树中序+层序/先序/后序建立树,输出中序/先序/后序/ 层序序列。(背模板即可)
- 层序+中序确定树模板还需要反复加强理解
-
不建树做法:当结点数比较大并且比较容易操作时
-
给出中序序列和先序序列,再给出两个点,求这两个点的最近公共祖先【PAT A 1151】
-
思路:
①不用建树,则需要中序序列的inl和inr下标以及先序序列的根节点下标preRoot。
②求最近公共祖先,函数参数还需要传入的两个数的值。
③在递归函数中,第一步是递归边界;第二步是确定中序序列根节点下标以及a和b两个数在中序序列中的下标位置。第二步自然而然想到用map来做会方便许多。
④第三步递归式。总共有5种情况,都在左子树、都在右子树、分别在左右子树、aIn等于中序根节点、bIn等于中序根节点
-
·代码实现
#include<bits/stdc++.h> using namespace std; map<int, int> pos; vector<int> pre, in; //不用建树确定,则需要中序序列以及先序的根节点位置 void lca(int inl, int inr, int preRoot, int a, int b){ //递归边界 if(inl > inr)return; //递归式 //1、计算根节点在中序序列中下标位置以及a、b节点在中序序列中下标位置 int inRoot = pos[pre[preRoot]], aIn = pos[a], bIn = pos[b]; //2、判断各种情况 //2.1 aIn和bIn都在左子树,往左子树递归 if(aIn < inRoot && bIn < inRoot)lca(inl, inRoot-1, preRoot + 1, a, b); //2.2 aIn和bIn分别在左右子树两侧,找到公共祖先 else if((aIn < inRoot && bIn > inRoot) || (aIn > inRoot && bIn < inRoot)){ printf("LCA of %d and %d is %d.\n", a, b, in[inRoot]); } //2.3 aIn和bIn都在右子树,往右子树递归 else if(aIn > inRoot && bIn > inRoot)lca(inRoot + 1, inr, preRoot + (inRoot - inl) + 1, a, b); //2.4 aIn等于inRoot else if(aIn == inRoot){ printf("%d is an ancestor of %d.\n", a, b); } //2.5 bIn等于inRoot else if(bIn == inRoot){ printf("%d is an ancestor of %d.\n", b, a); } } int main(){ int m, n, a, b; cin >> m >> n; in.resize(n + 1), pre.resize(n + 1); for(int i = 1; i <= n; i++){ cin >> in[i]; //利用map,直接将值与下标对应 pos[in[i]] = i; } for(int i = 1; i <= n; i++)cin >> pre[i]; for(int i = 0; i < m; i++){ cin >> a >> b; //判断是否出现在树中 if(pos[a] == 0 && pos[b] == 0){ printf("ERROR: %d and %d are not found.\n", a, b); } else if(pos[a] == 0 || pos[b] == 0){ printf("ERROR: %d is not found.\n", pos[a] == 0 ? a : b); }else{ lca(1, n, 1, a, b); } } return 0; }
-
-
最近公共祖先+完全二叉搜索树【PAT A 1143】
-
思路:
①和上一题相比,完全二叉搜索树的结点值本身是有序的,就没有必要和上一题一样取出结点下标来进行比较,而直接可以拿值进行比较。因为是完全二叉搜索树,可以夹出唯一一个结点值,是它在u和v的中间或者等于u和v两者中的一个。
-
代码实现
#include<bits/stdc++.h> using namespace std; map<int, int> mp; int main(){ int m, n, u, v, temp; cin >> m >> n; vector<int> pre(n); for(int i = 0; i < n; i++){ cin >> pre[i]; mp[pre[i]] = 1; } //直接进行多种情况判断 for(int i = 0; i < m; i++){ cin >> u >> v; //对pre数组遍历 for(int j = 0; j < n; j++){ temp = pre[j]; //利用循环+break模拟递归 if((temp > u && temp < v) || (temp < u && temp > v) || (temp == u) || (temp == v))break; } if(mp[u] == 0 && mp[v] == 0){ printf("ERROR: %d and %d are not found.\n", u, v); }else if(mp[u] == 0 || mp[v] == 0){ printf("ERROR: %d is not found.\n", mp[u] == false ? u : v); }else if(temp == u || temp == v){ printf("%d is an ancestor of %d.\n", temp, temp == u ? v : u); }else{ printf("LCA of %d and %d is %d.\n", u, v, temp); } } return 0; }
-
-
给出先序序列和中序序列,求后序序列第一个值。结点个数最多有50000个。【PAT A 1138】
-
思路:
①结点个数很大,不能建树做,明显会超时。
②使用不建树的思想,dfs函数中参数需要中序序列的inl和inr两个端点,以及先序序列确定的根节点下标preRoot。
③dfs函数内部,首先是递归边界;第二步是递归式,递归式参照后序遍历的模板,即先遍历左子树再遍历右子树,最后打印根节点的形式。
-
代码实现
#include<bits/stdc++.h> using namespace std; int n, flag = 0; vector<int> pre, in; unordered_map<int, int> pos;//记录中序序列中值的下标 void dfs(int inl, int inr, int preRoot){ //递归边界 if(flag || inl > inr)return; //递归式 //1、计算中序序列中根节点位置 int inRoot = pos[pre[preRoot]]; //2、输出后序序列的形式:先打遍历左子树然后再遍历右子树最后打印根节点 dfs(inl, inRoot - 1, preRoot + 1); dfs(inRoot + 1, inr, preRoot + 1 + inRoot - inl); if(!flag){ printf("%d", in[inRoot]); flag = 1; } } int main(){ cin >> n; pre.resize(n + 1), in.resize(n + 1); for(int i = 1; i <= n; i++){ cin >> pre[i]; } for(int i = 1; i <= n; i++){ cin >> in[i]; pos[in[i]] = i; } dfs(1, n, 1); return 0; }
-
-
给出先序序列和后序序列,输出中序序列;判断中序序列是否唯一即树是否唯一【PAT A 1119】
-
思路:
①dfs函数参数有先序的两个端点prel和prer,以及后序的两个端点postl和postr。
②dfs函数中,首先确定递归边界——当prel和prer相等时,可以夹出唯一的值即树的最左边结点值,压入到in容器中。 第二部分递归式,首先寻找post中下一个根节点在pre中的位置下标;如果pre部分没有左子树部分,那么就说明树不是唯一的(可以画图看),如果存在,则往左子树部分递归;第三步将根节点压入到in容器中,此处根节点为postr所在下标的数;第四步往右子树进行递归。
-
代码实现
#include<bits/stdc++.h> using namespace std; vector<int> pre, post, in; int uniq = 0; void dfs(int prel, int prer, int postl, int postr){ //递归边界 if(prel == prer){//夹出一个数,也就是树最左边的结点 in.push_back(pre[prel]); return; } //递归式 if(pre[prel] == post[postr]){ //在先序序列中寻找下一个根节点位置 int i = prel + 1; while(i <= prer && pre[i] != post[postr-1])i++; if(i - prel > 1){//说明存在左子树部分 dfs(prel + 1, i-1, postl, postl + i-prel-1-1); }else{ uniq = 1;//说明该树不唯一 } //中序根节点入in容器中 in.push_back(post[postr]); //往右子树遍历 dfs(i, prer, postl + i-prel-1, postr-1); } } int main(){ int n; cin >> n; pre.resize(n), post.resize(n); for(int i = 0; i < n; i++)cin >> pre[i]; for(int i = 0; i < n; i++)cin >> post[i]; dfs(0, n-1, 0, n-1); if(uniq)printf("No\n"); else printf("Yes\n"); for(int i = 0; i < in.size(); i++){ printf("%d", in[i]); if(i != in.size() - 1)printf(" "); else printf("\n"); } return 0; }
-
-
给出中序序列和后序序列,求层序序列【PAT A 1020】
-
思路:
①层序序列想到给树的结点从小到大进行编号;编号可以按先序遍历的形式,当遍历到根节点的时候,给树编号;编号用map来存储,那么map会自动根据第一个键值升序排序,也就是层序输出的顺序。
-
代码实现
#include<bits/stdc++.h> using namespace std; vector<int> post, in; unordered_map<int, int> pos; map<int, int> level;//map会根据第一个值升序排序 void dfs(int inl, int inr, int postRoot, int index){ //递归边界 if(inl > inr)return; //计算根节点在中序序列中下标 int inRoot = pos[post[postRoot]]; //递归式 //先序模板,将根节点与结点下标重新编号对应 level[index] = in[inRoot]; //左递归 dfs(inl, inRoot - 1, postRoot - 1 + inRoot - inr, 2 * index); //右递归 dfs(inRoot + 1, inr, postRoot - 1, 2 * index + 1); } int main(){ int n; cin >> n; post.resize(n + 1), in.resize(n + 1); for(int i = 1; i <= n; i++)cin >> post[i]; for(int i = 1; i <= n; i++){ cin >> in[i]; pos[in[i]] = i; } dfs(1, n, n, 1); auto it = level.begin(); printf("%d", it->second); it++; for(; it != level.end(); it++){ printf(" %d", it->second); } return 0; }
-
-
-
树遍历
-
求树遍历权值最大路径【PAT A 1053]
-
思路:
①本题思路不难,先建树,然后dfs遍历求权值最大的路径;接着对双重vector进行排序
②本题难点在双重vector的排序操作
bool cmp(vector<int> a, vector<int> b){ for(int i = 0; i < a.size() && i < b.size(); i++){ if(a[i] > b[i])return a > b; } if(a.size() > b.size())return a > b; } vector<vector<int> >path; vector<int> tempPath; path.push_back(tempPath); sort(path.begin(), path.end(), cmp);
-
代码实现
#include<bits/stdc++.h> using namespace std; int n, m, s; struct node{ int v, w; vector<int> child; }tree[110]; vector<vector<int> >path; vector<int> tempPath; bool cmp(vector<int> a, vector<int> b){ for(int i = 0; i < a.size() && i < b.size(); i++){ if(a[i] > b[i])return a > b; } if(a.size() > b.size())return a > b; } void dfs(int root, int sum){ //递归边界 if(tree[root].child.size() == 0){ if(sum == s){ tempPath.push_back(tree[root].w); path.push_back(tempPath); tempPath.pop_back(); } return; } //递归式 tempPath.push_back(tree[root].w); for(int i = 0; i < tree[root].child.size(); i++){ int value = tree[root].child[i]; dfs(value, sum + tree[value].w); } tempPath.pop_back(); } int main(){ int id, k, temp; cin >> n >> m >> s; for(int i = 0; i < n; i++){ cin >> tree[i].w; } for(int i = 0; i < m; i++){ cin >> id >> k; while(k--){ cin >> temp; tree[id].child.push_back(temp); } } //深度遍历 dfs(0, tree[0].w); sort(path.begin(), path.end(), cmp); for(int i = 0; i < path.size(); i++){ for(int j = 0; j < path[i].size(); j++){ printf("%d", path[i][j]); if(j != path[i].size() - 1)printf(" "); else printf("\n"); } } return 0; }
-
-
-
-
二叉搜索树(二叉查找树)
-
二叉搜索树+完全二叉树+层序遍历知识点结合【PAT A 1064】
-
思路:
①二叉搜索树的性质是中序序列是有序的。将此题给出的序列升序排序之后,就是二叉搜索树的中序序列。
②同时,该树又是完全二叉树,完全二叉树的性质是下标之间有对应的关系。于是,问题转换为中序遍历完全二叉树,为树的结点赋值。
③层序遍历序列就是完全二叉树结点下标顺序排列,也就是按结点下标升序排序输出对应的结点值。
本题很巧妙!!
-
代码实现
#include<bits/stdc++.h> using namespace std; int n, num = 0; vector<int> in; struct node{ int data; }tree[1010]; void inOrder(int index){ if(index > n)return; //左递归 inOrder(index * 2); //根节点赋值 tree[index].data = in[num++]; //右递归 inOrder(index * 2 + 1); } int main(){ cin >> n; in.resize(n); for(int i = 0; i < n; i++){ cin >> in[i]; } //升序排序 sort(in.begin(), in.end()); inOrder(1); //输出,按节点顺序输出即为层序遍历序列 for(int i = 1; i <= n; i++){ printf("%d", tree[i].data); if(i != n)printf(" "); } return 0; }
-
-
中序遍历填充二叉搜索树+层序遍历输出结果【PAT A 1099】
-
思路:
①将序列升序排序,为二叉搜索树中序序列。
②中序遍历二叉搜索树,填充值
③层序遍历输出序列
-
代码实现
#include<bits/stdc++.h> using namespace std; int n, num = 0; vector<int> in; struct node{ int v, data; int lchild, rchild; }tree[110]; void inOrder(int root){ //递归边界 if(root == -1)return; //左子树递归 inOrder(tree[root].lchild); //根节点赋值 tree[root].data = in[num++]; //右子树递归 inOrder(tree[root].rchild); } //层序遍历 void layerOrder(int root){ queue<int> q; q.push(root); int cnt = 0; while(!q.empty()){ int now = q.front(); q.pop(); printf("%d", tree[now].data); cnt++; if(cnt != n)printf(" "); if(tree[now].lchild != -1)q.push(tree[now].lchild); if(tree[now].rchild != -1)q.push(tree[now].rchild); } } int main(){ cin >> n; in.resize(n); for(int i = 0; i < n; i++){ cin >> tree[i].lchild >> tree[i].rchild; tree[i].v = i; } for(int i = 0; i < n; i++){ cin >> in[i]; } sort(in.begin(), in.end()); //中序遍历填充二叉树 inOrder(0); //层序遍历输出结果 layerOrder(0); return 0; }
-
-
红黑树+二叉搜索树+ 先序序列【PAT A 1135】
-
思路:
①红黑树不是AVL树,题目中给的是先序序列和二叉搜索树的性质来建树
②红黑树的条件:1、根结点时黑色的;2、结点不是红色就是黑色的;3、红色结点的孩子结点都是黑色的; 4、遍历每条路径黑色结点的个数都相同
③上述第3个条件,用递归判断,如果遇到红色结点的孩子不是黑色的,就返回false,最后返回左右两个结点的判断。这种dfs方法十分值得学习!
④上述第4个条件,用递归判断,利用了AVL树当中的计算更新树高 + 计算树高的方法,不过是计算黑色结点的个数 ;是updateHeight方法 + getHeight方法的变形。 -
代码实现
#include<bits/stdc++.h> using namespace std; vector<int> v; struct node{ int val; node *lchild, *rchild; }; node* build(node *root, int val){ if(root == NULL){ root = new node(); root->val = val; root->lchild = root->rchild = NULL; } //往左子树插入,不要忘记前面要等于root->lchild语句;如果没哟前面这句, //将导致这个新建的结点没有连接在二叉树上。 else if(abs(val) < abs(root->val)){ root->lchild = build(root->lchild, val); } //往右子树插入 else root->rchild = build(root->rchild, val); //建树完成返回结点 return root; } //判断红色结点的左右孩子结点是否都是黑色的 bool judge1(node *root){ if(root == NULL)return true; //左右孩子递归判断 if(root->val < 0){ if(root->lchild != NULL && root->lchild->val < 0)return false; if(root->rchild != NULL && root->rchild->val < 0)return false; } //最后返回两者的判断结果,取&&操作,只有两个都为真时才返回真 //递归函数 return judge1(root->lchild) && judge1(root->rchild); } //可以用求树高的函数模板来求解结点颜色的个数, //相当于将高度变为了黑色结点的个数 int getNum(node *root){ if(root == NULL)return 0; int l = getNum(root->lchild); int r = getNum(root->rchild); return root->val > 0 ? max(l, r) + 1 : max(l, r); } //判断任意遍历路径中的黑色结点个数是否相同 bool judge2(node *root){ if(root == NULL)return true; //计算树高——黑色结点个数,比较左右子树的树高是否相同 int l = getNum(root->lchild); int r = getNum(root->rchild); if(l != r)return false; //带返回条件的递归函数 return judge2(root->lchild) && judge2(root->rchild); } int main(){ int n, k; cin >> k; for(int i = 0; i < k; i++){ cin >> n; v.resize(n); //在读取结点的过程中,建树 node *root = NULL; for(int j = 0; j < n; j++){ cin >> v[j]; root = build(root, v[j]);//建立二叉搜索树 } //判断是否是红黑树 //不是红黑树 if(v[0] < 0 || !judge1(root) || !judge2(root)){ printf("No\n"); } else printf("Yes\n"); } return 0; }
-
-
-
并查集
-
合并有相同爱好的人,输出集合的个数以及每个集合有多少人【PAT A 1107】
-
思路:
①既然要求的是人数,那么就将爱好作为合并条件,合并人。可以开一个数组hobby,爱好编号作为数组下标,人编号作为数组值。然后每读取一个爱好,判断该爱好是否其他人也有,没有就将该爱好的值记为当前人的编号。之后合并当前人的编号与hobby数组值。 这里会出现两种情况,一种是与自身合并,也就是i = hobby[temp];另一种是与有相同爱好的其他人合并。
②计算每个集合中人的个数通用模板
//计算每个集合人数 for(int i = 1; i <= n; i++){ isRoot[findFather(i)]++; }
③计算总共有多少集合通用模板:
int ans = 0; for(int i = 1; i <= n; i++){ if(isRoot[i] != 0)ans++; }
-
代码实现:
#include<bits/stdc++.h> using namespace std; int hobby[1010] = {0}, father[1010], isRoot[1010] = {0}; int findFather(int x){ if(x == father[x])return x; else{ int F = findFather(father[x]); father[x] = F; return F; } } void Union(int a, int b){ int fa = findFather(a); int fb = findFather(b); if(fa != fb){ father[fa] = fb; } } bool cmp(int a, int b){ return a > b; } int main(){ int n, k, temp; cin >> n; //初始化father数组 for(int i = 1; i <= n; i++)father[i] = i; for(int i = 1; i <= n; i++){ scanf("%d:", &k); while(k--){ cin >> temp; if(hobby[temp]==0) hobby[temp] = i; //将爱好与人编号对应起来,点睛之笔 Union(i, hobby[temp]);//将当前人与有相同爱好的人合并到一个集合中 } } //计算每个集合人数 for(int i = 1; i <= n; i++){ isRoot[findFather(i)]++; } //计算有多少个集合 int ans = 0; for(int i = 1; i <= n; i++){ if(isRoot[i] != 0)ans++; } sort(isRoot+1, isRoot + n + 1, cmp);//降序排序 //输出结果 printf("%d\n", ans); for(int i = 1; i <= ans; i++){ printf("%d", isRoot[i]); if(i != ans)printf(" "); } return 0; }
-
-
-
堆(基础操作还是不熟悉,再多重复几遍)
-
建堆,堆排序;插入排序【PAT A 1098】
-
思路:
①本题难点在于自己对堆的基础操作不熟悉。首先是建堆的概念,建堆完成不是初始序列的形式,而是初始序列经过向下调整之后完成的大顶堆。
②堆排序,通常是指已经建立完成大顶堆。然后需要将该堆变成升序序列,注意,升序序列不等于小顶堆。
-
代码实现
#include<bits/stdc++.h> using namespace std; int n, ori[110], tempori[110], ans[110]; bool isSame(int a[], int b[]){ for(int i = 1; i <= n; i++){ if(a[i] != b[i])return false; } return true; } bool insertSort(){ int flag = 0; for(int i = 2; i <= n; i++){ if(i != 2 && isSame(tempori, ans)){ flag = 1; } //插入排序 sort(tempori+1, tempori + i + 1); if(flag){ sort(ans+1, ans + i + 1); return true; } } return false; } //建立的是堆顶为最大值的堆 void downAdjust(int low, int high){ int i = low, j = i * 2; while(j <= high){ //寻找最大孩子结点 if(j+1 <= high && ori[j+1] > ori[j]){ j = j + 1; } //向下调整 if(ori[j] > ori[i]){ swap(ori[j], ori[i]); i = j; j = i * 2; }else{ break; } } } void create(){ for(int i = n / 2; i >= 1; i--){ downAdjust(i, n); } } void heapSort(){ //建堆 create(); for(int i = n; i > 1; i--){ swap(ori[i], ori[1]); downAdjust(1, i-1); if(isSame(ori, ans)){ swap(ori[i-1], ori[1]); downAdjust(1, i-2); return; } } } int main(){ cin >> n; for(int i = 1; i <= n; i++){ cin >> ori[i]; tempori[i] = ori[i]; } for(int i = 1; i <= n; i++){ cin >> ans[i]; } //判断是否是插入排序 if(insertSort()){ printf("Insertion Sort\n"); for(int i = 1; i <= n; i++){ printf("%d", ans[i]); if(i != n)printf(" "); } } else{ printf("Heap Sort\n"); //堆排序 heapSort(); for(int i = 1; i <= n; i++){ printf("%d", ori[i]); if(i != n)printf(" "); } } return 0; }
-
-
判断是否是Min堆或者Max堆
-
模板代码如下:
int isMin = 1, isMax = 1, a[maxn]; for(int i = 2; i <= n; i++){ if(a[i/2] > a[i])isMin = 0; if(a[i/2] < a[i])isMax = 0; } if(isMin == 1)printf("Min Heap\n"); else if(isMax == 1)printf("Max Heap\n"); else printf("Not Heap\n");
-
-