1.学习总结(2分)
1.1图的思维导图
1.2 图结构学习体会
谈谈你对图结构中的几个经典算法学习体会
- 深度遍历算法:其过程类似于前序遍历,从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点处出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问。
- 广度遍历算法:其过程类似于层次遍历,借助队列来实现。若把遍历的过程比喻成找东西,深度优先遍历意味着要彻底找完一个房间才查找下一个房间,而广度优先遍历是把所有房间显眼的地方找一遍,然后一步步扩大查找的范围。
- Prim算法:像是走一步看一步的思维方式,逐步生成最小生成树。
- Kruscal算法:更有全局意识,直接从图中的最短权值入手,找寻最后的答案。
- Dijkstra算法:更强调单源顶点查找路径的方式,比较符合我们正常的思维,容易理解原理,但是算法代码相对复杂。
- Floyd算法:完全抛开了单点的局限思维方式,巧妙利用矩阵的变换,用最清爽的代码实现了多顶点间最短路径求解的方案,原理理解有难度,但算法编写很简洁。
- 拓扑排序算法:其思想是不断删除入度为0的顶点并且删除以该节点为尾的弧并输出删除的顶点,巧秒地借助栈来存储处理过程中入度为0的顶点而避免了每次查找时都要去遍历顶点表找有没有入度为0的顶点,且巧妙利用减小删除顶点邻接点的入度in来间接实现了删除弧。
2.PTA实验作业
2.1 题目1:7-3 六度空间
2.2 设计思路
typedef struct{
int num; //编号
int deep; //深度
}Person;
void BFS(int n)
{
定义Person类型的队列q和变量host;
给host赋初值;
flag[n]=1;
把host放到q队列里;
while(队列不为空)
{
从队列中出一个元素并赋给temp;
sum++;
if(temp与host距离为6) continue;
for i=1 to i=N
if(顶点i已访问或temp与i之间没有关系) continue;
else{
定义Person类型变量node;
node的编号为i,深度为temp深度+1;
设置顶点i已访问并将node放入q队列中
}
}
}
2.3 代码截图
2.4 PTA提交列表说明。
-
Q1:答案错误
-
A1:一开始忘记了这是个无向图,在建邻接矩阵的时候应该要relation[p1][p2] = 1;relation[p2][p1] = 1;
-
Q2:修改以后答案依然错误
-
A2:因为朋友的朋友的朋友很可能其实是你的朋友而且已经访问和计数过了,所以要引入变量flag数组来标记已经访问过的结点避免重复计数导致错误
-
Q3:是的,答案终于不一样了但是依然错误!
-
A3:这个错误找了很久才发现,一直都有很小心sum的值要每次都初始化,但是发现忘记初始化我们的flag数组了...而且要注意,int flag={0}只能在刚刚定义的时候这样子初始化,如果是其他时候的初始化,除了很麻烦的循环以外就要用C++中的memset函数,附上memset函数的用法
还有C/C++数组初始化的一些误区 - CSDN博客 https://blog.csdn.net/u014417133/article/details/77185009
2.1 题目2:7-4 公路村村通
2.2 设计思路
int Prim() //最小生成树
{
初始化lowcost,adjvex数组
for i=1 to n
遍历lowcost数组
若lowcost[i]!=0, 找最小边min和找其最小边邻接点k
sum = sum+min
lowcost[k]=0;
遍历lowcost数组
若lowcost[i]!=0 && G[k][i]<lowcost[i]
修正adjvex[i]=k lowcost[i]=G[k][i]
end
return sum
}
bool empty_bfs() //判断图是否连通
{
定义整数型队列q和变量count=1,定义visited数组并初始化为0
把顶点1加入队列中并标记顶点1已访问
while(队列q不为空)
{
从队列中出一个元素并赋给temp
count++;
for i=1 to N
{
if(顶点i还未访问过且i和temp有边) 标记i已访问且把i放入队列q中
}
}
if(count等于总顶点数N) return true
else return false
}
2.3 代码截图
2.4 PTA提交列表说明。
-
Q1:图不连通时答案错误
-
A1:
通过输出count发现count的值不对,检查发现一开始的初始化应该要把count的值置为1而不是0,因为最后一次出队列后队列就为空了就跳出循环不会再执行count++的语句了 -
Q2:修改后count的值对了但是答案还是错误
-
A2:被自己蠢哭系列之又瞎到没看清题目说不畅通输出是-1而不是1阿喂!
-
Q3:最大N M不连通错误
-
A3:好像是改了数组的大小?当时改得挺晚的然后在睡着的边缘试探后来全部正确就溜去睡了就就不是很清楚了哭唧唧...
2.1 题目3:7-8 城市间紧急救援
2.2 设计思路
void dijkstra(int s)
{
初始化dist数组,path数组,vis数组,aid数组,num数组 //aid代表急救队数量,num代表最短路径数
遍历图中所有节点
{
for(i=0;i<g.n;i++) //找最短dist
{
若s[i]!=0,则dist数组找最短路径,顶点为u
}
顶点u加入集合vis,vis[u]=1
for(i=0;i<g.n;i++) //修正dist
{
若s[i]!=0 && dist[i]>dist[u]+mat[u][i] //寻找最短路径
则修正
dist[i] = dist[u]+mat[u][i]
aid[i] = aid[u]+medicine[j];
path[i] = u;
num[i] = num[k]
若s[i]!=0 && dist[i]=dist[u]+mat[u][i] //最短路径dist相同
则修正
num[i] = num[i]+num[u];
if(路过u到j路径上的急救队多余直接到j路径上的急救队)
{
aid[i] = aid[u] + medicine[i];
path[i] = u;
}
}
}
}
void print(int s,int t) //最短路径输出
{
定义int类型的栈q;
while(t不等于s)
{
把t放入栈中;
t = 最短路径上t的前一个结点
}
把s放入栈q中;
while(q不为空)
{
输出栈顶元素并出栈
}
}
2.3 代码截图
2.4 PTA提交列表说明。
-
Q1:一开始并不会求最短路径数,没啥思路的那种
-
A1:来自班里某大佬的思路:用cnt[]统计到每个点的最短路线与几条,遇到某一点时,如果当前路线是最短的, 该点的cnt等于与它连接的点的cnt,如果与该点的各其他最短路线等长, 该点的cnt等于与它连接的点的cnt加上该点原有的cnt
-
Q2:按大佬的思路修改了自己代码后输出错误,而且还是输出很诡异的03的喂!
-
A2:其实吧是我对题目的理解错了,只要输出个数就好了我还输出最短路径的长度干啥呀,然后0和3是分别两个数,只不过这样子的话就一块输出了
-
Q3:注意要初始化最短路径数num数组,具体做法是若某点与源点有直接的路径就置为1,没有就为0
-
A3:输出正确以后还要稍微注意一下格式呗~
3.截图本周题目集的PTA最后排名
3.1 PTA排名
3.2 我的总分:280
4. 阅读代码
/* 题目:一个公司总共n个人,m个关系,u v 表示u是v的上司,现在要提拔l个和r个人,要先提拔上司,上司提拔了,才能提拔下属,也就是只能先提拔入度为0的点。求在提拔l和r个人的情况下一定会提拔的人,求在r下一定不会被提拔的人 */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 5000+10;
#pragma comment(linker, "/STACK:1024000000,1024000000")
//正向和反向的
vector<int>fo[maxn],re[maxn];
int vis[maxn];
int dfs1(int u,int timer)
{
//时间戳 避免重复访问,也是一个剪枝
if(vis[u] == timer + 2 )
return 0;
//注意因为vis是默认初始化0的,时间戳肯定不能与0重合,因为点重0开始标记,所以这里时间戳加2与vis初始化隔开
vis[u] = timer+2 ;//把当前点打上时间戳,回退到这个点的时候结束这一层
int sum1=1;
for(int i=0; i<fo[u].size(); i++)
sum1+=dfs1(fo[u][i],timer);
return sum1;
}
int dfs2(int u,int timer)
{
if(vis[u] == timer + 2 )
return 0;
vis[u] = timer + 2;
int sum2=1;
for(int i=0; i<re[u].size(); i++)
sum2+=dfs2(re[u][i],timer);
return sum2;
}
int main()
{
// freopen("in.txt","r",stdin);
int l,r,n,m,res[maxn];
while(~scanf("%d%d%d%d",&l,&r,&n,&m))
{
memset(res,0,sizeof(res));
memset(vis,0,sizeof(vis));
int t1=0,t2=0,t3=0;
for(int i=0; i<n; i++) {
fo[i].clear();
re[i].clear();
}
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
fo[u].push_back(v),re[v].push_back(u);
}
for(int i=0; i<n; i++)
res[i]=dfs1(i,i);
for(int i=0; i<n; i++)
{
if(n-res[i]<l)
t1++;
if(n-res[i]<r)
t2++;
}
memset(vis,0,sizeof(vis));
for(int i=0; i<n; i++)
res[i]=dfs2(i,i);
for(int i=0; i<n; i++)
if(res[i]>r)
t3++;
printf("%d
%d
%d
",t1,t2,t3);
}
return 0;
}
- 思路:如果提拔n个人,这个人被提拔的条件是:如果这个人不被提拔,那么他下面的人全都不会被提拔,然后剩下的人小于n,那么这个人一定可以被提拔(这个人和他下面的人为一个集合),同理r也是,至于一定不会被提拔的人就利用反向建图。如果这个人被提拔,那么他下面的人全都会被提拔,dfs跑出每个点所支配的人数包括自己(注意这里要加上时间戳,时间戳的值还要和vis的初始化隔开,所以加2,加1都行),且判断的时候默认是向下的,并且是反向建图。当n-re[i]大于所要提拔的数量,当前点其实肯定不会被提拔。
- 学习体会:这题乍一看好像是拓扑,但是这题有个小窍门,那就是利用逆向思维—反过来想,现在是找出一定会被提拔的人,重复的出点没影响。这道题就像概率中“至少”“至多”,用反向和总和相减,巧妙求出结果。