算法和数据结构——图

图这种数据结构表现的是对象集合以及其间关系的集合。图里的“对象”称为结点(Node)或者顶点(Vertex)。"关系"表示顶点与顶点之间的关系,称为边(Edge)。

图大致分为以下四类:

名称特征
有向图边无方向
无向图边有方向
加权无向图边有权无方向
加权有向图边有权有方向

图的具体例子这里不再啰嗦了。直接来了解一下图的表示与术语:

顶点集合为V,边集合为E的图记作G=(V,E)。另外G=(V,E)的顶点数和边数分别为|V|与|E|。连接两个顶点u,v的边记作e=(u,v)。在无向图中(u,v)与(v,u)代表同一条边。加权图(u,v)的权中称为w(u,v)。无向图中存在(u,v),我们就称顶点u和顶点v相邻(Adjacent)。相邻顶点的序列称为路径(Path)。起点和终点相同的路径为环(Cycle)。不存在环的有向图称为Directed Acyclic Graph(DAG)。与顶点u相连的边数称为u顶点的度(degree)。如果对图G=(V,E)而言,任意两个顶点u,v都存在从u到v的路径,那么G称为连通图。如果G`的顶点集和边集均为G的点集和边集的子集,那么G`为G的子图。

图的表示:邻接矩阵、邻接链表

