目录
一、图的定义
- 图G由顶点集V和边集E组成,记为G = (V, E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)集合。若V = { v1,v2,...,vn },则用 | V | 表示图G中顶点的个数,也称图G的阶,E = { (u,v) | u属于V,v属于V },用 | E | 表示图G中的边的条数。
- 注意:线性表可以是空表,树可以是空树,但图不可以是空,即V一定是非空集。
- 无向图:在图G中,若E是无向边(简称边)的有限集合时,则称为无向图。
- 补充:边是顶点的无序对,记为(v, w)或(w, v),因为(v, w) = (w, v),其中v, w是顶点。可以说顶点w和顶点v互为邻接点。边(v, w)依附于顶点w和顶点v,或者说边(v, w)和顶点v, w相关联。
例如图1中:
G1 = (V2, E2)
V1 = { A,B,C,D,E }
E1{ (A,B),(B,D),(B,E),(C,D),(C,E),(D,E) }
- 有向图:在图G中,若E是有向边(也称弧)的有限集合时,则称为有向图。
- 补充:弧是顶点的有序对,记为<v, w>,其中v, w是顶点,v称为弧尾,w称为弧头,<v, w>称为顶点v到顶点w的弧,也称v邻接到w,或w邻接自v。
例如图2中:
G2 = (V1, E1)
V2 = { A,B,C,D,E }
E2= { <A,B>,<A,C>,<A,D>,<A,E>,<B,A>,<B,C>,<B,E>,<C,D> }
- 简单图:1.不存在重复边。2.不存在顶点到自身的边。
- 多重图:1.图G中某两个结点之间的边数多于1条,又允许顶点通过同一条边和自己关联。
顶点的度、入度、出度
- 对于无向图:顶点v的度是指依附于该顶点的边的条数,记为TD(v)
- 对于有向图:入度是以顶点v为终点的有向边的数目,记为ID(v) 出度是以顶点v为起点的有向边的数目,记为OD(v) 顶点v的度等于其入度和出度之和,即TD(v)=ID(v)+OD(v)
- 路径:在一个图G=(V,E)中,从顶点i到顶点j的一条路径是一个顶点序列(i,i1,i2,...,j)
- 路径长度:是指一条路径上经过的边的数目
- 回路:第一个顶点和最后一个顶点相同的路径称为回路或环
- 简单路径:在路径序列中,顶点不重复出现的路径(即不含回路的路径)
- 简单回路:除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路
- 点到点的距离:从顶点u出发到顶点v的最短路径若存在,则此路径的长度称为从u到v的距离。若从u到v根本不存在路径,则记该距离为无穷。
子图:
- 设有两个图G=(V,E)和G1=(V1,E1),若V1是V的子集,即V1属于V,且E1是E的子集,即E1属于E,则称G1是G的子图。
- 生成子图:若有满足V(G1)=V(G),则称其为G的生成子图。
连通图(是对无向图而言):
- 1.在无向图G中,若从顶点i到顶点j有路径,则称顶点i和顶点j是连通的。
- 2.如果对于图中任意两个顶点都连通,则称G是连通图,否则称为非连通图。 如果G是连通图,则最少有n-1条边; 如果G是非连通图,则最多可能有;(比如一共有5个顶点,只需要拿出4个顶点并且从中任 意选取两个顶点。)
- 3.连通分量:无向图中的极大连通子图称为连通分量。 显然,连通图只有一个连通分量,就是它本身,而非连通图有多个连通分量
强连通图(是对有向图而言):
- 1.若图中任何一对顶点都是强连通的,则称此图为强连通图 注意:如果G是强连通图,则最少有n条边(形成回路)
- 2.强连通分量:有向图中的极大强连通子图。 显然,强连通图只有一个强连通分量,即本身。非强连通图有多个强连通分量
生成树:
- 连通图的生成树是包含图中全部顶点的一个极小连通子图(极小是指在这个图里面的边的数量尽可能少)
- 若图中顶点数为n,则它的生成树含有n - 1条边。对于生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成一个回路。
生成森林:
在非连通图中,连通分量的生成树构成了非连通图的生成森林。
边的权、带权图/网、带权路径长度
- 边的权:在一个图中,每条边都可以标上具有某种含义的数值,给数值称为该边的权值。
- 带权的图/网:边上带权值的图称为带权图,也称网。
- 带权路径长度:当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度
几种特殊形态的图
- 完全图:若图中每两个顶点之间都存在着一条边,则称该图为完全图。
- 无向完全图:无向图中任意两个顶点之间都存在边。
- 有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧。
- 注意:完全有向图有n(n - 1)条边;完全无向图有n(n - 1) / 2条边。
- 稀疏图:边数很少的图
- 稠密图:当一个图接近完全图时,称为稠密图。
二、图的存储结构
- 基本的分为邻接矩阵和邻接表
邻接矩阵
- 邻接矩阵:是一种表示顶点之间邻接关系的矩阵
- 邻接矩阵是图的一种顺序存储结构,从邻接矩阵的行数或列数可知顶点数。
设G=(V,E)是具有n个顶点的不带权图,则G的邻接矩阵的具有如下定义的n阶方阵A
A[i][j]=1,表示有边(i,j)或者<i,j>
A[i][j]=0,表示没有边(i,j)(0<=i,j<=n-1)
A[i][i]=0,表示顶点i到自身没有边
-
图的邻接矩阵存储结构的类型声明:
#define MaxVertexNum 100//顶点数目的最大值
#define INFINITY 100000//宏定义常量“无穷”
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//带权图中边上权值的数据类型
typedef struct
{
VertexType vexs[MaxVertexNum];//顶点表(存放顶点)
EdgeType edges[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表(存放任意两个顶点之间的距离)
int n, e;//图的当前顶点数和边数/弧数
}MGraph;
邻接矩阵的性能分析
- 空间复杂度:只和顶点数相关,和实际的边数无关
- 适合用于存储稠密图
- 无向图的邻接矩阵是对称矩阵,可以压缩存储(值存储上三角区/下三角区)
完整代码
- MGraph.h
#pragma once
#include<iostream>
#include<stdbool.h>
#include<stdio.h>
using namespace std;
//输入流的头文件
#include <cstring>
#include <io.h>
#include <fstream>
#define txtRows 5 //txt行数
#define txtCols 5 //txt列数
#define MaxVertexNum 100//顶点数目的最大值
//#define INFINITY 100000//宏定义常量“无穷”
#define MAXV 100
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//带权图中边上权值的数据类型
typedef struct
{
VertexType vexs[MaxVertexNum];//顶点表(存放顶点)
EdgeType edges[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表(存放任意两个顶点之间的距离)
int n, e;//图的当前顶点数和边数/弧数
}MGraph;
void CreatMat(MGraph& G, int A[][MAXV], int n);//由数组A[n][n]生成邻接矩阵G
//生成图的邻接矩阵方法1
void CreatMGraph(MGraph& G);//生成图的邻接矩阵方法2
void DisMGraph(MGraph& G);//遍历打印
- MGraph1.cpp
#include"MGraph.h"
void CreatMat(MGraph& G, int A[][MAXV], int n)//由数组A[n][n]生成邻接矩阵G
{
G.n = n;
G.e = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
G.edges[i][j] = A[i][j];
if (A[i][j] != 0 && A[i][j]!=INFINITY)
{
G.e++;//边数加1
}
}
}
}
void CreatMGraph(MGraph& G)//生成图的邻接矩阵方法2
{
int i, j, k;
cout << "请依次顶点数和边数:";
cin >> G.n >> G.e;
cout << "请输入顶点信息:";
for (int i = 0; i < G.n; i++)
{
cin >> G.vexs[i];
}
for (int i = 0; i < G.n; i++)//初始化邻接矩阵
{
for (int j = 0; j < G.n; j++)
{
G.edges[i][j] = 0;
}
}
cout << "请输入每条边对应的两个顶点的序号:";
for (k = 0; k < G.e; k++)
{
cin >> i >> j;
G.edges[i][j] = 1;
}
}
void DisMGraph(MGraph& G)//遍历打印
{
for (int i = 0; i < G.n; i++)
{
for (int j = 0; j < G.n; j++)
{
cout << G.edges[i][j]<<" ";
}
cout << endl;
}
}
- Text.cpp
#include"MGraph.h"
//第一种常规的:
int main()
{
MGraph G;
int A[][MAXV]={{0,1,0,1,1},{1,0,1,1,0},{0,1,0,1,1},{1,1,1,0,1},{1,0,1,1,0}};
CreatMat(G,A,5);//方法1
DisMGraph(G);
CreatMGraph(G);//方法2
DisMGraph(G);
return 0;
}
#include"MGraph.h"
//第2种运用文件的输入
int main()
{
MGraph G;
int A[MAXV][MAXV];
int i, j;
FILE* fp;
errno_t err = fopen_s(&fp, "C:\\Users\\86173\\Desktop\\Data.txt", "r");
//注意:Data.txt的属性中显示的是C:\Users\86173\Desktop
//需要将单斜杠变双斜杠,双斜杠变四斜杠
if (fp == NULL)
{
cout << "文件读取错误!";
return -1;
}
for (i = 0; i < txtRows; i++)
{
for (j = 0; j < txtCols; j++)
{
fscanf_s(fp, "%d", &A[i][j]);/*每次读取一个数,fscanf_s函数遇到空格或者换行结束*/
}
fscanf_s(fp, "\n");
}
fclose(fp);
CreatMat(G, A, 5);//方法1
DisMGraph(G);
return 0;
}
邻接表
- 邻接表是图的一种链表和顺序表相结合的存储结构
- 核心思想 : 对具有n个顶点的图建立n个线性链表存储该图
- 1.为每个顶点建立一个单链表,边结点由三个域组成,adjvex指示与顶点i邻接的顶点的编号,nextarc指向对应下一条边的节点,info存储与边相关的信息,如权值等。
- 2.表头节点有两个域组成,data存储顶点i对应的名称或其他信息,firstarc指向对应顶点i的链表中的第一个边节点。
-
图的邻接表的存储结构的类型声明:
typedef char ElemType;
typedef int InfoType;
#define MAXV 100
typedef struct ANode//边结点类型
{
int adjvex;//该边的终点位置
struct ANode* nextarc;//指向一下条边的指针
InfoType info;//该边的相关信息,如带权图可存放权重
}ArcNode;
typedef struct Vode//表头结点的类型
{
ElemType data;//顶点信息
ArcNode* firstarc;//指向第一条边
}VNode;
typedef VNode AdjList[MAXV]; //AdjList是邻接表类型
typedef struct
{
AdjList adjlist;//邻接表(表头结点组成一个数组)
int n, e;//定义顶点数和边数
}AGraph;//图的领接表类型
完整代码
- AGraph.h
#pragma once
#include<iostream>
#include<stdbool.h>
#include<stdio.h>
using namespace std;
//输入流的头文件
#include <cstring>
#include <io.h>
#include <fstream>
#define txtRows 5 //txt行数
#define txtCols 5 //txt列数
typedef char ElemType;
typedef int InfoType;
#define MAXV 100
#define INF 10000
typedef struct ANode//边结点类型
{
int adjvex;//该边的终点位置
struct ANode* nextarc;//指向一下条边的指针
InfoType info;//该边的相关信息,如带权图可存放权重
}ArcNode;
typedef struct Vode//表头结点的类型
{
ElemType data;//顶点信息
ArcNode* firstarc;//指向第一条边
}VNode;
typedef VNode AdjList[MAXV]; //AdjList是邻接表类型
typedef struct
{
AdjList adjlist;//邻接表(表头结点组成一个数组)
int n, e;//定义顶点数和边数
}AGraph;//图的领接表类型
void CreatAdj(AGraph* &G, int A[][MAXV], int n);//由数组A[n][n]生成邻接矩阵G
//生成图的邻接表
void DisAdj(AGraph *G);//打印
- AGraph1.cpp
#include"AGraph.h"
void CreatAdj(AGraph*& G, int A[][MAXV], int n)//由数组A[n][n]生成邻接表G
{
G = (AGraph*)malloc(sizeof(AGraph));//分配邻接表空间
G->n = n;
G->e = 0;
cout << "请依次输入顶点信息:";
for (int i = 0; i < n; i++)//邻接表的所有表头结点的指针域都设置为空
{
cin >> G->adjlist[i].data;
G->adjlist[i].firstarc = NULL;
}
ArcNode* p = NULL;
for (int i = 0; i < n; i++)
{
for (int j = n - 1; j >= 0; j--)
{
if (A[i][j] != 0 && A[i][j] != INF)//存在一条边
{
p = (ArcNode*)malloc(sizeof(ArcNode));//创建一个边结点p
p->adjvex = j;//该边的终点
p->info = A[i][j];//该边的权重
p->nextarc = G->adjlist[i].firstarc;//将新边结点p用头插法插入到顶点Vi的边表头部
G->adjlist[i].firstarc = p;
G->e++;//对于无向图,边数需要除以2
}
}
}
}
void DisAdj(AGraph *G)//打印
{
ArcNode* p;
for (int i = 0; i < G->n; i++)
{
cout << "[" << i << "]" <<G->adjlist[i].data;
p = G->adjlist[i].firstarc;//找到顶点i的第一个邻接点
while (p != NULL)
{
cout << "->";
cout << p->adjvex ;
p = p->nextarc;//找到下一个邻接点
}
cout << endl;
}
}
- Text.cpp
#include"AGraph.h"
int main()
{
int A[MAXV][MAXV];
FILE* fp;
errno_t err = fopen_s(&fp, "C:\\Users\\86173\\Desktop\\Data1.txt", "r");
//注意:Data.txt的属性中显示的是C:\Users\86173\Desktop
//需要将单斜杠变双斜杠,双斜杠变四斜杠
if (fp == NULL)
{
cout << "文件读取错误!";
return -1;
}
for (int i = 0; i < txtRows; i++)
{
for (int j = 0; j < txtCols; j++)
{
fscanf_s(fp, "%d", &A[i][j]);/*每次读取一个数,fscanf_s函数遇到空格或者换行结束*/
}
fscanf_s(fp, "\n");
}
fclose(fp);
AGraph* G;
CreatAdj(G, A, 5);
DisAdj(G);
return 0;
}
- data1数据和运行结果: