这一周呢,自己主要学习的是并查集和拓扑排序的资料,也稍微看了一部分相关的题型。除此之外,这一周也在继续看老师发的95道左右的搜索题目,并有了自己的一些心得。下面是这一周的具体总结。
目录
对并查集、拓扑排序的理解
一、并查集
就是一种维护集合的数据结构,一个是查找,一个是合并。基本的定义就是用数组实现,比如father[1]=2,代表元素1的父亲节点是元素2。
1、查找
基本原理,就是一个集合只存在一个根节点,寻找根节点的标志就是father[i]==i,满足这个条件就是根节点了。基本思路,可以用递推或者是递归来实现,用一个while循环,反复寻找根节点。
2、合并
基本原理,就是把其中一个集合的根节点的父亲指向另一个集合的根节点。基本思路,就是先判断是否属于同一集合,然后再开始进行合并,把其中一个集合的父亲结点指向另一个结点就能实现了。并查集产生的每一个集合都是一棵树,不会有环出现。注意事项,一定要先寻找根节点,不能直接将其中一个元素的父亲设为另一个元素,否则达不到合并效果。
3、路径压缩
1、路径压缩主要是针对优化查找操作的,因为如果元素数量多,而且在最后边,效率很低。基本原理,就是把当前查询结点的路径上的所有结点的父亲都指向根节点。基本思路,先按原来写法获得一个元素的根节点,重新从这个元素走一遍寻找根节点的过程,把经过的所有结点的父亲全部改成根节点,也可以用递归来实现。
2、优化的方法还有一个是按秩合并,合并的时候将元素少的集合合并到元素多的集合中,合并之后树的高度会相对较小。
4、并查集应用
应用,求无向图的连通分量个数,最近公共祖先,求最小生成树等等。
5、并查集例题
例题:畅通工程
题意呢,就是问还需要修建多少条路,使得各个城镇之间实现联通。
基本思路,就是先将所有的元素初始化,然后再将各个元素根据题目要求建立集合关系,最后查询有几个根节点,就说明有几个独立的集合,然后把他们的根节点连起来就可以实现联通,连接了多少次就需要修建多少条道路。
下面给出大佬代码,很好理解。
#include<stdio.h>
int father[1005];
int Find(int x)
{
while(x!=father[x])
x=father[x];
return x;
}
void Combine(int a,int b)
{
int fa=Find(a);
int fb=Find(b);
if(fa!=fb)
{
father[fa]=fb;
}
}
int main()
{
int n,m;
int i;
int a,b;
while(~scanf("%d",&n))
{
if(n==0)
break;
scanf("%d",&m);
int sum=0;
for(i=1;i<=n;i++)
father[i]=i;
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
Combine(a,b);
}
for(i=1;i<=n;i++)
{
if(father[i]==i)
sum++;
}
printf("%d\n",sum-1);
}
return 0;
}
例题:感染病毒
这个比上边那个稍微复杂了些,但是基本思路还是一致的。题意呢,就是说一个集合中的某个元素可能感染病毒,同集合中也可能被感染,给出分组,问感染人数。
基本思路,查询祖先结点,祖先节点不一致,让二者中某一个认另一个为祖先,这样就在同一个集合中了,最后查询人数即可,也可以用状态压缩,让他们都指向同一个祖先结点,可以加快速度。如下代码所示。
int find(int x)
{
if(x!=pre[x])
//找到其祖先节点,并将其父节点变成找到的祖先节点
pre[x] = find(pre[x]);
//由父节点继续向上递归查询
return pre[x];
}
二、拓扑排序
1、基本思路和应用
拓扑排序感觉稍微复杂了些,有一部分些内容还没有看懂。先说拓扑序列吧,就是将有向无环图的所有顶点排成线性序列,在这个图中的任意两个顶点,存在一定的指向方向关系,例如a->b,a一定在b的前面,这个序列成为拓扑序列,通俗的理解就是类似于学习某一门课,学完特定的课程之后才能学习下一门,顺序唯一,而有些科目没有一个先后的学习顺序,顺序不唯一。拓扑排序呢,意思是说,将有向图中的顶点以线性方式进行排序,就是拓扑排序。
基本思路,可以用邻接表实现拓扑排序,也要定义数组记录结点的入度。具体操作就是定义一个队列,把所有入度为零的结点接入队列,取出队首结点并输出,删去从它出发的所有边,并且让这些边到达顶点的入度减一,某个顶点入度为零时,加入队列。重复以上操作即可,直到队列为空,如果入过队的结点数目恰好为n,拓扑排序就完成了。
应用,很重要的一个应用就是判断一个给定的图是否是有向无环图。如果拓扑排序成功,说明给定的图是有向无环图,否则,拓扑排序失败,不是有向无环图。
部分实现代码如下:
vector<int>G[MAXV];
int n, m, inDegree[MAXN];
bool topologicalSort() {
int num = 0;
queue<int>q;
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
inDegree[v]--;
if (inDegree[v] == 0) {
q.push(v);
}
}
G[u].clear();
num++;
}
if (num == n) {
return true;
}
else {
return false;
}
2、拓扑排序例题
例题:确定比赛名次
题意呢,就是说给定几支队伍,和几只队伍的输赢关系,求最终的排名顺序(按照编号顺序)。
一开始,自己对前面拓扑排序的实现思路还不大理解,但是看了这个题(有图形的描述),自己基本就明白了。
基本思路,就是要先根据题目描述的各个队伍之间的输赢关系,建立下面这样一张有向无环图,对顶点进行拓扑排序。先找到入度为零的点1和4,删去点1和与1相关的边,接着寻找入度为0的点,继续输出就可以了。
题型思路技巧概括
1、并查集题型,合纵连横,并查集和最小生成树结合,并查集和离散化结合,并查集路径压缩和按秩合并,并查集求连通块个数,并查集和欧拉路、Trie树结合等等。
2、拓扑排序题型,确定比赛名次,产生冠军,拓扑和并查集结合,拓扑排序和队列等等。
3、并查集的本质是维护传递性,也可以用来判环路,比DFS和BFS高效一些,可以找到所有环路。
4、拓扑排序的三种方法,无前趋的的顶点优先拓扑排序,无后继的的顶点优先拓扑排序,基于DFS递归的拓扑排序(逆序)。
5、一般无论是并查集还是拓扑排序,最好都要建立一个图,因为有些文字描述很抽象,这个时候如果能建立一张图,就很好理解了。
6、拓扑排序要保证没有环出现,而且拓扑排序的结果不一定唯一。
DFS和BFS题目AC心得
这周已经是第九周了,搜索专题也算是结束了,已经准备开始下一个专题了。90多道题目,基本看了一遍了,AC的题目数量不是很多,AC一道题基本就已经过去好长时间了。但是也是最终完成了看这90多道题的任务。
遇到的困难
1、首先一点吧,有的题目描述的很抽象,特别是中间靠后的一部分题目,有的还是英文,就有的时候很难读懂。
2、其次就是把思路转化成代码,自己AC的题目,基本都是思路比较清晰的,像是迷宫的,连通块的这一类,自己相对熟悉一些。其余的题目,可能有了思路,但是很难写出来,一般看了博客自己才能写出来。
3、还有就是看一道题目,有的比较长,自己就有的时候沉不下心来了,以后要改正。
一些小小收获
1、虽然遇到了不少困难,但是也有很多收获,即使没有做对题目,但是也有了对这些题目的认识,有自己的思考在里边,无论如何,都是有很大帮助的。同时也见识了更多的题目类型,搜索和其它知识结合的内容。
2、这些题目当中,遇见的比较多的是完全用DFS和BFS来做的题目,其次呢,是搜索和染色,搜索和联通块,搜索和图结合的题目,也有少数题目,用到了并查集,二分,环套树的一些知识。
3、自己做出来的题目,也并不是一帆风顺,得修改好几次,可能哪个边界没考虑,输出格式又出现了问题,还有各种的一些细节的地方自己没有考虑到,一般就是框架有了,但是让内容完善起来还有点难度。但是,反复调试并且AC的那一刻也是很让人高兴的。
4、总之吧,只要沉下心来,积极的去思考,总会有收获的,一些题型也是通过这样的反复练习,变得更加熟悉了,同时也见识了很多新颖的题目,像是我们的东方梦,机器人搬重物等题目。
反思规划
虽然搜索专题结束了,但是并查集和拓扑排序等知识也会不断到来。不能仅仅学完一个专题就想着放松了。还是自己之前下定的决心,既然做出了选择就一定会坚持下去,不会回头。以后会继续按照老师的思路,在学习的道路上继续坚持下去!