关于图的学习

目录

一、图的定义

顶点的度、入度、出度

子图:

连通图(是对无向图而言):

强连通图(是对有向图而言):

 生成树:

 生成森林:

边的权、带权图/网、带权路径长度

几种特殊形态的图

二、图的存储结构

邻接矩阵

图的邻接矩阵存储结构的类型声明:

邻接矩阵的性能分析

完整代码 

 邻接表

图的邻接表的存储结构的类型声明: 

完整代码


一、图的定义

  • 图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是非连通图,则最多可能有C^{_{n-1}^{2}};(比如一共有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数据和运行结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值