《算法笔记》10.1 10.2 10.3小节——图算法专题->图的定义、存储、遍历

10.2图的存储

可以用邻接矩阵或者邻接表来存储图
邻接矩阵:二维数组,对于无向图,邻接矩阵是一个对称矩阵,二维数组占的内存比较大,只适用于顶点小于1000的题目
邻接表:使用N个链表来组成邻接表,链表比较复杂,所以使用vector来实现邻接表,比如

vector<int> Adj[N];
vector<Node> Adj[N];

这里还可以定义结构体Node的构造函数,不用定义临时变量就可以实现加边操作

10.3图的遍历

10.3.1用DFS法遍历图
PAT A1034
这题使用邻接表来存储
使用DFS解决
PAT A1034 Head of a Gang
这题用了DFS,无向图,邻接矩阵

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<map>
#include<climits>
#include <cstring>
#include<iostream>
using namespace std;
const int maxn = 2010;

map<int, string> intToString;//编号->姓名
map<string, int> stringToInt;//姓名->编号
map<string, int> Gang;//head->人数

int G[maxn][maxn], weight[maxn];
int n, k, numPerson;
int vis[maxn] = { 0 };//0表示未被访问,1表示被访问
//DFS访问单个连通块
void DFS(int nowVisit,int &head,int &numMember,int &totalValue)
{
	numMember++;
	vis[nowVisit] = 1;
	if (weight[nowVisit] > weight[head])
	{
		head = nowVisit;
	}
	for (int i = 0; i < numPerson; i++)//枚举所有人
	{
		if (G[nowVisit][i] > 0)
		{
			totalValue += G[nowVisit][i];
			G[nowVisit][i] = G[i][nowVisit] = 0;
			if (vis[i] == 0)
			{
				DFS(i, head, numMember, totalValue);
			}
		}
	}
}

void DFSTrave()
{
	for (int i = 0; i < numPerson; i++)
	{
		if (vis[i] ==0)
		{
			int head = i, numMember = 0, totalValue = 0;
			DFS(i,head,numMember,totalValue);
			if (numMember > 2 && totalValue > k)
				Gang[intToString[head]] = numMember;
		}
	}
}
int change(string str)
{
	if (stringToInt.find(str) != stringToInt.end())
		return stringToInt[str];
	else
	{
		stringToInt[str] = numPerson;
		intToString[numPerson] = str;
		return numPerson++;
	}
}
int main()
{
	int w;
	string str1, str2;
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		cin >> str1 >> str2 >> w;
		int id1 = change(str1);
		int id2 = change(str2);
		weight[id1] += w;
		weight[id2] += w;
		G[id1][id2] += w;
		G[id2][id1] += w;
	}
	DFSTrave();
	cout << Gang.size() << endl;
	map<string, int>::iterator it;
	for (it = Gang.begin(); it != Gang.end(); it++)
	{
		cout << it->first <<" "<< it->second << endl;
	}
	return 0;
}

使用并查集解决

10.3.2用BFS法遍历图
PAT A1076 Forwards on Weibo
这题因为有一个转发层数的限制,所以用BFS比DFS简单许多,在BFS有一个判断,要先把转发数加上,再判断,最后判断成功才可以设置Inq数组,然后入队

if (inq[next.id] == 0 && next.layer <= L)
			{
				q.push(next);
				inq[next.id] = 1;
				numForward++;
			}

另外当输入追随者的时候需要一条从粉丝指向博主的有向边

Adj[idfollow].push_back(user);//注意这里的边在邻接表里面是怎么写的

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include <cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxv = 1010;
struct node {
	int id;
	int layer;
};
vector<node> Adj[maxv];//邻接表
int inq[maxv] = { 0 };//=0表示不在队列q中,反之表示在队列q中
int BFS(int s, int L)
{
	int numForward = 0;
	queue<node> q;
	node start;
	start.id = s;
	start.layer = 0;
	q.push(start);
	inq[start.id] = 1;
	while (!q.empty())
	{
		node topnode = q.front();
		q.pop();
		int u = topnode.id;
		for (int i = 0; i < Adj[u].size(); i++)
		{
			node next = Adj[u][i];
			next.layer = topnode.layer + 1;
			if (inq[next.id] == 0 && next.layer <= L)
			{
				q.push(next);
				inq[next.id] = 1;
				numForward++;
			}
		}
	}
	return numForward;
}
int main()
{
	node user;
	int n, L, numfollow, idfollow;
	scanf("%d %d",&n,&L);
	for (int i = 1; i <=n; i++)
	{
		user.id = i;
		scanf("%d",&numfollow);
		for (int j = 1; j <= numfollow; j++)
		{
			scanf("%d",&idfollow);
			Adj[idfollow].push_back(user);//注意这里的边是怎么写的
		}
	}
	int numquery, s;
	scanf("%d",&numquery);
	for (int i = 1; i <= numquery; i++)
	{
		memset(inq, 0, sizeof(inq));
		scanf("%d",&s);
		int numForward = BFS(s, L);
		printf("%d\n",numForward);
	}

	return 0;
}

习题

问题 A: 第一题
问题描述:该题的目的是要你统计图的连通分支数。

  • 输入
每个输入文件包含若干行,每行两个整数i,j,表示节点i和j之间存在一条边。
  • 输出
输出每个图的联通分支数。
  • 样例输入
1 4
4 3
5 5
  • 样例输出
2

邻接表做法:
这题看起来蛮简单的怎么做起来就不一样了orz,我一开始纠结题目给的编号都不是连在一起的,这样该怎么枚举呢,后来看到题解才知道可以用Adj[i].size()!=0来判断这个编号有没有出现过,另外还需要max来记录最大的编号
这部分也是必要的:

