图(Graph)——图G是由两个集合V(G)和E(G)组成的,记为G=(V,E)其中:V(G)是顶点的非空有限集,E(G)是边的有限集合,边是顶点的无序对或有序对。
有向图——有向图G是由两个集合V(G)和E(G)组成
其中:V(G)是顶点的非空有限集,E(G)是有向边(也称弧)的有限集合,弧是顶点的有序对,记为
无向图——无向图G是由两个集合V(G)和E(G)组成
其中:V(G)是顶点的非空有限集,E(G)是边的有限集合,边是顶点的无序对,记为(v,w)或(w,v),并且(v,w)=(w,v)。
权——与图的边或弧相关的数
网——带权的图
顶点的度
- 无向图中,顶点的度为与每个顶点相连的边数;
- 有向图中,顶点的度分成入度与出度;
- 入度:以该顶点为头的弧的数目;
- 出度:以该顶点为尾的弧的数目;
路径——路径是顶点的序列V={Vi0,Vi1,……Vin},满足(Vij-1,Vij)∈E 或 < Vij-1,Vij>∈E,(1
路径长度——沿路径边的数目或沿路径各边权值之和
回路——第一个顶点和最后一个顶点相同的路径
简单路径——序列中顶点不重复出现的路径
简单回路——除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路
连通——从顶点V到顶点W有一条路径,则说V和W是连通的
连通图——图中任意两个顶点都是连通部分
连通分量——非连通图的每一个连通部分
强连通图——有向图中,如果对每一对 Vi,Vj 从 Vi 到 Vj 和从 Vj 到 Vi 都存在路径。
实现:为图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(有向图中指以Vi为尾的弧)
特点:
这里我采用的是邻接链表存储结构。图的遍历不管是广度遍历还是深度和树的遍历过程都非常相似,在这里我就很简单概述一下我的思路,如果不理解可以好好看一下我的另一篇文章------ 数据结构:树和二叉树(基础概念操作图文解释)同时不论是深度遍历还是广度遍历都需要辅助空间来标记某结点是否被访问过。
这里的代码很简单,唯一值得注意的是find函数以及输入的关系声明为char x,y;这里只是为了让整个程序有更好的适应性,即无论这个关系是数字与数字之间还是字母与字母之间程序都可以很好的运行。
int find(int n,char v)//求v在表头数组中的下标
{
for(int i=1;i<=n;i++)
if(m[i].data==v)
return i;
return -1;
}
void creat(int n1,int n2)//生成邻接表
{
char x,y;
int z,n;
jd* p;
getchar();//吸收回车
for(int i=0;i<n2;i++)
{
cin>>x>>y;
getchar();
z=find(n1,x);
n=find(n1,y);
p=(jd*)malloc(sizeof(jd));
p->id=z;
p->next=m[n].firstnode;//头插
m[n].firstnode=p;
p=(jd*)malloc(sizeof(jd));
p->id=n;
p->next=m[z].firstnode;
m[z].firstnode=p;
}
}
思路: 每次先访问表头的第一个孩子结点,若未访问过,则访问这个以这个孩子结点为表头的第一个孩子节点,若访问过,则访问它的第二个孩子结点······,这个过程非常类似于树的深度遍历不断访问左孩子过程。
void dfs(int v,int visit[10])//深度遍历
{
visit[v]=1;//标记访问过的位置
jd* w;
w=m[v].firstnode;//访问v结点的第一个孩子节点
cout<<v;
while(w!=NULL)//解决某结点分叉
{
if(!visit[w->id])//如果w->id未访问过继续递归进入下一层
dfs(w->id,visit);
w=w->next;//如果访问过就访问w->id的兄弟节点
}
}
思路: 顺序访问起始表头(这个表头是随意的)的所有孩子结点,入队列,按队列的存储顺序访问相应表头的所有孩子结点,相当于树的层次遍历。
void bfs(int v,int visit[10])//广度遍历
{
visit[v]=1;//标记起始节点
jd* w;
queue<int> q;//声明对列
q.push(v);//起始结点入队
while(!q.empty())
{
v=q.front();//取队头结点
cout<<v;
q.pop();//删除(更新)队头元素
w=m[v].firstnode;//访问v结点第一个孩子结点
while(w!=NULL)//遍历与某一个元素所有有关系的结点
{
if(!visit[w->id])//若此结点未被遍历过则入队
{
q.push(w->id);//w->id结点入队
visit[w->id]=1;//标记w->id结点
}
w=w->next;//访问w的兄弟结点
}
}
}
思路: 如果一个图的所有结点都是连通的,那么for循环里的代码就只会执行一次,如果有多个连通分量,那么当一个连通分量的所有结点遍历完成后必然会进入for循环,然后继续遍历下一个连通分量,直到所有的结点都遍历完。
int travel(int n)
{
int visit[10],sum=0;
for(int j=1;j<10;j++)//初始化辅助数组为未访问状态
visit[j]=0;
for(int j=1;j<=n;j++)//连通性问题的核心
{
if(visit[j]==0)
{
bfs(j,visit);
sum++;
}
}
return sum;
}
完整代码
#include
#include
#include
using namespace std;
typedef struct node//结点
{
int id;
struct node* next;
}jd;
typedef struct head//表头
{
char data;
struct node* firstnode;
}td;
td m[10];
int find(int n,char v)
{
for(int i=1;i<=n;i++)
if(m[i].data==v)
return i;
return -1;
}
void creat(int n1,int n2)//生成邻接表
{
char x,y;
int z,n;
jd* p;
getchar();
for(int i=0;i<n2;i++)
{
cin>>x>>y;
getchar();
z=find(n1,x);
n=find(n1,y);
p=(jd*)malloc(sizeof(jd));
p->id=z;
p->next=m[n].firstnode;//头插
m[n].firstnode=p;
p=(jd*)malloc(sizeof(jd));
p->id=n;
p->next=m[z].firstnode;
m[z].firstnode=p;
}
}
void dfs(int v,int visit[10])//深度遍历
{
visit[v]=1;
jd* w;
w=m[v].firstnode;
cout<<v;
while(w!=NULL)//解决某结点分叉
{
if(!visit[w->id])
dfs(w->id,visit);
w=w->next;
}
}
void bfs(int v,int visit[10])
{
visit[v]=1;
jd* w;
queue<int> q;
q.push(v);//起始结点入队
while(!q.empty())
{
v=q.front();
cout<<v;
q.pop();//删除队头元素
w=m[v].firstnode;
while(w!=NULL)//遍历与某一个元素所有有关系的结点
{
if(!visit[w->id])//若此结点未被遍历过则入队
{
q.push(w->id);
visit[w->id]=1;
}
w=w->next;
}
}
}
int travel(int n)
{
int visit[10],sum=0;
for(int j=1;j<10;j++)
visit[j]=0;
for(int j=1;j<=n;j++)
{
if(visit[j]==0)
{
bfs(j,visit);
sum++;
}
}
return sum;
}
int main()
{
int n1,n2,visit[10]={0},s,e;
char d;
cin>>n1>>n2;
for(int i=1;i<=n1;i++)//初始化表头
{
cin>>m[i].data;
m[i].firstnode=NULL;
}
creat(n1,n2);
int m=travel(n1);
cout<<m;
bfs(1,visit);
return 0;
}
本篇博客讲的都是图最基本的概念和操作以及本人自己的一点理解,希望这篇博客能够帮助到屏幕前的小伙伴!!!我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。