图这种数据结构表现的是对象集合以及其间关系的集合。图里的“对象”称为结点(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),删除与添加都很方便。时间复杂度为。
图的邻接矩阵消耗内存空间等于顶点的平方数。对于稀疏图尤其浪费,并且只能记录顶点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;
}