图和树有点像,或者说是变化更多更复杂的森林也不为过。
先清晰一下概念:
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在图中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。
图按照边的有无方向分为无向图和有向图。无向图由顶点和边组成,有向图由顶点和弧构成。弧有弧尾和弧头之分,带箭头一端为弧头。
图按照边或弧的多少分稀疏图和稠密图。如果图中的任意两个顶点之间都存在边叫做完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。
图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度。有向图顶点分为入度和出度。
图上的边或弧带有权则称为网。
图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复的叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图。图中有子图,若子图极大连通则就是连通分量,有向的则称为强连通分量。
无向图中连通且n个顶点n-1条边称为生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。
--------------------------------------------
基于此大概还是对于复杂,变化多的非线性数据进行处理,以下是几种存储方法。
邻接矩阵的表示,其对于稀疏图的处理很浪费空间,所以一般用于稠密图:
#include<iostream>
using namespace std;
enum Graphkind{ DG, DN, UDG, UDN }; //{有向图,无向图,有向网,无向网}
typedef struct Node
{
int * vex; //顶点数组
int vexnum; //顶点个数
int edge; //图的边数
int ** adjMatrix; //图的邻接矩阵
enum Graphkind kind;
}MGraph;
void createGraph(MGraph & G,enum Graphkind kind)
{
cout << "输入顶点的个数" << endl;
cin >> G.vexnum;
cout << "输入边的个数" << endl;
cin >> G.edge;
//输入种类
//cout << "输入图的种类:DG:有向图 DN:无向图,UDG:有向网,UDN:无向网" << endl;
G.kind = kind;
//为两个数组开辟空间
G.vex = new int[G.vexnum];
G.adjMatrix = new int*[G.vexnum];
cout << G.vexnum << endl;
int i;
for (i = 0; i < G.vexnum; i++)
{
G.adjMatrix[i] = new int[G.vexnum];
}
for (i = 0; i < G.vexnum; i++)
{
for (int k = 0; k < G.vexnum; k++)
{
if (G.kind == DG || G.kind == DN)
{
G.adjMatrix[i][k] = 0;
}
else {
G.adjMatrix[i][k] = INT_MAX;
}
}
}
/*//输入每个元素的信息,这个信息,现在还不需要使用
for (i = 0; i < G.vexnum; i++)
{
cin >> G.vex[i];
}*/
cout << "请输入两个有关系的顶点的序号:例如:1 2 代表1号顶点指向2号顶点" << endl;
for (i = 0; i < G.edge; i++)
{
int a, b;
cin >> a;
cin >> b;
if (G.kind == DN) {
G.adjMatrix[b - 1][a - 1] = 1;
G.adjMatrix[a - 1][b - 1] = 1;
}
else if (G.kind == DG)
{
G.adjMatrix[a - 1][b - 1] = 1;
}
else if (G.kind == UDG)
{
int weight;
cout << "输入该边的权重:" << endl;
cin >> weight;
G.adjMatrix[a - 1][b - 1] = weight;
}
else {
int weight;
cout << "输入该边的权重:" << endl;
cin >> weight;
G.adjMatrix[b - 1][a - 1] = weight;
G.adjMatrix[a - 1][b - 1] = weight;
}
}
}
void print(MGraph g)
{
int i, j;
for (i = 0; i < g.vexnum; i++)
{
for (j = 0; j < g.vexnum; j++)
{
if (g.adjMatrix[i][j] == INT_MAX)
cout << "∞" << " ";
else
cout << g.adjMatrix[i][j] << " ";
}
cout << endl;
}
}
void clear(MGraph G)
{
delete G.vex;
G.vex = NULL;
for (int i = 0; i < G.vexnum; i++)
{
delete G.adjMatrix[i];
G.adjMatrix[i] = NULL;
}
delete G.adjMatrix;
}
int main()
{
MGraph G;
cout << "有向图例子:" << endl;
createGraph(G, DG);
print(G);
clear(G);
cout << endl;
cout << "无向图例子:" << endl;
createGraph(G, DN);
print(G);
clear(G);
cout << endl;
cout << "有向图网例子:" << endl;
createGraph(G, UDG);
print(G);
clear(G);
cout << endl;
cout << "无向图网例子:" << endl;
createGraph(G, UDN);
print(G);
clear(G);
cout << endl;
return 0;
}
邻接表代码的实现,主要用于稀疏图
#include<iostream>
#include<string>
using namespace std;
typedef string Vertextype;
//表结点结构
struct ArcNode {
int adjvex; //某条边指向的那个顶点的位置(一般是数组的下标)。
ArcNode * nextarc; //指向下一个表结点
int weight; //这个只有网图才需要使用。
};
//头结点
struct Vnode
{
Vertextype data; //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
ArcNode * firstarc; //指向第一条依附在该顶点边的信息(表结点)
};
//
struct Graph
{
int kind; //图的种类(有向图:0,无向图:1,有向网:2,无向网:3)
int vexnum; //图的顶点数
int edge; //图的边数
Vnode * node; //图的(顶点)头结点数组
};
void createGraph(Graph & g,int kind)
{
cout << "请输入顶点的个数:" << endl;
cin >> g.vexnum;
cout << "请输入边的个数(无向图/网要乘2):" << endl;
cin >> g.edge;
g.kind = kind; //决定图的种类
g.node = new Vnode[g.vexnum];
int i;
cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
for (i = 0; i < g.vexnum; i++)
{
cin >> g.node[i].data;
g.node[i].firstarc=NULL;
}
cout << "请输入每条边的起点和终点的编号:" << endl;
for (i = 0; i < g.edge; i++)
{
int a;
int b;
cin >> a; //起点
cin >> b; //终点
ArcNode * next=new ArcNode;
next->adjvex = b - 1;
if(kind==0 || kind==1)
next->weight = -1;
else {//如果是网图,还需要权重
cout << "输入该边的权重:" << endl;
cin >> next->weight;
}
next->nextarc = NULL;
//将边串联起来
if (g.node[a - 1].firstarc == NULL) {
g.node[a - 1].firstarc=next;
}
else
{
ArcNode * p;
p = g.node[a - 1].firstarc;
while (p->nextarc)//找到该链表的最后一个表结点
{
p = p->nextarc;
}
p->nextarc = next;
}
}
}
void print(Graph g)
{
int i;
cout << "图的邻接表为:" << endl;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data<<" ";
ArcNode * now;
now = g.node[i].firstarc;
while (now)
{
cout << now->adjvex << " ";
now = now->nextarc;
}
cout << endl;
}
}
int main()
{
Graph g;
cout << "有向图的例子" << endl;
createGraph(g,0);
print(g);
cout << endl;
cout << "无向图的例子" << endl;
createGraph(g, 1);
print(g);
cout << endl;
return 0;
}
十字链表的建立,其可以很好的处理顶点的入度:
typedef string Vertextype;
//表结点结构
struct ArcNode {
int tailvex; //弧尾的下标,一般都是和对应的头结点下标相同
int headvex; //弧头的下标
ArcNode * hlink; //指向下一个弧头同为headvex的表结点 ,边是箭头的那边
ArcNode * tlink; //指向下一个弧尾为tailvex的表结点,边不是箭头的那边
int weight; //只有网才会用这个变量
};
//头结点
struct Vnode
{
Vertextype data; //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
ArcNode *firstin; //指向第一条(入度)在该顶点的表结点
ArcNode *firstout; //指向第一条(出度)在该顶点的表结点
};
还有邻接多重表,以下是无向图的存储结构:
#include<iostream>
#include<string>
using namespace std;
//表结点
struct ArcNode
{
int mark; //标志位
int ivex; //输入边信息的那个起点
ArcNode * ilink; //依附在顶点ivex的下一条边的信息
int jvex; //输入边信息的那个终点
ArcNode * jlink; //依附在顶点jvex的下一条边的信息
int weight;
};
//头结点
struct VexNode {
string data; //顶点的信息,如顶点名称
ArcNode * firstedge; //第一条依附顶点的边
};
struct Graph {
int vexnum; //顶点的个数
int edge; //边的个数
VexNode *node; //保存顶点信息
};
void createGraph(Graph & g)
{
cout << "请输入顶点的个数:" << endl;
cin >> g.vexnum;
cout << "请输入边的个数(无向图/网要乘2):" << endl;
cin >> g.edge;
g.node = new VexNode[g.vexnum];
int i;
cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
for (i = 0; i < g.vexnum; i++)
{
cin >> g.node[i].data;
g.node[i].firstedge = NULL;
}
cout << "请输入每条边的起点和终点的编号:" << endl;
for (i = 0; i < g.edge; i++)
{
int a, b;
cin >> a;
cin >> b;
ArcNode * next = new ArcNode;
next->mark = 0;
next->ivex = a - 1; //首先是弧头的下标
next->jvex = b - 1; //弧尾的下标
next->weight = -1;
next->ilink = NULL;
next->jlink = NULL;
//更新顶点表a-1的信息
if (g.node[a - 1].firstedge == NULL)
{
g.node[a - 1].firstedge = next;
}
else {
ArcNode * now;
now = g.node[a - 1].firstedge;
while (1) {
if (now->ivex == (a - 1) && now->ilink == NULL)
{
now->ilink = next;
break;
}
else if (now->ivex == (a - 1) && now->ilink != NULL) {
now = now->ilink;
}
else if (now->jvex == (a - 1) && now->jlink == NULL)
{
now->jlink = next;
break;
}
else if (now->jvex == (a - 1) && now->jlink != NULL) {
now = now->jlink;
}
}
}
//更新顶点表b-1
if (g.node[b - 1].firstedge == NULL)
{
g.node[b - 1].firstedge = next;
}
else {
ArcNode * now;
now = g.node[b - 1].firstedge;
while (1) {
if (now->ivex == (b - 1) && now->ilink == NULL)
{
now->ilink = next;
break;
}
else if (now->ivex == (b - 1) && now->ilink != NULL) {
now = now->ilink;
}
else if (now->jvex == (b - 1) && now->jlink == NULL)
{
now->jlink = next;
break;
}
else if (now->jvex == (b - 1) && now->jlink != NULL) {
now = now->jlink;
}
}
}
}
}
void print(Graph g)
{
int i;
for (i = 0; i < g.vexnum; i++)
{
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstedge;
while (now)
{
cout << "ivex=" << now->ivex << " jvex=" << now->jvex << " ";
if (now->ivex == i)
{
now = now->ilink;
}
else if (now->jvex == i)
{
now = now->jlink;
}
}
cout << endl;
}
}
int main()
{
Graph g;
createGraph(g);
print(g);
system("pause");
return 0;
}
搞完之后,就牵扯到图的遍历了,分为深度搜索优先(逐渐深入)和广度搜索优先(对一层一层进行处理)
然后在应用里面学到了最小生成树,主要引入为计算多个带权值的最小路径。
普里姆算法(按点生成):
#include<stdio.h>
#include<string.h>
#define INF 1000000 //无穷大
#define MAXN 21 //顶点个数最大值
int n, m; //顶点个数、边数
int Edge[MAXN][MAXN]; //邻接矩阵
int lowcost[MAXN]; //记录集合T1中各顶点到集合T内各顶点权值最小的边的权值
int nearvex[MAXN]; //记录集合T1中各顶点距离顶点集合T内哪个顶点最近;当nearvex[i] = -1时,表示顶点i属于集合T
void prim(int u0) //从顶点u0出发执行prim算法
{
int i, j; //循环变量
int sumweight = 0; //生成树的权值
for(i = 1; i <= n; i++) //初始化lowcost数组和nearvex数组
{
lowcost[i] = Edge[u0][i];
nearvex[i] = u0;
}
nearvex[u0] = -1;
for(i = 1; i < n; i++)
{
int min = INF;
int v = -1;
//在lowcost数组中找最小值
for(j = 1; j <=n; j++)
if(nearvex[j]!=-1 && lowcost[j]<min) { v = j; min = lowcost[v]; }
if(v != -1) //表示找到权值最小的边
{
printf("%d %d %d\n", nearvex[v], v, lowcost[v]);
nearvex[v] = -1; //将选出的边的状态置为-1,表示在集合T中
sumweight += lowcost[v];
//由于集合T新加入了顶点v,所以要更新lowcost中顶点v与T1集合中各顶点的距离,同时将更新的顶点记录到nextvex中
for(j = 1; j <= n; j++) if(nearvex[j]!=-1 && Edge[v][j]<lowcost[j])
{
lowcost[j] = Edge[v][j];
nearvex[j] = v;
}
}
}
printf("The weight of MST is %d\n", sumweight);
}
int main()
{
int i, j;
int u, v, w;
scanf("%d%d", &n, &m);
memset(Edge, 0, sizeof(Edge));
//构造邻接矩阵
for(i = 1; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &w);
Edge[u][v] = Edge[v][u] = w;
}
for(i = 1; i <= n; i++)
for(j = 1; j <=n; j++)
{
if(i == j) Edge[i][j] = 0;
else if(Edge[i][j] == 0) Edge[i][j] = INF;
}
printf("The lines chosen are :\n");
prim(1); //从顶点1出发构造最小生成树
return 0;
克鲁斯卡尔算法(按边生成):
#include <stdio.h>
#include <stdlib.h>
#define MAXEDGE 20
#define MAXVEX 20
#define INFINITY 65535
typedef struct
{
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;//顶点数,边数
}MGraph;
typedef struct
{
int begin;
int end;
int weight;
}Edge; //对边集数组Edge结构的定义
//创建图的邻接矩阵
void CreateMGraph(MGraph *G) {
int i, j;
G->numEdges=11;
G->numVertexes=7;
for (i = 0; i < G->numVertexes; i++) {
for ( j = 0; j < G->numVertexes; j++) {
if (i==j)
G->arc[i][j]=0;
else
G->arc[i][j] = G->arc[j][i] = INFINITY;
}
}
G->arc[0][1]=7;
G->arc[0][3]=5;
G->arc[1][2]=8;
G->arc[1][3]=9;
G->arc[1][4]=7;
G->arc[2][4]=5;
G->arc[3][4]=15;
G->arc[3][5]=6;
G->arc[4][5]=8;
G->arc[4][6]=9;
G->arc[5][6]=11;
for(i = 0; i < G->numVertexes; i++) {
for(j = i; j < G->numVertexes; j++) {
G->arc[j][i] =G->arc[i][j];
}
}
}
//快速排序的条件
int cmp(const void* a, const void* b) {
return (*(Edge*)a).weight - (*(Edge*)b).weight;
}
//找到根节点
int Find(int *parent, int f) {
while ( parent[f] > 0) {
f = parent[f];
}
return f;
}
// 生成最小生成树
void MiniSpanTree_Kruskal(MGraph G) {
int i, j, n, m;
int k = 0;
int parent[MAXVEX]; //用于寻找根节点的数组
Edge edges[MAXEDGE]; //定义边集数组,edge的结构为begin,end,weight,均为整型
// 用来构建边集数组并排序(将邻接矩阵的对角线右边的部分存入边集数组中)
for ( i = 0; i < G.numVertexes-1; i++) {
for (j = i + 1; j < G.numVertexes; j++) {
if (G.arc[i][j] < INFINITY) {
edges[k].begin = i; //编号较小的结点为首
edges[k].end = j; //编号较大的结点为尾
edges[k].weight = G.arc[i][j];
k++;
}
}
}
//为边集数组Edge排序
qsort(edges, G.numEdges, sizeof(Edge), cmp);
for (i = 0; i < G.numVertexes; i++)
parent[i] = 0;
printf("打印最小生成树:\n");
for (i = 0; i < G.numEdges; i++) {
n = Find(parent, edges[i].begin);//寻找边edge[i]的“首节点”所在树的树根
m = Find(parent, edges[i].end);//寻找边edge[i]的“尾节点”所在树的树根
//假如n与m不等,说明两个顶点不在一棵树内,因此这条边的加入不会使已经选择的边集产生回路
if (n != m) {
parent[n] = m;
printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}
int main(void)
{
MGraph G;
CreateMGraph(&G);
MiniSpanTree_Kruskal(G);
return 0;
}
很完美!接下来是一道作业题
给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。
输入第1行给出2个整数N(0)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
按照"{ v?1?? v?2?? ... v?k?? }"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。
#include <iostream> #include <string.h> #include <queue> using namespace std; int map[10][10]; bool visited[10]; int result[10]; int k; int n, m; //深搜算法 void dfs(int x) { result[k++] = x; visited[x] = true; for (int i = 0; i < n; i++) { if (map[x][i] == 1 && !visited[i]) { dfs(i); } } } /* 广搜 */ void bfs(int x) { queue<int> q; q.push(x); visited[x] = 1; result[k++] = x; while (!q.empty()) { int l = q.front(); q.pop(); for (int i = 0; i < n; i++) { if (map[l][i] == 1 && !visited[i]) { visited[i] = 1; result[k++] = i; q.push(i); } } } } int main() { cin >> n >> m; memset(visited, 0, sizeof(visited)); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) map[i][j] = 0; while (m--) { int i, j; cin >> i >> j; map[i][j] = 1; map[j][i] = 1; } ///* 列出图深搜所有的连通集 */ for (int i = 0; i < n; i++) { k = 0; if (!visited[i]) { dfs(i); cout << "{ "; for (int i = 0; i < k; i++) cout << result[i] << " "; cout << "}" << endl; } } memset(visited, 0, sizeof(visited)); /* 列出图广搜所有的连通集 */ for (int i = 0; i < n; i++) { k = 0; if (!visited[i]) { bfs(i); cout << "{ "; for (int i = 0; i < k; i++) cout << result[i] << " "; cout << "}" << endl; } } }
其实就考了个深搜和广搜的处理,没什么技术含量。。。
007的还没肝完,写完了也许会在总结一下?
原文:https://www.cnblogs.com/20181002925hh/p/10891546.html