主要讲解图的最常用的两种创建方式,邻接矩阵和邻接表,代码实现
0x01.邻接矩阵
含义:用两个数组来表示图,一个一维数组存储顶点的信息,一个二维数组(邻接矩阵)存储边或弧的信息。
对于普通的图(指没有权值的图):
如果 或 则 .
反之,.
求某个顶点的度,其实就是这个顶点 在矩阵中第 行的元素之和。
对于无向图,如:
对于有向图,如:
网图,是带权值的图。
如果 或 则 .(W指权值)
如果 ,则 .
如果两点不存在边,则为 ,一般用通常认为不可能达到的值,如 32727,65535表示。如:
结构:
#define MAXSIZE 100
#define INTMAX 32767//通常认为无法到达的int值,short型的边界值
typedef struct
{
char ver[MAXSIZE];//顶点信息域
int edge[MAXSIZE][MAXSIZE];
int numv;//顶点数
int nume;//边数
}Graph;
建立:
void CreateGraph(Graph *G)
{
int i, j, k, w;
printf("请输入图的顶点数和边数:\n");
scanf("%d %d", &G->numv, &G->nume);
while (getchar() != '\n');
printf("请输入节点信息:");
for (i = 0; i < G->numv; i++)//读入顶点信息
{
scanf("%c", &G->ver[i]);
}
for (i = 0; i < G->numv; i++)//初始化邻接矩阵
{
for (j = 0; j < G->numv; j++)
{
G->edge[i][j] = INTMAX;
}
}
for (k = 0; k < G->nume; k++)//读入边信息,最好加上i-i权值为0,不然其它都会是INTMAX
{
printf("请输入第 %d 条边(Vi,Vj)的下标i,j,和权值w:\n", k + 1);
while (getchar() != '\n');//必须清空缓冲区
scanf("%d %d %d",&i, &j, &w);
G->edge[i][j] = w;
G->edge[j][i] = G->edge[i][j];//无向图则矩阵对称,有向图不需要
}
}
时间复杂度:n个顶点e条边创建 ,初始化
0x02.邻接表
含义:对于边数较少的图采用邻接矩阵会造成浪费,于是考虑使用链式存储结构存储边信息,顶点用一维数组存储,数组中上还存储了指向第一条边的指针,边表用单链表存储,这样就将顶点和边连接起来了。
缺陷:邻接表默认都存储的是一个顶点的出度,即这个顶点的指向,难以确定入度信息。
逆邻接表:解决邻接表不能确定入度信息的缺陷,只是以顶点为弧头建立一个邻接表,但也无法确定出度信息。
图示:
结构:
#define MAXSIZE 100
//边表结构
typedef struct EdgeNode
{
int adjvex;//存储该顶点对应的数组下标
int weight;//存储权值
struct EdgeNode* next;//指针域,指向下一个邻接点
}EdgeNode;
//顶点结构
typedef struct VertexNode
{
char data;//存储顶点的信息
EdgeNode* firstedge;//代表边表的头指针
}VertexNode,AdjList[MAXSIZE];//同时创建了一个大小为MAXSIZE的顶点数组
//整个图结构
typedef struct
{
AdjList adjList;//此时的AdjList代表顶点表的指针类型,adjList就是创建的顶点表数组
int numv;//顶点数
int nume;//边数
}GraphAdjList;
建立:
void CreateALGraph(GraphAdjList* G)
{
int i, j,k,w;
EdgeNode* e;//建立边表的头结点
printf("请输入顶点数目和边数目,以空格隔开\n");
scanf("%d %d", &G->numv, &G->nume);
while (getchar() != '\n');//必须清空缓冲区
for (i = 0; i < G->numv; i++)//开始建立顶点表
{
scanf("%c", &G->adjList[i].data);//读取数据域
G->adjList[i].firstedge = NULL;//初始化边表头指针
}
for (k = 0; k < G->nume; k++)//开始建立边表
{
printf("请输入边左端下标 i ,右端下标 j,和权值 :\n");
while (getchar() != '\n');//必须清空缓冲区
scanf("%d %d %d", &i,&j,&w);
e = (EdgeNode*)malloc(sizeof(EdgeNode));//边表头结点申请内存
e->adjvex = j;//存储 i 指向的顶点的下标
e->weight = w;//存储该边的权值
e->next = G->adjList[i].firstedge;//将 e 指向顶点表中的边表头结点
G->adjList[i].firstedge = e;//将当前顶点表的头节点指向e,单链表的头插法
//以下代码对于无向图而言,对称的
e = (EdgeNode*)malloc(sizeof(EdgeNode));//e重新申请一块内存
e->adjvex = i;
e->weight = w;
e->next = G->adjList[j].firstedge;
G->adjList[j].firstedge = e;
}
}
邻接表的普通遍历:
void PrintfGraphAdjList(GraphAdjList G)//只传结构,不传指针,以防遍历时改变原来结构
{
for (int i = 0; i < G.numv; i++)
{
EdgeNode* p = G.adjList[i].firstedge;
printf("顶点 %c 的边有:\n", G.adjList[i].data);
while (p)//遍历链表
{
printf("顶点 %c 到顶点 %c ,权值为 %d \n", G.adjList[i].data, G.adjList[p->adjvex].data, p->weight);
p = p->next;
}
printf("\n");
}
}
时间复杂度:n个顶点e条边 O(n+e)
主函数测试:
int main()
{
GraphAdjList G;
CreateALGraph(&G);
PrintfGraphAdjList(G);
return 0;
}