首先图分为有向图和无向图。
我们先来介绍无向图:
无向图定义:若图中所有的边均满足的两个顶点没有次序关系和方向性,即(v1,v2)和(v2,v1)代表同一条边,则称为无向图。
图所示
无向图就是由结点V={1,2,3,4,5},和边E={(1,2),(1,4),(2,3),(3.4),(3,5),(2,5)},构成的。
我们也不难观察到该图是沿着对角线对称的,即如果图的邻接矩阵是沿对角线对称,便可判断是无向图
无向图的一些概念:
1.完全图
若图中n个结点间所有可能的边均存在,即有
C = n*(n-1)/2条边,则称为完全图。也就是说所有点都连上
2.依附
边(V1,V2)依附于顶点V1,V2
3.子图
若G’ 是图G的子图,则有V(G‘)<=V(G),和E(G’)<=E(G)
4.路径
图中从Vp到Vq的一条路径是指由顶点构成的连续序列Vp,Vi1,Vi2,。。。Vin-1
Vin,Vq,其中(Vp,Vi1),(Vi1,Vi2),….(Vin-1,Vin),(Vin,Vq)均为E(G)的边
5.路径长度
路径所含边得数目即为路径的长度
6.简单路径
指路径上除起点和终点可能相同外,其他顶点都不同。
7.回路和环
起点和终点为同一顶点的简单路径称为回路或环。
8.连通
若从V1到V2有路径可通,则称顶点V1和顶点V2是连通的。
9.连通图
图中任意两个结点都有路径相通,称为连通图a,b,c都是连通图
10.非连通图
只要有两个结点无路径相通,则称为非连通图。
11。度
指无向图中顶点V相关的边得个数
下面我们来看一下无向图创建的代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct node
{
int nVertex;
int nEdge;
int *pMatrix;
}Graph;
Graph *CreateGraph()
{
Graph *pGraph = NULL;
pGraph = (Graph*)malloc(sizeof(Graph));
int nV;
int nE;
printf("请输入顶点的个数 和 边数:\n");
scanf("%d%d",&nV,&nE);
pGraph->nVertex = nV;
pGraph->nEdge = nE;
//根据顶点个数 申请矩阵
pGraph->pMatrix = (int*)malloc(sizeof(int)*nV*nV);
memset(pGraph->pMatrix,0,sizeof(int)*nV*nV);
//根据边的条数 放置边
int i;
int v1,v2;
for(i = 1;i<=nE;i++)
{
printf("输入两个顶点确定一条边:\n");
scanf("%d%d",&v1,&v2);
if(v1>=1 && v1 <= nV && v2>=1&& v2<= nV && pGraph->pMatrix[(v1-1)*nV + (v2-1)] == 0)
{
pGraph->pMatrix[(v1-1)*nV + (v2-1)] = 1;
pGraph->pMatrix[(v2-1)*nV + (v1-1)] = 1;
}
else
{
i--;
}
}
return pGraph;
}
我大概解释一下图的创建的代码
首先我们需要申请一个矩阵,矩阵的大小为图中点的个数的平方,接下来就将用户输入的相应的顶点的位置变为1,因为矩阵实际上在内存里是一段连续的地址空间,所以对应位置需要我们计算出来,(x-1)*顶点个数+(y-1),我们还应该注意的是,如果在循环中没有进入if语句中,还要把i–,保证能边的个数
二,有向图
若图中边得两个顶点存在次序关系和方向性,即 <V1,V2>和 <V2,V1>代表两条不同的边则称为有向图。
图a所示的有向图就是由结点V={1,2,3,4}和边E={<1,2>,<1,3>,<3,4>,<4,1>}构成
1.完全图
若图中结点间n个有可能的边均存在,即Pn=n*(n-1)条边,称为完全图。
2。依附
边<V1,V2>依附于顶点V1于顶点V2
3。路径
图中从Vp到Vq的一条路径是指由顶点构成的连续序列Vp,Vi1,Vi2,。。。Vin-1
Vin,Vp,其中<Vp,Vi1>,<Vi1,Vi2>,….<Vin-1,Vin>,<Vin,Vq>均为E(G)上的有向边。
4. 强连通
如果每个相异成对顶点Vi和Vj都同时有从Vi到Vj和从Vj到Vi的路径,则称该有向图为强连通。
5.强连通分量
即有向图中的极大强连通子图
6.入度
顶点V的入度是指以V为终点有向边得个数
7.出度
顶点V的出度是指以V为起点有向边的个数。
小结:
1.无向图邻接矩阵的第i行或第i列非0元素的个数其实就是第i个顶点的度
2.有向图邻接矩阵的第i行非0元素的个数其实就是第i个顶点的出度,而第i列非0元素的个数是第i个顶点的入度,即第i个顶点的度是第i行和第i列非0元素个数之和
3.区分无向图和有向图的3个依据是:
(1) 图中各边无箭头的是无向图,边有箭头的是有向图
(2) 图中各边采用()来描述的是无向图,采用<>来描述的是有向图
(3) 图的邻接矩阵若以对角线为对称,若无特别声明就是无向图,不对称的则是有向图。
4.邻接矩阵和邻接表的区别在于:
(1)从空间存储来讲,邻接矩阵适用于边很多的图,邻接表则适用于边不多的图。
(2)从数据结构上讲,邻接矩阵对于图来说是唯一的,而邻接表并不唯一
图的遍历
一。图的深度优先遍历(DFS)
二。图的广度优先遍历(BFS)
深度优先搜索(DFS)
深度优先搜索在搜索过程中访问某个顶点后,需要递归地访问此顶点的所有未访问过的相邻顶点。
选择一个作为起始顶点,按照如下步骤遍历:
a. 选择起始顶点,申请一个标记数组,将标记数组初始化为0
b. 从该顶点的邻接顶点中选择一个,继续这个过程(即再寻找邻接结点的邻接结点),一直深入下去,直到一个顶点没有邻接结点了,并将标记数组相应位置变为1
c.继续如上操作,如果所有邻接结点往下都访问过了,
代码如下
void MyDFS(Graph *pGraph,int nBegin,int *pMark)
{
//打印 标记
printf("%d ",nBegin);
pMark[nBegin-1] = 1;
//遍历与当前顶点相关顶点
int i;
for(i = 0;i < pGraph->nVertex;i++)
{
//相关的 且 未被标记的
if(pGraph->pMatrix[(nBegin-1)*pGraph->nVertex + i] == 1 && pMark[i] == 0)
{
MyDFS(pGraph,i+1,pMark);
}
}
}
void DFS(Graph *pGraph,int nBegin)
{
if(pGraph == NULL )return;
//申请标记数组
int *pMark = NULL;
pMark = (int *)malloc(sizeof(int)*pGraph->nVertex);
memset(pMark,0,sizeof(int)*pGraph->nVertex);
MyDFS(pGraph,nBegin,pMark);
free(pMark);
pMark = NULL;
}
首先需要申请一个标记数组,用来标记打印过的顶点,在这里使用的是循环+递归的方法进行遍历,
广度优先搜索(BFS)
广度优先搜索在进一步遍历图中顶点之前,先访问当前顶点的所有邻接结点。
a .首先选择一个顶点作为起始结点,申请一个标记数组
b. 将起始结点放入队列中。
c. 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点进行标记,
d. 按照同样的方法处理队列中的下一个结点。
代码如下
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct node3
{
int nValue;
struct node3 *pNext;
}MyQueue;
typedef struct node4
{
int nCount;
MyQueue *pHead;
MyQueue *pTail;
}Queue;
void q_Init(Queue **pQueue)
{
*pQueue = (Queue*)malloc(sizeof(Queue));
(*pQueue)->pHead = NULL;
(*pQueue)->pTail = NULL;
(*pQueue)->nCount = 0;
}
void q_Push(Queue *pQueue,int nNum)
{
if(pQueue == NULL)return;
MyQueue *pTemp = NULL;
pTemp = (MyQueue*)malloc(sizeof(MyQueue));
pTemp->nValue = nNum;
pTemp->pNext = NULL;
if(pQueue->pHead == NULL)
{
pQueue->pHead = pTemp;
}
else
{
pQueue->pTail->pNext = pTemp;
}
pQueue->pTail = pTemp;
pQueue->nCount++;
}
int q_Pop(Queue *pQueue)
{
if(pQueue == NULL || pQueue->nCount == 0)return -1;
MyQueue *pDel = NULL;
int nNum;
pDel = pQueue->pHead;
nNum = pDel->nValue;
pQueue->pHead = pQueue->pHead->pNext;
free(pDel);
pDel = NULL;
pQueue->nCount--;
if(pQueue->nCount == 0)
{
pQueue->pTail = NULL;
}
return nNum;
}
int q_IsEmpty(Queue *pQueue)
{
if(pQueue == NULL)return -1;
return pQueue->nCount ? 0:1;
}
typedef struct node
{
int nVertex;
int nEdge;
int *pMatrix;
}Graph;
Graph *CreateGraph()
{
Graph *pGraph = NULL;
pGraph = (Graph*)malloc(sizeof(Graph));
int nV;
int nE;
printf("请输入顶点的个数 和 边数:\n");
scanf("%d%d",&nV,&nE);
pGraph->nVertex = nV;
pGraph->nEdge = nE;
//根据顶点个数 申请矩阵
pGraph->pMatrix = (int*)malloc(sizeof(int)*nV*nV);
memset(pGraph->pMatrix,0,sizeof(int)*nV*nV);
//根据边的条数 放置边
int i;
int v1,v2;
for(i = 1;i<=nE;i++)
{
printf("输入两个顶点确定一条边:\n");
scanf("%d%d",&v1,&v2);
if(v1>=1 && v1 <= nV && v2>=1&& v2<= nV && pGraph->pMatrix[(v1-1)*nV + (v2-1)] == 0)
{
pGraph->pMatrix[(v1-1)*nV + (v2-1)] = 1;
pGraph->pMatrix[(v2-1)*nV + (v1-1)] = 1;
}
else
{
i--;
}
}
return pGraph;
}
void BFS(Graph *pGraph ,int nBegin)
{
if(pGraph == NULL)return;
//辅助队列
Queue *pQueue = NULL;
q_Init(&pQueue);
//标记数组
int *pMark = NULL;
pMark = (int*)malloc(sizeof(int)*pGraph->nVertex);
memset(pMark,0,sizeof(int)*pGraph->nVertex);
// 入队 标记
q_Push(pQueue,nBegin);
pMark[nBegin-1] = 1;
while(!q_IsEmpty(pQueue))
{
//弹出
nBegin = q_Pop(pQueue);
printf("%d ",nBegin);
//将其未被标记的 有关联的顶点入队
int i;
for(i = 0;i<pGraph->nVertex;i++)
{
if(pGraph->pMatrix[(nBegin-1)*pGraph->nVertex + i] == 1 && pMark[i] == 0)
{
//入队 标记
q_Push(pQueue,i+1);
pMark[i] = 1;
}
}
}
}
int main()
{
Graph *pGraph = NULL;
pGraph = CreateGraph();
BFS(pGraph,1);
return 0;
}