图的定义
既然了解到图,想必都了解线性表和树形结构,他们都属于简单结构
线性表:一对一关系,每个元素对应前驱和后继。
树形结构:一对多关系,一个根结点对应多个孩子。
名称解释:
图:多对多关系,是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。图不能为空,而线性表和树可以为空表或空树。
边:无方向
弧:有方向
无向边:顶点与顶点之间的边没有方向,用(Vi,Vj)表示。
有向弧:顶点与顶点之间的边有方向,用<Vi,Vj>表示,Vi为弧尾,Vj为弧头
简单图:不存在顶点到其自身的边,其同一条边不重复出现
无向完全图:在无向图中,任意两个顶点间都存在边,含n个顶点的无向完全图有 n * (n-1) / 2 条边。
有向完全图:在有向图中,任意两个顶点间都存在方向互为相反的两条弧,含n个顶点的无向完全图有 n * (n-1) 条边。
稀疏图:边或弧数小于 n * logn,反之为稠密图
网:带权的图
连通图:无向图中任意两个顶点都是连通的。
连通分量:无向图中极大连通子图。
强连通图:在有向图中,对于每一个Vi到Vj都存在路径。
强连通分量:有向图中的极大强连通子图。
连通图生成树:一个极小的连通子图,含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。
邻接矩阵:
顶点用一维来记录;每个顶点的边用二维数组来记录。
无向图特点:
某个顶点的度等于在个顶点Vi在邻接矩阵中第 i 行(或第 j 列)的元素之和
求顶点Vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[ i ][ j ]为1就是邻接点
矩阵关于对角线对称。
有向图特点:
某一顶点的出度等于该顶点对应的行元素之和。
某一顶点的入度等于该顶点对应的列元素之和。
无向图的描述: 一个指针数组指向所有连接的结点,一个数据,一个连接数
有向图的描述: 一个指针数组指向所被连接的结点,一个数据,一个出度,一个入度
有向图/无向图建立代码:
本人原本分开写,但是觉得分开的话很难将有向无向的特点体现出来,因此结合在一起实现,大家如何想理解代码,建议先编译执行一下,了解实现的功能,在入手代码会比较清晰。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VEXNUM 100
typedef struct adjoin_Matrix_List{
char *vex_buf;//顶点数组
int **adjmat;//二维邻接矩阵
int vex_num;//顶点个数
int edge_num;//边数(本人不知道有何作用- -,看别的文章都有定义)
}adjMatlist, *p_adjMatlist;
typedef struct NODIR_GRAPH_VEX{
struct NODIR_GRAPH_VEX **next;//用作指针数组,指向所有连接的顶点
char vertex;//顶点数值
int num;//当前结点的连接数
}nodir_graph_vex, *p_nodir_graph_vex;
typedef struct DIR_GRAPH_VEX{
struct DIR_GRAPH_VEX **next;//用作指针数组,指向所有被连接顶点
char vertex;//顶点数值
int in_degree, out_degree; //出入度
}dir_graph_vex, *p_dir_graph_vex;
int init_adjMatrix(p_adjMatlist *list)
{
int i,j,flag;
*list = (p_adjMatlist )malloc(sizeof(adjMatlist));
printf("请输入顶点数:");
again1:
scanf("%d", &(*list)->vex_num);
if((*list)->vex_num > MAX_VEXNUM){
printf("不能大于100!请重新输入\n");
goto again1;
}
printf("请输入边数: ");
scanf("%d", &(*list)->edge_num);
(*list)->vex_buf = (char *)malloc(sizeof(char) * (*list)->vex_num);
(*list)->adjmat = (int **)malloc(sizeof(int *) * (*list)->vex_num);
for( i = 0; i<(*list)->vex_num; i++ ){
(*list)->adjmat[i] = (int *)malloc(sizeof(int) * (*list)->vex_num);
printf("请输入第%d个顶点的数值:",i+1);
scanf(" %c",&(*list)->vex_buf[i]);
}
printf("/t**************************************\n\t\t1:无向图\t2:有向图\n\t**************************************\n请输入数字:");
again2:
scanf(" %d", &flag);
if(flag != 1 && flag != 2){
printf("请输入1或2\n");
goto again2;
}
//无向图的邻接矩阵
if(flag == 1){
//对角线初始化
for( i = 0; i<(*list)->vex_num; i++ )
(*list)->adjmat[i][i] = 0;//正无穷∞
//边信息初始化
printf("从矩阵对角线右边第一个开始\n");
for( i = 0; i<(*list)->vex_num-1; i++ ){
printf("请输入第%d个顶点的边信息:",i+1);
for(j=i+1;j<(*list)->vex_num; j++){
scanf( "%d", &( (*list)->adjmat[i][j] ) );
(*list)->adjmat[j][i] = (*list)->adjmat[i][j];
}
printf("\n");
}
printf("\t\t已输入完毕!\n");
}
//有向图的邻接矩阵
else
{
//对角线初始化
for( i = 0; i<(*list)->vex_num; i++ )
(*list)->adjmat[i][i] = 9999;//正无穷∞
//边信息初始化
printf("不输入对角线信息!\n");
for( i = 0; i<(*list)->vex_num; i++ ){
printf("请输入第%d个顶点的边信息:",i+1);
for(j=0;j<(*list)->vex_num; j++){
if(i==j)
continue;
scanf( "%d", &( (*list)->adjmat[i][j] ) );
}
printf("\n");
}
printf("\t\t已输入完毕!\n");
}
//打印邻接矩阵
printf("**************************邻接矩阵表***********************\n\t\t");
for( i = 0; i<(*list)->vex_num+1; i++ )
printf("%c\t",(*list)->vex_buf[i]);
for( i = 0; i<(*list)->vex_num; i++ )
{
printf("\n\t%c", (*list)->vex_buf[i]);
for(j=0; j<(*list)->vex_num; j++)
printf("%8d", (*list)->adjmat[i][j]);
}
printf("\n");
return flag;
}
void creat_nodir_graph(p_adjMatlist list, p_nodir_graph_vex *graph)
{
int i,j;
p_nodir_graph_vex tmp;//打印用
*graph = (p_nodir_graph_vex)malloc(sizeof(nodir_graph_vex) * list->vex_num);//定义若干顶点
for( i=0; i<list->vex_num; i++ ){
(*graph)[i].next = (p_nodir_graph_vex *)malloc(sizeof(p_nodir_graph_vex));
(*graph)[i].vertex = list->vex_buf[i];
(*graph)[i].num = 0;
}
//遍历矩阵右上角
for(i=0; i<list->vex_num; i++){
for(j=i+1; j<list->vex_num; j++){
//有连接
if( list->adjmat[i][j] != 0 ){
//连接顶点申请内存
if( (*graph)[i].num == 0 )
*((*graph)[i].next) = (p_nodir_graph_vex)malloc(sizeof(nodir_graph_vex));
else
*((*graph)[i].next) = (p_nodir_graph_vex)realloc(*((*graph)[i].next), sizeof(nodir_graph_vex) * ( (*graph)[i].num+1 ) );
//被连接的顶点申请内存
if( (*graph)[j].num == 0 )
*((*graph)[j].next) = (p_nodir_graph_vex)malloc(sizeof(nodir_graph_vex));
else
*((*graph)[j].next) = (p_nodir_graph_vex)realloc(*((*graph)[j].next), sizeof(nodir_graph_vex) * ( (*graph)[j].num+1 ) );
//相互指向
(*((*graph)[i].next))[ (*graph)[i].num ] = (*graph)[j];
(*((*graph)[j].next))[ (*graph)[j].num ] = (*graph)[i];
//连接数自增
(*graph)[i].num++;
(*graph)[j].num++;
}
}
//打印每个结点的连接情况
tmp = *((*graph)[i].next);
printf("顶点%c连接情况:", (*graph)[i].vertex);
for( j=0; j<(*graph)[i].num; j++)
{
printf("%c\t", (*((*graph)[i].next))[j].vertex );
}
printf("\n");
}
}
void creat_dir_graph(p_adjMatlist list, p_dir_graph_vex *graph)
{
int i,j;
p_dir_graph_vex tmp;//打印用
*graph = (p_dir_graph_vex)malloc(sizeof(dir_graph_vex) * list->vex_num);//定义若干顶点
for( i=0; i<list->vex_num; i++ ){
(*graph)[i].next = (p_dir_graph_vex *)malloc(sizeof(p_dir_graph_vex));
(*graph)[i].vertex = list->vex_buf[i];
(*graph)[i].in_degree = (*graph)[i].out_degree = 0;
}
//遍历矩阵右上角
for(i=0; i<list->vex_num; i++){
for(j=0; j<list->vex_num; j++){
if( i==j )
continue;
//有连接
if( list->adjmat[i][j] != 0 ){
//连接顶点申请内存
if( (*graph)[i].out_degree == 0 )
*((*graph)[i].next) = (p_dir_graph_vex)malloc(sizeof(nodir_graph_vex));
else
*((*graph)[i].next) = (p_dir_graph_vex)realloc(*((*graph)[i].next), sizeof(nodir_graph_vex) * ( (*graph)[i].out_degree+1 ) );
//指向弧头
(*((*graph)[i].next))[ (*graph)[i].out_degree ] = (*graph)[j];
//连接数自增
(*graph)[i].out_degree++;
(*graph)[j].in_degree++;
}
}
//打印每个结点的连接情况
tmp = *((*graph)[i].next);
printf("顶点%c出度%d入度%d,连接情况:%c->", (*graph)[i].vertex, (*graph)[i].out_degree, (*graph)[i].in_degree, (*graph)[i].vertex );
for( j=0; j<(*graph)[i].out_degree; j++)
{
printf("%c->", (*((*graph)[i].next))[j].vertex );
}
printf("null\n");
}
}
int main()
{
int ret;
p_adjMatlist list = NULL;
ret = init_adjMatrix(&list);
if(ret == 1){
p_nodir_graph_vex graph;
creat_nodir_graph(list, &graph);
}
else{
p_dir_graph_vex graph;
creat_dir_graph(list, &graph);
}
return 0;
}
结果总结分析
- 无向图关于对角线对称,所以只需要初始化右上角或左下角的边信息,连接顶点时,双方同时建立连接,连接的顶点用二级指针(指针数组保存),连接数用于判断当前节点已连接的数量,重新申请内存存放于一级指针数组最后一个中,尾插法。
- 有向图需要初始化除对角线外的所有边信息,出度由该顶点对应的行元素有连接之和,入度对应该顶点对应的列元素有连接之和,连接时弧尾顶点出度自增,弧头顶点入度自增。
- 有向图中,遍历到某个顶点时,对于入度将无法确定,因为要等到所有顶点有遍历完毕才能确定入度情况(所有顶点都有可能指向当前顶点)。