文章目录
- 概念以及基本算法实现
- 重难点 最小生成树相关算法
- 红皮书 图算法
- 1、设有向图G(V,E)采用领结表存储,节点集为1到n的整数G(V)={1,2,…,n},边的数量为e,设计一个算法,计算G中所有顶点的入度,结果存放在一维数组中
- 2、自由数(无环连通图),T = (V,E)的直径是书中所有顶点之间最短路径的最大值,设计一个一个时间复杂度尽可能第的算法求T的直径
- 3、判断图是否是树
- 4、已知图中两个顶点,设计一个算法求出顶点i到顶点j的所有简单路径
- 5、编写函数,给定节点i和步长k,计算节点i经k步能到达的所有节点
- 6、 深度优先遍历实现拓扑排序
- 7、求限定条件下的支撑树
- 8、已知图的领结链表,设计算法生成相应的逆领接链表,时间复杂度为O(N+E)
- 9、设有一个正权有向图G=(V,E),w是G的一个顶点,w的偏心距定义为max{从u到w的最短路径长度|u属于V},其中的路径长度是指路径上各边权值之和,将G中偏心距最小的顶点称为G的中心,设计一个函数返回G的中心
概念以及基本算法实现
重难点 最小生成树相关算法
这里主要参考 作用太大了销夜
最小生成树算法的相关变形题
kruskal 基本算法 求最小生成树
先从最简单的并查集实现kruskal算法写起
并查集表示:
typedef struct Edge{//并查集的边节点 front,to为边的指向,w代表权值
int front;
int to;
int w;
}Edge;
找到所属根节点:
int father[maxsize] = {0}; //所属根节点
//查找根节点
int Find(int x){ //递归找到所属根节点
if(father[x]<=0){ //找到了
return x;
} //递归查找根节点
return father[x] = Find(father[x]);
}
按秩合并:
void Union(int x, int y){ //按秩合并两个并查集 秩:当前节点深度乘以-1
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return; //两者属于同一个并查集,无需合并
if(father[fx] < father[fy]){//fy深度 小于 fx深度 ,fy合并到fx中
father[fy] = fx;
}
else{ //否则 fx合并到 fy中
if(father[fx] == father[fy]){ //秩相同时,合并深度加1 ,秩要减1,
father[fy]--;
}
father[fx] = fy;
}
}
对边进行排序: 实现从图到并查集的转换,同时使用冒泡排序边结构体进行排序
int SortEdge(int graph[][maxsize], int n, Edge edge[]){ //实现两个功能,将图转化为并查集表示,同时对并查集进行排序
int edgenum = 0; //边的个数
for(int i = 0; i <= n; i++) //无向图遍历上半矩阵即可
for(int j = i+1; j <= n; j++)
if(graph[i][j]){ //存在边,赋值
edgenum++;
edge[edgenum].front = i;
edge[edgenum].to = j;
edge[edgenum].w = graph[i][j];
}
//冒泡排序对边进行排序
for(int i = edgenum; i > 1; i--){
int flag = 0;
for(int j =1; j < i; j++){
if(edge[j].w > edge[j+1].w){ //递增排序 求最大生成树只需要改为递减排序即可
Edge temp = edge[j];
edge[j] = edge[j+1];
edge[j+1] = temp;
flag = 1; //本趟进行了交换
}
}
if(flag == 0){ //该趟未排序,已经有序
break;
}
}
return edgenum;
}
上述代码基本上实现了一个并查集的各个功能,下面实现最小生成树算法
思路:
- kruskal算法实现基本步骤
- 对边进行排序
- 每次选取最小的边,该边的两个顶点是否属于同一个并查集
- 如果是,已经在最小生成树当中,如果不是,就加入,同时记录当前最小生成树权值之和
- 添加变量记录并查集个数以及遍历的边的下标
代码:
//基础算法:求最小生成树
void Kruskal_MinTree(int graph[][maxsize], int n){
Edge edge[maxsize];
int edgenum = SortEdge(graph,n,edge); //对当前图进行构造并查集,并排序
int setnum = n; //连通分量个数,最开始为n个顶点,所以有n个
int k = 1;
int ans = 0;
while(setnum > 1){ //最小生成树的算法实现
int x = edge[k].front;
int y = edge[k].to;
int w = edge[k].w; //不断取最小的边,判断是否处于同一个并查集,不同就加入
k++;
if(Find(x) == Find(y)) continue;
Union(x,y); //属于不同并查集,合并
setnum--;
ans += w;
printf("%d--%d:%d ", x, y, w);
}
printf("最小生成树的权值为%d", ans);
}
全部代码: 实现最小生成树
//最小生成树算法
typedef struct Edge{//并查集的边节点 front,to为边的指向,w代表权值
int front;
int to;
int w;
}Edge;
int father[maxsize] = {0}; //所属根节点
//查找根节点
int Find(int x){ //递归找到所属根节点
if(father[x]<=0){ //找到了
return x;
} //递归查找根节点
return father[x] = Find(father[x]);
}
//按秩合并
void Union(int x, int y){ //按秩合并两个并查集 秩:当前节点深度乘以-1
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return; //两者属于同一个并查集,无需合并
if(father[fx] < father[fy]){//fy深度 小于 fx深度 ,fy合并到fx中
father[fy] = fx;
}
else{ //否则 fx合并到 fy中
if(father[fx] == father[fy]){ //秩相同时,合并深度加1 ,秩要减1,
father[fy]--;
}
father[fx] = fy;
}
}
//对边进行排序
int SortEdge(int graph[][maxsize], int n, Edge edge[]){ //实现两个功能,将图转化为并查集表示,同时对并查集进行排序
int edgenum = 0; //边的个数
for(int i = 0; i <= n; i++) //无向图遍历上半矩阵即可
for(int j = i+1; j <= n; j++)
if(graph[i][j]){ //存在边,赋值
edgenum++;
edge[edgenum].front = i;
edge[edgenum].to = j;
edge[edgenum].w = graph[i][j];
}
//冒泡排序对边进行排序
for(int i = edgenum; i > 1; i--){
int flag = 0;
for(int j =1; j < i; j++){
if(edge[j].w > edge[j+1].w){ //递增排序 求最大生成树只需要改为递减排序即可
Edge temp = edge[j];
edge[j] = edge[j+1];
edge[j+1] = temp;
flag = 1; //本趟进行了交换
}
}
if(flag == 0){ //该趟未排序,已经有序
break;
}
}
return edgenum;
}
//基础算法:求最小生成树
void Kruskal_MinTree(int graph[][maxsize], int n){
Edge edge[maxsize];
int edgenum = SortEdge(graph,n,edge); //对当前图进行构造并查集,并排序
int setnum = n; //连通分量个数,最开始为n个顶点,所以有n个
int k = 1;
int ans = 0;
while(setnum > 1){ //最小生成树的算法实现
int x = edge[k].front;
int y = edge[k].to;
int w = edge[k].w; //不断取最小的边,判断是否处于同一个并查集,不同就加入
k++;
if(Find(x) == Find(y)) continue;
Union(x,y); //属于不同并查集,合并
setnum--;
ans += w;
printf("%d--%d:%d ", x, y, w);
}
printf("最小生成树的权值为%d", ans);
}
kruskal 进阶算法1 加入新边求最小生成树
思路: 在加入新边的环中找到最大的那一条边,删除最大的就是当前的最小生成树
-
前面的找父节点、排序算法不变,后序的最小生成树的算法也是
-
添加一个算法DFS寻找当前新加入边所形成的环中最大的那一条边
- 添加二维数组保存最小生成树
- 利用深搜+回溯找到最大边并返回
-
添加一个加入新边求最小生成树的函数
- 利用dfs所找到的最大边判断,是否需要更新 更新判断条件,新边权值小于当前环中边最大值
-
kruskal函数基本不变
DFS找最大边:
int mintree[maxsize][maxsize] = {0}; //领接矩阵存储最小生成树
//
//在用图存储的最小生成树中找到新形成的环中,除新加入的边的最大值
void DFS_FindMostLong(int v, int b, int n,int visited[], int curw, int &maxw, Edge &maxe){
//v顶点为新加入的边的起始节点,b为结尾顶点,n为环中边的个数,visited标记是否访问该边,
//curw存储当前最大权值,maxw为需要找到的最大权值,maxe保存最大边
visited[v] = 1;
if(v == b){ //已经找完了
maxw = curw;
return;
}
for(int i = 0; i <= n; i++){
if(mintree[v][i] != 0 && !visited[i]){ //遍历未被访问过的边
int temp = curw; //用于回溯
if(mintree[v][i] > curw){ //记录更大的边
curw = mintree[v][i];
maxe.front = v;
maxe.to = i;
}
DFS_FindMostLong(i,b,n,visited,curw,maxw,maxe);
curw = temp; //回溯
}
}
}
加入新边判断是否需要更新最小生成树
//加入新边找到最小生成树
int AddNewEdge(Edge newe, int ans, int n){
int visited[maxsize] ={0};
int maxw; //存储最长边
Edge maxe;
DFS_FindMostLong(newe.front, newe.to,n,visited, 0, maxw, maxe);
if(newe.w < maxw){ //新加入的边的权值如果小于当前环中最长边的权值,替换
ans = ans - maxw + newe.w;
mintree[maxe.front][maxe.to] = 0; //删除最长的那条边
mintree[maxe.to][maxe.front] = 0;
mintree[newe.front][newe.to] = newe.w; //加入新边
mintree[newe.to][newe.front] = newe.w;
}
return ans;
}
整体实现代码:
typedef struct Edge{//并查集的边节点 front,to为边的指向,w代表权值
int front;
int to;
int w;
}Edge;
int father[maxsize] = {0}; //所属根节点
//查找根节点
int Find(int x){ //递归找到所属根节点
if(father[x]<=0){ //找到了
return x;
} //递归查找根节点
return father[x] = Find(father[x]);
}
//按秩合并
void Union(int x, int y){ //按秩合并两个并查集 秩:当前节点深度乘以-1
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return; //两者属于同一个并查集,无需合并
if(father[fx] < father[fy]){//fy深度 小于 fx深度 ,fy合并到fx中
father[fy] = fx;
}
else{ //否则 fx合并到 fy中
if(father[fx] == father[fy]){ //秩相同时,合并深度加1 ,秩要减1,
father[fy]--;
}
father[fx] = fy;
}
}
//进阶算法1:加入一条新边,求最小生成树
int mintree[maxsize][maxsize] = {0}; //领接矩阵存储最小生成树
//
//在用图存储的最小生成树中找到新形成的环中,除新加入的边的最大值
void DFS_FindMostLong(int v, int b, int n,int visited[], int curw, int &maxw, Edge &maxe){
//v顶点为新加入的边的起始节点,b为结尾顶点,n为环中边的个数,visited标记是否访问该边,
//curw存储当前最大权值,maxw为需要找到的最大权值,maxe保存最大边
visited[v] = 1;
if(v == b){ //已经找完了
maxw = curw;
return;
}
for(int i = 0; i <= n; i++){
if(mintree[v][i] != 0 && !visited[i]){ //遍历未被访问过的边
int temp = curw; //用于回溯
if(mintree[v][i] > curw){ //记录更大的边
curw = mintree[v][i];
maxe.front = v;
maxe.to = i;
}
DFS_FindMostLong(i,b,n,visited,curw,maxw,maxe);
curw = temp; //回溯
}
}
}
//加入新边找到最小生成树
int AddNewEdge(Edge newe, int ans, int n){
int visited[maxsize] ={0};
int maxw; //存储最长边
Edge maxe;
DFS_FindMostLong(newe.front, newe.to,n,visited, 0, maxw, maxe);
if(newe.w < maxw){ //新加入的边的权值如果小于当前环中最长边的权值,替换
ans = ans - maxw + newe.w;
mintree[maxe.front][maxe.to] = 0; //删除最长的那条边
mintree[maxe.to][maxe.front] = 0;
mintree[newe.front][newe.to] = newe.w; //加入新边
mintree[newe.to][newe.front] = newe.w;
}
return ans;
}
//算法实现
int Kruskal_AddNewEdge(int graph[][maxsize], int n, Edge newe){
Edge edge[maxsize];
int edgenum = SortEdge(graph,n,edge);
int setnum = n;
int k = 1;
int ans = 0;
while(setnum > 1){
int x = edge[k].front;
int y = edge[k].to;
int w = edge[k].w;
k++;
if(Find(x) == Find(y)) continue;
Union(x,y);
setnum--;
ans += w;
mintree[x][y] = mintree[y][x] = w; //利用矩阵保存最小生成树
}
ans = AddNewEdge(newe,ans,n);
printf("新的最小生成树的最小权值为%d",ans);
return ans;
}
kruskal 进阶算法2 求次小生成树
思路:
- 同加入一条新边求最小生成树一样,不同的就是需要枚举所有未加入最小生成树的边,所有得加入一个新的标记数组判断是否有被访问过
- 算法实现的话,就额外添加一个函数,不断遍历未被访问过的边求最小生成树,保存当前最小值即可
代码:
typedef struct Edge{//并查集的边节点 front,to为边的指向,w代表权值
int front;
int to;
int w;
}Edge;
int father[maxsize] = {0}; //所属根节点
//查找根节点
int Find(int x){ //递归找到所属根节点
if(father[x]<=0){ //找到了
return x;
} //递归查找根节点
return father[x] = Find(father[x]);
}
//按秩合并
void Union(int x, int y){ //按秩合并两个并查集 秩:当前节点深度乘以-1
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return; //两者属于同一个并查集,无需合并
if(father[fx] < father[fy]){//fy深度 小于 fx深度 ,fy合并到fx中
father[fy] = fx;
}
else{ //否则 fx合并到 fy中
if(father[fx] == father[fy]){ //秩相同时,合并深度加1 ,秩要减1,
father[fy]--;
}
father[fx] = fy;
}
}
//对边进行排序
int SortEdge(int graph[][maxsize], int n, Edge edge[]){ //实现两个功能,将图转化为并查集表示,同时对并查集进行排序
int edgenum = 0; //边的个数
for(int i = 0; i <= n; i++) //无向图遍历上半矩阵即可
for(int j = i+1; j <= n; j++)
if(graph[i][j]){ //存在边,赋值
edgenum++;
edge[edgenum].front = i;
edge[edgenum].to = j;
edge[edgenum].w = graph[i][j];
}
//冒泡排序对边进行排序
for(int i = edgenum; i > 1; i--){
int flag = 0;
for(int j =1; j < i; j++){
if(edge[j].w > edge[j+1].w){ //递增排序 求最大生成树只需要改为递减排序即可
Edge temp = edge[j];
edge[j] = edge[j+1];
edge[j+1] = temp;
flag = 1; //本趟进行了交换
}
}
if(flag == 0){ //该趟未排序,已经有序
break;
}
}
return edgenum;
}
//在用图存储的最小生成树中找到新形成的环中,除新加入的边的最大值
void DFS_FindMostLong(int v, int b, int n,int visited[], int curw, int &maxw, Edge &maxe){
//v顶点为新加入的边的起始节点,b为结尾顶点,n为环中边的个数,visited标记是否访问该边,
//curw存储当前最大权值,maxw为需要找到的最大权值,maxe保存最大边
visited[v] = 1;
if(v == b){ //已经找完了
maxw = curw;
return;
}
for(int i = 0; i <= n; i++){
if(mintree[v][i] != 0 && !visited[i]){ //遍历未被访问过的边
int temp = curw; //用于回溯
if(mintree[v][i] > curw){ //记录更大的边
curw = mintree[v][i];
maxe.front = v;
maxe.to = i;
}
DFS_FindMostLong(i,b,n,visited,curw,maxw,maxe);
curw = temp; //回溯
}
}
}
//添加新边找到最小生成树
int AddNewEdge(Edge newe, int ans, int n){
int visited[maxsize] ={0};
int maxw; //存储最长边
Edge maxe;
DFS_FindMostLong(newe.front, newe.to,n,visited, 0, maxw, maxe);
printf("在原先的最小生成树中,节点%d到节点%d的路径上的最长边的权值为:%d\n", maxe.front, maxe.to, maxw);
ans = ans - maxw + newe.w;
printf("加入边 %d--%d 后,新的最小生成树的权值为:%d\n\n", maxe.front, maxe.to, ans);
return ans;
}
//找到最小生成树
int Kruskal_SecTree(int graph[][maxsize], int n){
edgenum = SortEdge(graph,n,edge);
int setnum = n;
int k = 1;
int ans = 0;
while(setnum > 1){
int x = edge[k].front;
int y = edge[k].to;
int w = edge[k].w;
k++;
if(Find(x) == Find(y)) continue;
Union(x,y);
setnum--;
ans += w;
mintree[x][y] = mintree[y][x] = w; //利用矩阵保存最小生成树
isuse[k-1] = 1; //记录使用过的边 前面已经k++,所以这里是k-1
}
return ans;
}
int SecondTree(int graph[][maxsize], int n){
int ans = Kruskal_SecTree(graph,n);
int newans = 100000; //保存次小生成树的权值
int min; //保存次小生成树下标
for(int i = 1; i <= edgenum; i++){
if(!isuse[i]){
int temp = AddNewEdge(edge[i],ans,n); //计算加入新边最小权值
if(temp < newans){
newans = temp; //保存最小权值以及对应最小边
}
}
}
printf("次小生成树的权值为%d",newans);
return newans;
}
kruskal 进阶算法3 判断最小生成树是否唯一
思路:
- 因为kruskal求最小生成树是基于排好序的边权值的,如果说能够取得两个最小值相同的边,那么最小生成树不唯一
- 算法实现就是,在kurskal算法实现过程中,判断相同权值的边是否处于不同的并查集,如果是,那么最小生成树不唯一
代码:
typedef struct Edge{//并查集的边节点 front,to为边的指向,w代表权值
int front;
int to;
int w;
}Edge;
int father[maxsize] = {0}; //所属根节点
//查找根节点
int Find(int x){ //递归找到所属根节点
if(father[x]<=0){ //找到了
return x;
} //递归查找根节点
return father[x] = Find(father[x]);
}
//按秩合并
void Union(int x, int y){ //按秩合并两个并查集 秩:当前节点深度乘以-1
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return; //两者属于同一个并查集,无需合并
if(father[fx] < father[fy]){//fy深度 小于 fx深度 ,fy合并到fx中
father[fy] = fx;
}
else{ //否则 fx合并到 fy中
if(father[fx] == father[fy]){ //秩相同时,合并深度加1 ,秩要减1,
father[fy]--;
}
father[fx] = fy;
}
}
//对边进行排序
int SortEdge(int graph[][maxsize], int n, Edge edge[]){ //实现两个功能,将图转化为并查集表示,同时对并查集进行排序
int edgenum = 0; //边的个数
for(int i = 0; i <= n; i++) //无向图遍历上半矩阵即可
for(int j = i+1; j <= n; j++)
if(graph[i][j]){ //存在边,赋值
edgenum++;
edge[edgenum].front = i;
edge[edgenum].to = j;
edge[edgenum].w = graph[i][j];
}
//冒泡排序对边进行排序
for(int i = edgenum; i > 1; i--){
int flag = 0;
for(int j =1; j < i; j++){
if(edge[j].w > edge[j+1].w){ //递增排序 求最大生成树只需要改为递减排序即可
Edge temp = edge[j];
edge[j] = edge[j+1];
edge[j+1] = temp;
flag = 1; //本趟进行了交换
}
}
if(flag == 0){ //该趟未排序,已经有序
break;
}
}
return edgenum;
}
//判断最小生成树是否唯一
bool Kruskal_Isunique(int graph[][maxsize], int n) {
edgenum = SortEdge(graph,n,edge);
int setnum = n;
int k = 1;
int ans = 0;
while (setnum > 1) { //连通分量个数大于1 构造最小生成树的过程
int x = edge[k].front;
int y = edge[k].to;
int w = edge[k].w;
k++;
if (Find(x) == Find(y)) continue; //这条边的两个顶点从属于一个集合舍弃这条边
for (int i = k; i <= edgenum; i++) {
if (edge[i].w == w) { //如果这条新边的权值与刚才那条边权值相同
int newa = edge[i].front;
int newb = edge[i].to;
//若两边的所属连通块相同,说明最小生成树不唯一;需注意此时还没有将a,b为顶点的边合并
if (Find(x) == Find(newa) && Find(y) == Find(newb) || Find(x) == Find(newb) && Find(y) == Find(newa)){
printf("边 %d--%d 与边 %d-- %d 可任意选,最小生成树不唯一\n", x, y, newa, newb);
return false;
}
}
}
Union(x, y); //已经确保该条边没有可替代的边,将这条边合并
setnum--; //合并之后连通分量-1
ans += w;
}
return true;
}
红皮书 图算法
1、设有向图G(V,E)采用领结表存储,节点集为1到n的整数G(V)={1,2,…,n},边的数量为e,设计一个算法,计算G中所有顶点的入度,结果存放在一维数组中
思路:
- 用领接表实现,考虑深度优先遍历或者广度优先遍历,由于深度优先遍历的递归中不好判断结束条件,所以采用广度优先遍历,不需要设置visited数组,只需要遍历每一条边即可
代码:
typedef struct ArcNode{ //边结点
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //顶点节点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领接表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
//领接表的广度优先遍历
int *dfsAGraph(AGraph *G){
int n = G->vexnum;
int *in = (int *)malloc(sizeof(int)*n);
for(int i=0; i < n; i++){
in[i] = 0;
}
for(int i=0; i < G->vexnum;i++){
ArcNode *p = G->adjlist[i].firstarc;
while(p!=NULL){
in[p->adjvex] += 1;
p = p->next;
}
}
return in;
}
2、自由数(无环连通图),T = (V,E)的直径是书中所有顶点之间最短路径的最大值,设计一个一个时间复杂度尽可能第的算法求T的直径
思路:
- 先从任意一个顶点找到该顶点最短路径中最长的一个,这个点为直径的某个端点
- 然后再从这个顶点出发,再进行一次BFS,求出距离该顶点最短路径最长的一个,该顶点为直径的另一端
- 返回两个顶点的路径长度,就是自由树的直径
代码:
typedef struct ArcNode{ //边节点
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //顶点节点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领接表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
int MaxLenBFS(AGraph *G, int v, int dist[]){
int visited[maxsize] = {0};
int queue[maxsize];
int front = -1, rear = -1,i,k,temp,max=0;
ArcNode *p = G->adjlist[v].firstarc;
for(i = 0; i < G->vexnum; i++){
dist[i] = -1;
}
queue[++rear] = v;
visited[v] = 1;
dist[v] = 0;
while(rear != front){
k = queue[++front];
p = G->adjlist[k].firstarc;
while(p!=NULL){
temp = p->adjvex;
if(visited[temp]==0){
queue[++rear] = temp;
visited[temp] = 1;
dist[temp] = dist[k] + 1;
}
p = p->next;
}
}
for(i=0; i < G->vexnum; i++){
if(dist[i]>dist[max]){
max = i;
}
}
return max; //返回端点
}
int Diameter(AGraph *G){
int dist[maxsize];
int first = MaxLenBFS(G,0,dist);
int last = MaxLenBFS(G,first,dist);
printf("直径为:%d",dist[last]);
return dist[last]; //返回直径长度
}
3、判断图是否是树
请设计一个算法判断无向图G是否为一棵树,若是树,返回1,否则返回0
思路
-
判断一个图是否为树需要两个条件
- 只有一个连通图,且该连通图包含所有顶点
- 边数与顶点数构成关系 边数等于顶点数-1 anum = vnum-1
-
实现:
- 深度优先遍历,多添加两个参数,遍历整个图的所有顶点以及边
- 边的遍历需要注意一下,只要有这条边便遍历,这里是唯一和深度优先遍历模板有较大差别的地方
- 此后再判断这条边所在顶点是否访问过,未访问进行深度优先遍历的递归实现
代码:
//思路:若该图为数,则该图为连通图,且顶点数-1 = 边数,深度优先
typedef struct MGraph{
int vex[maxsize];
int edge[maxsize][maxsize];
int vexnum, edgenum;
}MGraph;
int visited[maxsize] = {0};
void DFS(MGraph *G, int v, int &vnum, int &anum){
visited[v] = 1;
vnum++;
for(int i = 0; i < G->vexnum; i++){
if(G->edge[v][i] == 1){
anum++;
if(visited[i] == 0){
DFS(G,i,vnum,anum);
}
}
}
}
bool judgetree(MGraph *G){
int vnum = 0, anum = 0;
DFS(G,0,vnum,anum);
if(vnum == G->vexnum && 2*(vnum-1) == anum){
printf("是一棵树");
return true;
}
else{
printf("不是一棵树");
return false;
}
}
4、已知图中两个顶点,设计一个算法求出顶点i到顶点j的所有简单路径
思路:
- dfs+回溯,在图dfs基础上修改,注意回溯的位置是在遍历完该条边所有领接边之后再回溯,而不是立马遍历一条边就回溯,不然找不到完整的路径,同时visited,path数组也是在dfs遍历的时候就标记
代码:
typedef struct ArcNode{ //边结点
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //顶点节点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领接表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
//思路:深度优先遍历回溯
int visited[maxsize] = {0};
int path[maxsize] = {0};
void dfsfindall(AGraph *G, int i, int j, int count){ //count统计路径长度
path[count++] = i;
visited[i] = 1;
if(i == j){
for(int k = 0; k < count; k++){
printf("经过了节点%d ",path[k]);
}
printf("\n");
}
ArcNode *p = G->adjlist[i].firstarc;
while(p!=NULL){
if(visited[p->adjvex] == 0){
dfsfindall(G,p->adjvex,j,count);
}
p = p->next;
}
visited[i] = 0; //回溯
}
5、编写函数,给定节点i和步长k,计算节点i经k步能到达的所有节点
N*N的矩阵a表示有向图的领接表,其中
d[i,j] = 1 节点i到节点j有边
d[i,j] = 0 节点i到节点j无边
思路:
- dfs回溯(三大步骤)
- 1、确定函数的中的参数
- 2、确定函数的终止条件
- 3、确定单层递归的具体表达式(回溯在这里实现)
代码:
int visited[N] = {0}; //判断是否访问过
int isout[N] = {0}; //判断是否输出
void dfsfindallnode(int a[N][N], int i, int k){
if(k == 0){ //函数的终止条件
if(isout[i] == 0){
printf("可以到达节点%d\n",i);
isout[i] = 1;
return; //结束当前层递归
}
}
for(int j = 0; j < N; j++){
if(a[i][j] == 1 && visited[j]==0){//如果可以到达这个点的话
visited[j]=1;
dfsfindallnode(a,j,k-1);
visited[j]=0; //回溯还原
}
}
}
6、 深度优先遍历实现拓扑排序
若G是一个使用领接表存储的有向图,设计一个算法,利用深度优先遍历算法,对图中节点进行拓扑排序
思路:
- 深度优先遍历最深层的一个节点一定是拓扑序列的最后一个节点,然后依照深度优先遍历修改一下即可
代码:
typedef struct ArcNode{ //边结点
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //顶点节点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领接表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
int visited[maxsize] = {0};
int topo[maxsize];
int t;
void dfs(AGraph *G, int v){
visited[v] = 1;
struct ArcNode *p = G->adjlist[v].firstarc;
while(p!=NULL){
if(visited[p->adjvex] == 0){
dfs(G,p->adjvex);
}
p =p->next;
}
topo[t--] = v; //深度最深的,是拓扑序列的最后一位,然后依次递减
}
void toposort(AGraph *G){
t = G->vexnum-1;
for(int i =0; i < G->vexnum; i++){
if(visited[i] == 0){
dfs(G,i);
}
}
}
7、求限定条件下的支撑树
1、给定连通图G和G中的一个丁点v,求G的支撑树,满足两个条件,
(1)T的根为v
(2)T的层次遍历恰好是以v为起点的G的某个广度优先遍历
思路:
- 需要多建立一个数据结构用来保存支撑树,这个数不是具体的二叉树或者三叉树,所以需要用一个数组指针来保存孩子节点
- 基本实现方式类似于树的层次遍历,但是在每次入队列的时候先得创建对应的支撑树节点,先将根节点入队,然后不断出队,在图中访问对应的连接的节点,
并创建对应的支撑树节点,入队,重复上述操作
代码:
typedef struct ArcNode{ //边结点
int adjvex;
int info;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //表结点
int data;
ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领结表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
typedef struct TNode{ //保存层次遍历的结构与数据
int val,childnum;
struct TNode *child[maxsize];
}TNode;
TNode *bfs(AGraph *G, int v){ //多叉树的层次遍历,每一个图的节点都要重新申请一个树的节点来保存
TNode *queue[maxsize],*k;
int front = -1, rear = -1;
int visited[G->vexnum] = {0};
struct TNode *root = (struct TNode *)malloc(sizeof(struct TNode));
struct ArcNode *p;
root->val = v; //将根节点入队并标记访问数组
visited[v] = 1;
queue[++rear] = root;
while(rear != front){
k = queue[++front];
k->childnum = 0;
p = G->adjlist[k->val].firstarc;
while(p!=NULL){
if(visited[p->adjvex] == 0){
struct TNode *temp = (struct TNode *)malloc(sizeof(struct TNode));
temp->val = p->adjvex;
k->child[k->childnum++] = temp;
queue[++rear] = temp;
visited[p->adjvex] = 1;
}
p = p->next;
}
}
return root;
}
8、已知图的领结链表,设计算法生成相应的逆领接链表,时间复杂度为O(N+E)
思路:
- 遍历所有顶点的边,一边遍历,一边创建逆领接表,指向与顶点互换
- 知识点补充
- 邻接表:反映的是顶点出度的情况。
- 逆邻接表:反映的是顶点的入度情况。
代码:
typedef struct ArcNode{ //领接边
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //领接顶点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct AGraph{ //领接表
VNode adjlist[maxsize];
int vexnum, edgenum;
}AGraph;
//思路:层次遍历每一条边,然后把每条边的指向的节点改为顶点节点,其起始节点为其指向节点
void GetReserseAdjlist(AGraph *G, AGraph *R){ //G为领接链表,R为逆领接链表
R->edgenum = G->edgenum; //初始化逆领接链表
R->vexnum = G->vexnum;
for(int i = 0; i < G->vexnum; i++){
R->adjlist[i].data = G->adjlist[i].data;
R->adjlist[i].firstarc = NULL;
}
for(int i =0; i < G->vexnum; i++){ //遍历领接表
ArcNode *p = G->adjlist[i].firstarc;
while(p!=NULL){
struct ArcNode *temp = (struct ArcNode *)malloc(sizeof(struct ArcNode));
temp->adjvex = i; //指向换一下
temp->next = R->adjlist[p->adjvex].firstarc; //不带头结点的头插法
R->adjlist[p->adjvex].firstarc = temp;
p = p->next; //继续往下找
}
}
}
9、设有一个正权有向图G=(V,E),w是G的一个顶点,w的偏心距定义为max{从u到w的最短路径长度|u属于V},其中的路径长度是指路径上各边权值之和,将G中偏心距最小的顶点称为G的中心,设计一个函数返回G的中心
思路:
先理解偏心距:偏心距指的是其他顶点到某个顶点中的最大值
中心:偏心距最小
先求出每个顶点到其他顶点的最短路径,然后从这些顶点中找到最大值 作为偏心距,然后在从这些偏心距中最小的作为中心
代码:
typedef struct ArcNode{ //边结点
int adjvex;
int info;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //表结点
int data;
struct ArcNode *firstarc;
}VNode;
typedef struct{
VNode adjlist[maxsize]; //领接表
int vexnum, arcnum;
}AGraph;
void Dijstra_MIN(AGraph *G, int v, int A[maxnum][maxnum]){
int i,j,k,min,temp;
ArcNode *p = G->adjlist[v].firstarc;
int dist[G->vexnum] = {maxsize}; //这里不需要path数组,不需要求最短路径
int visited[G->vexnum] = {0};
while(p!=NULL){ //先将源点所连接的边加入最短路径当中
temp = p->adjvex;
dist[temp] = p->info;
p = p->next;
}
visited[v] = 1;
for(i = 0; i < G->vexnum-1; i++){
min = maxnum;
for(j = 0; j < G->vexnum; j++){
if(visited[j]==0 && dist[j] < min){
min = dist[j];
k = j;
}
}
visited[k] = 1;
p = G->adjlist[k].firstarc;
while(p!=NULL){
temp = p->adjvex;
if(visited[temp] == 0 && dist[k]+p->info < dist[temp]){
dist[temp] = dist[k]+p->info;
}
p = p->next;
}
}
for(i = 0; i < G->vexnum; i++){
A[i][v] = dist[i];
}
}
void FindCenter(AGraph *G){
int A[maxnum][maxnum];
int i,j,center,dist=0,mindist=maxnum;
for(i=0; i < G->vexnum; i++){
Dijstra_MIN(G,i,A);
}
for(i=0;i<G->vexnum; i++){
for(j=0; j < G->vexnum; j++){
dist = dist > A[j][i] ? dist : A[j][i];
}
if(dist < mindist){
mindist = dist;
center = i;
}
}
printf("图的中心为%d, 偏心距为%d\n",center, mindist);
}