图 算法 大总结

概念以及基本算法实现

图的概念以及基本算法实现

重难点 最小生成树相关算法

这里主要参考 作用太大了销夜 最小生成树算法的相关变形题

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的直径

思路:

  1. 先从任意一个顶点找到该顶点最短路径中最长的一个,这个点为直径的某个端点
  2. 然后再从这个顶点出发,再进行一次BFS,求出距离该顶点最短路径最长的一个,该顶点为直径的另一端
  3. 返回两个顶点的路径长度,就是自由树的直径

代码:

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);
}
  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值