if (temp1!=temp2)
		{
			Adj[temp1].push_back(temp2);
			Adj[temp2].push_back(temp1);
		}
		else
		{
			Adj[temp1].push_back(temp2);
		}

否则会显示运行错误,不知道为啥会显示这个,我觉得加不加判断都一样

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<climits>
#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int maxn = 500010;
vector<int> Adj[maxn];
int sum=0;//表示连通块的个数
int vis[maxn] = { 0 };
//DFS访问单个连通块
void DFS(int u)
{
	vis[u] = 1;
	for (int i = 0; i < Adj[u].size(); i++)
	{
		int v = Adj[u][i];
		if (vis[v] == 0)
		{
			DFS(v);
		}
	}	
}


int main()
{
	int temp1, temp2;
	int i;
	int max;
	//下面构建邻接表,计算节点个数n;
	while (scanf("%d %d", &temp1, &temp2)!=EOF)
	{
		
		if (temp1!=temp2)
		{
			Adj[temp1].push_back(temp2);
			Adj[temp2].push_back(temp1);
		}
		else
		{
			Adj[temp1].push_back(temp2);
		}
		max = max > temp1 ? max : temp1;
		max = max > temp2 ? max : temp2;
	}
	for (i = 0; i <=max; i++)
	{
		if (vis[i] == 0&&Adj[i].size()!=0)
		{
			sum++;
			DFS(i);
		}
	}
	
	printf("%d\n",sum);
	return 0;
}

并查集做法
想了想这题还可以用并查集做,思路简单不少
如果提示运行错误,错误的原因一般是数组越界
①maxn取到了数组的上界
②maxn取小了,题目输入了很多很多数让数组越界访问了

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<set>
using namespace std;
const int maxn = 1000010;
int father[maxn];
set<int> st;
int findFather(int x)
{
	int a = x;
	while (x != father[x])
	{
		x = father[x];
	}
	while (a != father[a])
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}
void Union(int a, int b)
{
	int faA = findFather(a);
	int faB = findFather(b);
	if (faA != faB)
	{
		father[faA] = faB;
	}
}
void init(int n)
{
	for (int i = 1; i < n; i++)
	{
		father[i] = i;
	}
}
int main()
{
	int temp1, temp2;
	int i;
	//因为不知道总数,所以就初始化全部的序号	
	init(maxn);
	while (scanf("%d %d", &temp1, &temp2)!=EOF)
	{
		Union(temp1,temp2);		
		st.insert(temp1);
		st.insert(temp2);
		
	}
	int ans = 0;	
	for (set<int>::iterator it = st.begin(); it != st.end(); it++)
	{		
		if (father[*it] == *it) ans++;
	}		
	printf("%d\n",ans);
	return 0;
}

问题 B: 连通图
问题描述:给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。

  • 输入
每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据,每行有两个值 x 和 y(0<x, y <=n),表示顶点 x 和 y 相连,顶点的编号从 1 开始计算。输入不保证这些边是否重复。
  • 输出
对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"
  • 样例输入
4 3
4 3
1 2
1 3
5 7
3 5
2 3
1 3
3 2
2 5
3 4
4 1
7 3
6 2
3 1
5 6
0 0
  • 样例输出
YES
YES
NO

DFS做法:

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<climits>
#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int maxn = 1010;
vector<int> Adj[maxn];
int vis[maxn] = { 0 };
//DFS访问单个连通块
void DFS(int u)
{
	vis[u] = 1;
	for (int i = 0; i < Adj[u].size(); i++)
	{
		int v = Adj[u][i];
		if (vis[v] == 0)
		{
			DFS(v);
		}
	}
}
int main()
{
	int temp1, temp2;
	int i;
	int n, m;
	int sum;
	while (scanf("%d %d", &n, &m) != EOF)
	{
		if (n == 0 && m == 0)
			break;
		else
		{
			for (i = 0; i < m; i++)
			{
				scanf("%d %d", &temp1, &temp2);
				if (temp1 != temp2)
				{
					Adj[temp1].push_back(temp2);
					Adj[temp2].push_back(temp1);
				}
				else
				{
					Adj[temp1].push_back(temp2);
				}
			}	
			sum = 0;
			for (i = 1; i <= n; i++)
			{
				if (vis[i] == 0)
				{
					sum++;
					DFS(i);
				}
			}
			if (sum == 1)
				printf("YES\n");
			else
				printf("NO\n");			
		}
	}
	return 0;
}

并查集做法

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<set>
using namespace std;
const int maxn = 1010;
int father[maxn];
int findFather(int x)
{
	int a = x;
	while (x != father[x])
	{
		x = father[x];
	}
	while (a != father[a])
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}
void Union(int a, int b)
{
	int faA = findFather(a);
	int faB = findFather(b);
	if (faA != faB)
	{
		father[faA] = faB;
	}
}
void init(int n)
{
	for (int i = 1; i < n; i++)
	{
		father[i] = i;
	}
}
int main()
{
	int n, m;
	int temp1, temp2;
	int i;
	int ans;
	while (scanf("%d %d", &n, &m) != EOF)
	{
		if (n == 0 && m == 0)
			break;
		else
		{
			init(n);
			for (i = 0; i < m; i++)
			{
				scanf("%d %d", &temp1, &temp2);
				Union(temp1, temp2);
			}
			ans = 0;
			for (i = 1; i <= n; i++)
			{
				if (father[i] == i) ans++;
			}
			if (ans == 1)
				printf("YES\n");
			else
				printf("NO\n");
		}
	}	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值