int M[N][N];//M[i][j]表示顶点i与顶点j相连的边
#include<iostream>
#define MAX 101
using namespace std;
int main(){
	//邻接矩阵 
	int M[MAX][MAX],n;
        //读入顶点数
	cin>>n;
	int u,k,v;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			M[i][j]=0;
	}
	for(int i=0;i<n;i++){
		cin>>u>>k;
		u--;
		for(int j=0;j<k;j++){
			cin>>v;
			v--;
			M[u][v]=1;
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			cout<<M[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}
/*
4
1 2 2 4
2 1 4
3 0
4 1 3
*/

图的邻接矩阵可以通过M[u][v]直接引用(u,v),删除与添加都很方便。时间复杂度为O(1)

图的邻接矩阵消耗内存空间等于顶点的平方数。对于稀疏图尤其浪费,并且只能记录顶点u到一个顶点v的信息。

vector<int>G[N];//邻接链表
#include<iostream>
#include<vector>
#define MAX 100
using namespace std;
vector<int>G[MAX];//邻接链表 
int main(){
	int n;
	int u,k,v;
	cin>>n; 
	for(int i=0;i<n;i++){
		cin>>u>>k;
		u--;
		for(int i=0;i<k;i++){
			cin>>v;
			G[u].push_back(v);
		}
	}
	for(int i=0;i<n;i++){
		cout<<i+1<<": ";
		for(int j=0;j<G[i].size();j++)
			cout<<G[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}

图的基本算法是搜索。

图最具有代表性的搜索算法:深度优先搜索(Depth First Search,DFS)、广度优先搜索(Breadth FIrst Search,BFS)。

深度优先搜索以“能走多远走多远”为基本原则,是图最自然且基本的搜索算法:

我们用临时栈来保存“仍在搜索中的顶点”。

  • 将最初的顶点入栈
  • 只要栈中仍有顶点,进行如下:
    • 访问栈顶顶点u
    • 记录和u连接的顶点v,使其入栈,否则删除出u顶点

使用栈的深度搜索主要需要如下几个变量:

int color[N];//WHITE,GRAY,BLACK表示顶点的状态
              未使用 使用中 结束使用
int M[N][N];//记录各点之间的信息
stack<int> S;//存储顶点         
#include<iostream>
#include<stack>
#define MAX 100
#define WHITE 0//没有访问 
#define GRAY 1//正在访问 
#define BLACK 2//结束访问
using namespace std;
int color[MAX];
int M[MAX][MAX],d[MAX],f[MAX],tt; 
int nt[MAX],n;
//深度搜索递归实现 
void dfs_visit(int u){
	//u结点进入访问
	color[u]=GRAY;
	d[u]=++tt;//记录开始时间
	for(int v=0;v<n;v++){
		if(M[u][v]==0)
			continue;
		if(color[v]==WHITE)
			dfs_visit(v);
	}
	//访问结束 
	color[u]=BLACK;
	f[u]=++tt; 
} 
int next(int u){
	//标号
	for(int v=nt[u];v<n;v++){
		nt[u]=v+1;
		if(M[u][v])
			return v;
	} 
	return -1;
}
//栈实现深度搜索 
void dfs_Stack_visit(int u){
	for(int i=0;i<n;i++)
		nt[i]=0;
	stack<int> S;
	while(!S.empty())
		S.pop();
	//u节点入栈 
	S.push(u);
	color[u]=GRAY;
	d[u]=++tt;
	
	while(!S.empty()){
		int u=S.top();
		int v=next(u);
		if(v!=-1){
			if(color[v]==WHITE){
				color[v]=GRAY;
				d[v]=++tt;
				S.push(v);
			}
		}
		else{
			S.pop();
			color[u]=BLACK;
			f[u]=++tt;
		}
	}	
} 
void dfs(){
	//初始化 
	for(int u=0;u<n;u++)
		color[u]=WHITE;
	for(int u=0;u<n;u++)
		if(color[u]==WHITE)
			//dfs_visit(u);
			dfs_Stack_visit(u);
	for(int u=0;u<n;u++)
		cout<<u+1<<" "<<d[u]<<" "<<f[u]<<endl; 
} 
int main(){
	cin>>n;
	int u,k,v;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			M[i][j]=0; 
	}
	for(int i=0;i<n;i++){
		cin>>u>>k;
		u--;
		for(int j=0;j<k;j++){
			cin>>v;
			v--;
			M[u][v]=1;
		}
	}
	dfs();
	return 0;
} 
/*
6
1 2 2 3
2 2 3 4
3 1 5
4 1 6
5 1 6
6 0
*/

广度优先搜索是尽可能多得搜索与已知点相连的未搜索的顶点,扩大范围搜索。我们常此方法用作求最短路径。

  • 将起点s放入队列Q中
  • 只要Q不为空,
    • 取出顶点u进行访问
    • 将与u相邻的未访问的顶点v放入Q,同时路径数++;

广度优先搜索需要的几个主要变量函数:

int color[N];//同深度搜索
int M[N][N];
Queue<int> Q;
d[n];//d[i]起点到顶点i的距离初始值为INFTY
#include<iostream>
#include<queue>
#define MAX 100
#define WHITE 0
#define GRAY 1
#define BLACK 2 
using namespace std;
static const int INFTY (1<<21);
int color[MAX],d[MAX];
int M[MAX][MAX],n;
void bfs(int s){
	queue<int> Q;
	while(!Q.empty())
		Q.pop();
	for(int i=0;i<n;i++){
		color[i]=WHITE;
		d[i]=INFTY;
	}
	//s定义为起点 
	Q.push(s);
	d[s]=0;
	int u;
	while(!Q.empty()){
		u=Q.front();
		Q.pop();
		for(int v=0;v<n;v++){
			//连不通 
			if(M[u][v]==0)
				continue;
			//已经走过了 
			if(d[v]!=INFTY)
				continue;
			d[v]=d[u]+1;
			Q.push(v);
		}
	}
	for(int i=0;i<n;i++){
		cout<<i+1<<" "<<( (d[i]==INFTY) ? (-1):d[i] )<<endl;
	}
}
int main(){
	int u,k,v;
	cin>>n;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			M[i][j]=0;
	} 
	for(int i=0;i<n;i++){
		cin>>u>>k;
		u--;
		for(int j=0;j<k;j++){
			cin>>v;
			v--;
			M[u][v]=1;
		}
	}
	bfs(0);
	return 0;
}
/*
4
1 2 2 4
2 1 4
3 0
4 1 3
*/

 


连通分量:

输入SNS的朋友关系,判断从指定人物出发能否通过双向朋友链抵达目标人物。

输入:SNS用户数n,以及朋友的关系数m,接下来m行输入关系。接着输入q个问题,q行问题。

输出:每个问题正确输出“yes”否则“no”。

题意:就是将有关系的朋友兄弟放在一起,有关系的则输出yes。

#include<iostream>
#include<vector>
#include<stack>
using namespace std;
static const int MAX = 100000;
static const int NIL = -1;
int n;
vector<int>G[MAX];//邻接表 
int color[MAX];
void dfs(int r,int c){
	stack<int> S;
	S.push(r);
	color[r]=c;
	int u;
	while(!S.empty()){
		u=S.top();
		S.pop();
		for(int i=0;i<G[u].size();i++){
			//和u连接的下一个节点
			int v=G[u][i];
			if(color[v]==NIL){
				color[v]=c;
				S.push(v);
			} 
		}
	}
}
void assignColor(){
	int id=1;
	for(int i=0;i<n;i++)
		color[i]=NIL;
	//第id有关的点都标记起来 
	for(int u=0;u<n;u++){
		if(color[u]==NIL)
			dfs(u,id++);
	}
}
int main(){
	int s,t,m,q;
	cin>>n>>m;
	for(int i=0;i<m;i++){
		cin>>s>>t;
		G[s].push_back(t);
		G[t].push_back(s);
	}
	assignColor();
	cin>>q;
	for(int i=0;i<q;i++){
		cin>>s>>t;
		if(color[s]==color[t])
			cout<<"yes"<<endl;
		else
			cout<<"no"<<endl;
	}
	return 0;
} 
/*
10 9
0 1
0 2
3 4
5 7
5 6
6 7
6 8
7 8
8 9
3
0 1
yes
5 9
yes
1 3
no
*/

还可以并查集来完成,并查集基本函数:

int findFa(int x){
	return (x==F[x])?x:F[x]=findFa(F[x]); 
}
void merge(int x,int y){
	F[findFa(x)]=findFa(y);
}

为有关系的兄弟朋友们标记相同的祖先。

#include<iostream>
#define MAX 100
using namespace std;
int F[MAX];
int findFa(int x){
	return (x==F[x])?x:F[x]=findFa(F[x]); 
}
void merge(int x,int y){
	F[findFa(x)]=findFa(y);
}
int main(){
	int n,q;
	cin>>n>>q;
	for(int i=0;i<n;i++){
		F[i]=i;
	}
	int s,t;
	for(int i=0;i<q;i++){
		cin>>s>>t;
		int x1=findFa(s);
		int y1=findFa(t);
		merge(y1,x1);
	}
	int m;
	cin>>m;
	for(int i=0;i<m;i++){
		cin>>s>>t;
		if(findFa(s)==findFa(t))
			cout<<"yes"<<endl;
		else
			cout<<"no"<<endl; 
		//cout<<i<<"的祖先: "<<F[i]<<endl; 
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值