图的存储结构
邻接矩阵
typedef struct graph{
int** connection;//邻接矩阵(实质是二维数组)
int numNodes;//矩阵阶数
}*graphPtr;
优点
- 便于判断两个顶点之间是否有边,即根据A[i][j]=0或1来判断。
- 便于计算各个顶点的度。对于无向图,邻接矩阵第i行元素之和就是顶点i的度;对于有向图,第i行元素之和就是顶点i的出度,第1列元素之和就是顶点i的入度。
缺点
- 不便于增加和删除顶点。
- 不便于统计边的数目,需要扫描邻接矩阵所有元素才能统计完毕,时间复杂度为O(n^2)。
- 空间复杂度高。如果是有向图,n个顶点需要n^2个单元存储边。如果是无向图,因其邻接矩阵是对称的,所以对规模较大的邻接矩阵可以采用压缩存储的方法,仅存储下三角(或上三角)的元素,这样需要n(n- 1)/2个单元即可。但无论以何种方式存储,邻接矩阵表示法的空间复杂度均为O(n^2),这对于稀疏图而言尤其浪费空间。
邻接表
邻接表:实质上是邻接矩阵的压缩存储
typedef struct AdjacencyNode{//邻接结点边表
int subscript;//邻接点域
struct AdjacencyNode* next;//链域
}AdjacencyNode,*AdjacentNodePtr;
typedef struct AdjacencyList{//邻接表顶点表
int numNodes;//结点数
struct AdjacencyNode* header;//一维数组
}AdjacencyList,*AdjacencyListPtr;
优点
- 便于增加和删除顶点。
- 便于统计边的数目,按顶点表顺序扫描所有边表可得到边的数目,时间复杂度为O(n+ e)。
- 空间效率高。对于一个具有n个顶点e条边的图G,若G是无向图,则在其邻接表表示中有n个顶点表结点和2e个边表结点;若G是有向图,则在它的邻接表表示或逆邻接表表示中均有n个顶点表结点和e个边表结点。因此,邻接表或逆邻接表表示的空间复杂度为O(n+e),适合表示稀疏图。对于稠密图,考虑到邻接表中要附加链域,因此常采取邻接矩阵表示法。
缺点
- 不便于判断顶点之间是否有边,要判定vi和yj之间是否有边,就需扫描第i个边表,最坏情况下要耗费O(n)时间。
- 不便于计算有向图各个顶点的度。对于无向图,在邻接表表示中顶点vi的度是第i个边表中的结点个数。在有向图的邻接表中,第i个边表上的结点个数是顶点vi的出度,但求vi的入度较困难,需遍历各顶点的边表。若有向图采用逆邻接表表示,则与邻接表表示相反,求顶点的入度容易,而求顶点的出度较难。
图的遍历
图的两种遍历:深度优先搜索(DFS)和广度优先搜索(WFS),两种遍历算法的实现都需要使用访问标志数组visitedPtr[i]来标记当前结点i是否已经被访问
深度优先搜索(DFS)
DFS:使用了访问标志数组和递归
void DFS(graphPtr paraGraphPtr, int paraNode/*结点标号*/){
int i;
visitedPtr[paraNode]=1;//将paraNode标记为1,表示该结点已经访问过
printf("%d\t",paraNode);
for(i=0;i<paraGraphPtr->numNodes;i++){
if(!visitedPtr[i]){//如果有未被访问的结点
if(paraGraphPtr->connection[paraNode][i]){//如果有paraNode的邻接点i(即[paraNode][i]为1)
DFS(paraGraphPtr,i);//对paraNode的尚未被访问的邻接点i递归调用DFS搜索
}//of if
}//of if
}//of for
}//of DFS
广度优先搜索(WFS)
WFS: 使用了访问标志数组和队列
void WFS(graphPtr paraGraphPtr, int paraStart/**/){
//初始化辅助队列
//printf("Begin to initialize queue.\r\n");
QueuePtr tempQueue=initQueue();
enqueue(tempQueue,paraStart);
//printf("paraStart enqueued.\r\n");
visitedPtr[paraStart]=1;
printf("%d\t",paraStart);
int i,j,tempNode;
i=0;//i计数
while(!isQueueEmpty(tempQueue)){//队列不为空
tempNode=dequeue(tempQueue);//tempNode接收出队元素
//printf("tempNode=%d\r\n",tempNode);
visitedPtr[tempNode]=1;//标记tempNode已经被访问
i++;//计数
for(j=0;j<paraGraphPtr->numNodes;j++){//遍历图寻找没有被访问且与tempNode有边的结点
if( visitedPtr[j]==0 && paraGraphPtr->connection[tempNode][j]==1){
printf("%d\t",j);
visitedPtr[j]=1;
enqueue(tempQueue,j);
}
}
//printf("i=%d",i);
}
//printf("i=%d",i);
}
使用邻接矩阵的两种图遍历算法的完整代码:
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#define QUEUE_SIZE 10//队列空间大小
int* visitedPtr;//记录结点是否已被访问
typedef struct graphNodeQueue{
int* node;
int front;
int rear;
}graphNodeQueue,*QueuePtr;
/*初始化队列*/
QueuePtr initQueue(){
QueuePtr resultPtr=(QueuePtr)malloc(sizeof(struct graphNodeQueue));
resultPtr->node=(int*)malloc(QUEUE_SIZE*sizeof(int));
resultPtr->front=0;
resultPtr->rear=1;
return resultPtr;
}
/*判断队列是否为空*/
bool isQueueEmpty(QueuePtr paraQueue){
if((paraQueue->front+1)%QUEUE_SIZE==paraQueue->rear){
return true;
}
return false;
}
/*结点入队*/
void enqueue(QueuePtr paraQueue,int paraNode){
//判断队满
if((paraQueue->rear+1)%QUEUE_SIZE==paraQueue->front%QUEUE_SIZE){//注意paraQueue->front也要%QUEUE_SIZE
printf("Error,tring to enqueue %d,but queue full.\r\n");
return;
}
//让结点入队(入尾指针处)
paraQueue->node[paraQueue->rear]=paraNode;
//更新队列尾指针
paraQueue->rear= (paraQueue->rear+1)%QUEUE_SIZE;
}
/*结点出队*/
int dequeue(QueuePtr paraQueue){
//判断队空
if(isQueueEmpty(paraQueue)){
printf("Error,empty queue.\r\n");
return -1;
}
paraQueue->front=(paraQueue->front+1)%QUEUE_SIZE;
//printf("dequeue %d ends.\r\n", paraQueue->node[paraQueue->front]);
return paraQueue->node[paraQueue->front];
}
/*图的存储表示*/
typedef struct graph{
int** connection;//邻接矩阵
int numNodes;//矩阵阶数
}*graphPtr;
/*初始化图*/
graphPtr initGraph(int paraSize,int** paraData){
int i,j;
graphPtr resultPtr=(graphPtr)malloc(sizeof(struct graph));
resultPtr->connection=(int**)malloc(paraSize*sizeof(int*));
resultPtr->numNodes=paraSize;
for(i=0;i<paraSize;i++){
resultPtr->connection[i]=(int*)malloc(paraSize*sizeof(int));
for(j=0;j<paraSize;j++){
//printf("i=%d,j=%d.\r\n",i,j);
resultPtr->connection[i][j]=paraData[i][j];
//printf("i=%d,j=%d,connection[i][j]=%d.\r\n",i,j,resultPtr->connection[i][j]);
}
}
return resultPtr;
}
/*初始化visitedPtr数组为0*/
void initVisitedPtr(graphPtr paraGraphPtr){
visitedPtr=(int*)malloc(paraGraphPtr->numNodes*sizeof(int));
int i;
for(i=0;i<paraGraphPtr->numNodes;i++){
visitedPtr[i]=0;
//printf("visitedPtr[%d]=%d\r\n",i,visitedPtr[i]);
}
}
/*深度优先搜索*/
void DFS(graphPtr paraGraphPtr, int paraNode/*结点标号*/){
int i;
visitedPtr[paraNode]=1;//将paraNode标记为1,表示该结点已经访问过
printf("%d\t",paraNode);
for(i=0;i<paraGraphPtr->numNodes;i++){
if(!visitedPtr[i]){//如果有未被访问的结点
if(paraGraphPtr->connection[paraNode][i]){//如果有paraNode的邻接点i(即[paraNode][i]为1)
DFS(paraGraphPtr,i);//递归调用DFS搜索邻接点i
}
}
}
}
/*广度优先搜索*/
void WFS(graphPtr paraGraphPtr, int paraStart/**/){
//初始化辅助队列
//printf("Begin to initialize queue.\r\n");
QueuePtr tempQueue=initQueue();
enqueue(tempQueue,paraStart);
//printf("paraStart enqueued.\r\n");
visitedPtr[paraStart]=1;
printf("%d\t",paraStart);
int i,j,tempNode;
i=0;//i计数
while(!isQueueEmpty(tempQueue)){//队列不为空
tempNode=dequeue(tempQueue);//tempNode接收出队元素
//printf("tempNode=%d\r\n",tempNode);
visitedPtr[tempNode]=1;//标记tempNode已经被访问
i++;//计数
for(j=0;j<paraGraphPtr->numNodes;j++){//遍历图寻找没有被访问且与tempNode有边的结点
if( visitedPtr[j]==0 && paraGraphPtr->connection[tempNode][j]==1){
printf("%d\t",j);
visitedPtr[j]=1;
enqueue(tempQueue,j);
}
}
//printf("i=%d",i);
}
//printf("i=%d",i);
}
/*测试函数*/
void graphTranverseTest(){
//初始化邻接矩阵
int myGraph[5][5] = {
{0, 1, 0, 1, 0},
{1, 0, 1, 0, 1},
{0, 1, 0, 1, 1},
{1, 0, 1, 0, 0},
{0, 1, 1, 0, 0}};
printf("Preparing data\r\n");
int i, j;
int** tempPtr= (int**)malloc(5*sizeof(int*));
for(i=0;i<5;i++){
tempPtr[i]=(int*)malloc(5*sizeof(int));
for(j=0;j<5;j++){
tempPtr[i][j]=myGraph[i][j];
}
}
printf("Data ready.\r\n");
//初始化图
graphPtr tempGraph=initGraph(5,tempPtr);
printf("Graph initialized.\r\n");
//DFS
printf("DFS:\r\n");
initVisitedPtr(tempGraph);//先将VisitedPtr数组初始化为0
DFS(tempGraph,4);
//WFS
printf("\r\nWFS:\r\n");
initVisitedPtr(tempGraph);//将VisitedPtr数组初始化为0
WFS(tempGraph,4);
}
int main(){
graphTranverseTest();
return 0;
}
运行结果:
使用邻接表的广度优先遍历算法:
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#define QUEUE_SIZE 10//队列空间大小
int* visitedPtr;//记录结点是否已被访问
typedef struct graphNodeQueue{
int* node;
int front;
int rear;
}graphNodeQueue,*QueuePtr;
/*初始化队列*/
QueuePtr initQueue(){
QueuePtr resultPtr=(QueuePtr)malloc(sizeof(struct graphNodeQueue));
resultPtr->node=(int*)malloc(QUEUE_SIZE*sizeof(int));
resultPtr->front=0;
resultPtr->rear=1;
return resultPtr;
}
/*判断队列是否为空*/
bool isQueueEmpty(QueuePtr paraQueue){
if((paraQueue->front+1)%QUEUE_SIZE==paraQueue->rear){
return true;
}
return false;
}
/*结点入队*/
void enqueue(QueuePtr paraQueue,int paraNode){
//判断队满
if((paraQueue->rear+1)%QUEUE_SIZE==paraQueue->front%QUEUE_SIZE){//注意paraQueue->front也要%QUEUE_SIZE
printf("Error,tring to enqueue %d,but queue full.\r\n");
return;
}
//让结点入队(入尾指针处)
paraQueue->node[paraQueue->rear]=paraNode;
//更新队列尾指针
paraQueue->rear= (paraQueue->rear+1)%QUEUE_SIZE;
}
/*结点出队*/
int dequeue(QueuePtr paraQueue){
//判断队空
if(isQueueEmpty(paraQueue)){
printf("Error,empty queue.\r\n");
return -1;
}
paraQueue->front=(paraQueue->front+1)%QUEUE_SIZE;
//printf("dequeue %d ends.\r\n", paraQueue->node[paraQueue->front]);
return paraQueue->node[paraQueue->front];
}
/*图的存储表示*/
typedef struct graph{
int** connection;//邻接矩阵
int numNodes;//矩阵阶数
}*graphPtr;
/*初始化图*/
graphPtr initGraph(int paraSize,int** paraData){
int i,j;
graphPtr resultPtr=(graphPtr)malloc(sizeof(struct graph));
resultPtr->connection=(int**)malloc(paraSize*sizeof(int*));
resultPtr->numNodes=paraSize;
for(i=0;i<paraSize;i++){
resultPtr->connection[i]=(int*)malloc(paraSize*sizeof(int));
for(j=0;j<paraSize;j++){
resultPtr->connection[i][j]=paraData[i][j];
//printf("i=%d,j=%d,connection[i][j]=%d.\r\n",i,j,resultPtr->connection[i][j]);
}
}
return resultPtr;
}
/*图的存储结构*/
typedef struct AdjacencyNode{//邻接结点边表
int subscript;//邻接点域
struct AdjacencyNode* next;//链域
}AdjacencyNode,*AdjacentNodePtr;
typedef struct AdjacencyList{//邻接表顶点表
int numNodes;//结点数
struct AdjacencyNode* header;//一维数组
}AdjacencyList,*AdjacencyListPtr;
/*将图转换为邻接表*/
AdjacencyListPtr graphToAdjacentList(graphPtr paraGraph){
//为邻接表分配空间
AdjacencyListPtr tempAdjList=(AdjacencyListPtr)malloc(sizeof(struct AdjacencyList));
tempAdjList->numNodes=paraGraph->numNodes;
tempAdjList->header=(AdjacentNodePtr)malloc(tempAdjList->numNodes*sizeof(struct AdjacencyNode));
//创建邻接表
int i,j;
AdjacentNodePtr p, q;//p用于指示结点,q用于创建结点
for(i=0;i<paraGraph->numNodes;i++){
//初始化边表
p=&(tempAdjList->header[i]);//p指向边表的头结点
p->subscript=-1;
p->next=NULL;
for(j=0;j<paraGraph->numNodes;j++){
if(paraGraph->connection[i][j]==1){
//创建新结点
q=(AdjacentNodePtr)malloc(sizeof(struct AdjacencyNode));
q->subscript=j;
q->next=NULL;
//链接
p->next=q;
p=q;//更新指针p,让它指向最后一个结点,便于下一次链接新结点
}//Of if
} //Of for
} //Of for
return tempAdjList;
}
/*打印邻接表*/
void printAdjList(AdjacencyListPtr paraAdjList){
printf("The graph is:\r\n");
int i;
AdjacentNodePtr p;
for(i=0;i<paraAdjList->numNodes;i++){
p=paraAdjList->header[i].next;
while(p!=NULL){
printf("%d\t",p->subscript);
p=p->next;
}//Of while
printf("\r\n");
}//Of for
}
/*广度优先搜索邻接表*/
void WFS(AdjacencyListPtr paraAdjList,int paraStart){
//初始化访问标志数组
int i;
visitedPtr=(int*)malloc(paraAdjList->numNodes*sizeof(int));
for(i=0;i<paraAdjList->numNodes;i++){
visitedPtr[i]=0;
}
//队列
QueuePtr tempQueue=initQueue();
printf("%d\t",paraStart);
visitedPtr[paraStart]=1;
enqueue(tempQueue,paraStart);//让已访问的元素入队
int tempNode;//接收出队元素
AdjacentNodePtr p;//指向每一个边表的头结点 ,遍历边表
while(!isQueueEmpty(tempQueue)){//队列不为空
tempNode=dequeue(tempQueue);//出队元素
for(p=&(paraAdjList->header[tempNode]);p!=NULL;p=p->next){
if(visitedPtr[p->subscript]==0){
printf("%d\t",p->subscript);
visitedPtr[p->subscript]=1;
enqueue(tempQueue,p->subscript);
}//Of if
}//Of for
}//Of while
}
void WFSofAdjacencyListTest(){
int i, j;
int myGraph[5][5] = {
{0, 1, 0, 1, 0},
{1, 0, 1, 0, 1},
{0, 1, 0, 1, 1},
{1, 0, 1, 0, 0},
{0, 1, 1, 0, 0}};
int** tempPtr;
printf("Preparing data\r\n");
tempPtr = (int**)malloc(5 * sizeof(int*));
for (i = 0; i < 5; i ++) {
tempPtr[i] = (int*)malloc(5 * sizeof(int));
for (j = 0; j < 5; j ++) {
tempPtr[i][j] = myGraph[i][j];
//printf("i = %d, j = %d, %d\r\n", i, j, tempPtr[i][j]);
}//Of for j
}//Of for i
printf("Data ready\r\n");
graphPtr tempGraphPtr = initGraph(5, tempPtr);
AdjacencyListPtr tempListPtr = graphToAdjacentList(tempGraphPtr);
printAdjList(tempListPtr);
printf("The WFS:\r\n");
WFS(tempListPtr, 4);
}
int main(){
WFSofAdjacencyListTest();
return 0;
}
运行结果: