图:G=(V, E)
V: 顶点(数据元素)的有穷非空集合
E: 边的有穷集合
有向图和无向图:前者边有方向,后者边无方向
稀疏图:有很少的边或弧的图 (e < nlogn)
稠密图:有较多边或弧
网:边/弧带权的图
邻接:有边/弧相连的两个顶点之间的关系 ()无向图,无先后关系;<>有向图,有先后关系
关联(依附):边/弧与顶点之间的关系。
顶点的度:与该顶点相关联的边的数目,记为TD(v)
当有向图中仅有一个顶点的入度为0,其余顶点的入度均为1(出度任意),则该图为树形,称为有向树
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和 (有权值按权值算)
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径
连通图(强连通图):在无(有)向图G = (V, {E})中,若对任意两个顶点v, u都存在从v到u的路径,则称G是连通图(强连通图)。无向图称为连通图,有向图称为强连通图
权与网
子图
设有两个图G = (V, {E}), G1 = (V1, {E1}), 若V1∈V,E1∈E,则称G1是G的子图 (即顶点和边都是图的子集)
连通分量 (强连通分量)
单顶点就是强连通分量
你和任何一个陌生人之间所间隔的人不会超过五个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”根据这个理论,你和世界上的任何一个人之间只隔着五个人,不管对方在哪个国家,属哪类人种,是哪种肤色。
将小世界理论的人际关系网络抽象成一个无向图G,用图G中的一个顶点表示一个人,两个人认识与否用代表这两个人的顶点之间是否有一条边来表示。从任一顶点出发用广度优先对图进行遍历,统计所有长度不超过7的顶点
图的逻辑结构:多对多
邻接矩阵:借助二维数组来表示图的元素间的关系
链式存储结构:由于图中顶点多对多的特性,很难确定需要多少个指针域,因此有如下几个常用的链式存储方式:邻接表,邻接多重表,十字链表
建立一个顶点表(记录各个顶点信息) 和一个邻接矩阵表(表示各个顶点之间的关系)
1.特点:无向图的邻接矩阵是对称矩阵,且对角线上元素为0,因为当有一个顶点到另一个顶点存在边时,另一个顶点到该顶点也一定存在一条边
2.统计某一个顶点的度是多少只需遍历该顶点对应的数组,计算1的次数即可,即顶点 i 的度 = 第i行(列)中 1 的个数
3.特别的:无向完全图的邻接矩阵中,对角元素为0,其余均为1
有向图存在方向,因此只有当存在当前顶点发出的弧时,才记为1 (和无向图的邻接矩阵一样,自身与自身无弧)
在有向图的邻接矩阵中:
1.有向图的邻接矩阵的特点:对角线元素为0,但不一定是对称矩阵
2.对有向图的邻接矩阵而言:
3.特别的:有向完全图的对角线元素全0,取余元素全1
网即有权图
如果存在边 / 弧属于VR图,则邻接矩阵记录该条边的权值Wij;否则记录INF
两个数组 :顶点表和邻接矩阵
//最大顶点数
#define MAX_VEX_NUM 100
//假的无穷大,填补邻接矩阵中没有邻接关系的点
#define MAX_INT 2147483647
typedef struct Graph
{
//顶点表
顶点数据类型 vexs[MAX_VEX_NUM];
//邻接矩阵表
边的类型 arcs[MAX_VEX_NUM][MAX_VEN_NUM];
//顶点数和边数
int vexNum, arcNum;
}AMGraph;
构造
void creatGraph(AMGraph* graph)
{
printf("请输入顶点数和边数:");
//1.顶点数和边数
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点数据
for (int i = 0; i < graph->vexNum; i++)
{
printf("请输入第%d个顶点数据:", i + 1);
scanf("%c", &(graph->vexs[i]));
setbuf(stdin, NULL);
putchar('\n');
}
//3.初始化边, n个顶点就有
for (int i = 0; i < graph->vexNum; i++)
{
for (int j = 0; j < graph->vexNum; j++)
{
if(i!=j)
graph->arcs[i][j] = MAX_INT;
else
graph->arcs[i][j] = 0;
}
}
//4.构造邻接矩阵
for (int i = 0; i < graph->arcNum; i++)
{
char v1, v2;
int weight;
printf("请输入第%d条边依附的顶点及权值:", i+1);
scanf("%c %c %d", &v1, &v2, &weight);
setbuf(stdin, NULL);
putchar('\n');
//找到边依附的两个顶点,记录下标,就是在邻接矩阵中的位置
int i = findVex(graph, v1);
int j = findVex(graph, v2);
//如果没查找到顶点或该顶点已经有依附的边
if (i == -1 || j == -1 || graph->arcs[i][j] != MAX_INT)
{
printf("不存在该顶点或该顶点已有依附的边\n");
exit(-1);
}
//对称赋值
graph->arcs[i][j] = weight;
graph->arcs[j][i] = weight;
}
}
查找顶点是否存在
int findVex(AMGraph* graph, char vex)
{
for (int i = 0; i < graph->vexNum; i++)
{
if (vex == graph->vexs[i])
{
return i;
}
}
return -1;
}
无向图:和无向网区别就是无向图的边不存在权值,因此初始化邻接矩阵时边均为0,构造邻接矩阵时,存在的边为1
有向网:有向网的邻接矩阵不一定时对称矩阵,因此仅需为graph->arcs[i][j]赋值,而不需要在对称元素上赋值
邻接矩阵不便于增加和删除顶点;当边数较少时比较浪费空间
边:用链表存储关联了同一顶点的所有边;每个结点有两个数据adjvex为邻接顶点,nextarc为下一条边;eg:代表v1有两条边,分别邻接的是在顶点数组中下标为3的顶点和下标为1的顶点
当为无向网时,可以给表结点增加数据域以存放权值
无向图邻接表的特点:
实现步骤:
//边结构
typedef struct GraphNode
{
int head;
struct GraphNode* nextArc;
}gNode;
//顶点表
typedef struct Head
{
char data;
gNode* firstArc;
}head;
//图的基本结构
typedef struct ALGraph
{
head gHead[MAX_VEX];
int vexNum, arcNum; //顶点数和边数
}ALgraph;
void creatGraph(ALgraph* graph)
{
printf("请输入顶点数和边数: ");
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点赋值
for (int i = 0; i < graph->vexNum; i++)
{
char c;
printf("请输入第%d个顶点的数据:", i+1);
scanf("%c", &c);
graph->gHead[i].data = c;
graph->gHead[i].firstArc = NULL;
setbuf(stdin, NULL);
putchar('\n');
}
//边结点链接
for (int i = 0; i < graph->arcNum; i++)
{
char v1, v2;
printf("请输入第%d条边依附的结点:", i + 1);
scanf("%c %c", &v1, &v2);
setbuf(stdin, NULL);
int i = indexOfVex(graph, v1);
int j = indexOfVex(graph, v2);
//生成两个结点,因为一条边会关联两个顶点,两个顶点都会链接上这条边
//只是不同顶点对应的边结点的head值不一样
gNode* g = (gNode*)malloc(sizeof(gNode));
g->head = j;
g->nextArc = NULL;
gNode* g2 = (gNode*)malloc(sizeof(gNode));
g2->head = i;
g2->nextArc = NULL;
/*头插,在对应的关联边链表中进行插入
如果用尾插法,需要记录上一次的尾结点,但上一次的尾指针所指向的边结点
不一定就在当次循环中找到的顶点的边链表当中
例如:1. a b a-->1 b-->0 pre-->1
2. b c b-->0 b由于在上次时已经有长子了
而pre此时还是指向a的长子1,pre-->next = g的话,就会变成 a-->1-->2 b-->0,这显然不是我们想要的*/
//链接到对应顶点,存终点下标的链接到起点顶点,存起点下标的链接到终点顶点
g->nextArc = graph->gHead[i].firstArc;
graph->gHead[i].firstArc = g;
g2->nextArc = graph->gHead[j].firstArc;
graph->gHead[j].firstArc = g2;
}
}
有向图的邻接表创建与无向图的邻接表创建区别仅在于:在构造有向图时,由于弧是有方向的,因此只需构造一条出度边依附在出度点上即可,该边的邻接点域值head存储入度点的下标;而在无向图中每条边需要保存两次;因此邻接表表示的无向图空间复杂度记为O(n+2e),邻接表表示的有向图空间复杂度为O(n+e)
void creatGraph(ALgraph* graph)
{
printf("请输入顶点数和边数: ");
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点赋值
for (int i = 0; i < graph->vexNum; i++)
{
char c;
printf("请输入第%d个顶点的数据:", i+1);
scanf("%c", &c);
graph->gHead[i].data = c;
graph->gHead[i].firstArc = NULL;
setbuf(stdin, NULL);
putchar