一、思维导图
二、重要概念的笔记
图的存储结构
邻接矩阵
#define MAXV 最大顶点个数
typedef struct
{
int no; //顶点的编号
InfoType info; //顶点的其他信息
}VertexType; //顶点的类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵数组
int n, e; //顶点数,边数
VertexType vexs[MAXV]; //存放顶点信息
}MatGraph; //完整的图邻接矩阵类型
邻接表
typedef struct ANode
{
int adjvex; //该边的邻接点编号
struct ANode *nextarc; //指向下一条边的指针
int weight; //该边的相关信息,如权值
}ArcNode; //边结点类型
typedef struct Vnode
{
InfoType info; //顶点的其他信息
ArcNode *firstarc; //指向第一个边结点
}VNode; //邻接表头结点类型
typedef struct
{
VNode adjlist[MAXV]; //邻接表的头结点数组
int n, e; //图中顶点数n和边数e
}Adjraph; //完整的图邻接表类型
图的遍历
深度优先遍历
int visited[MAX] = {0}; //全局数组
void DFS(AdjGraph *G, int v)
{
ArcNode *p;
visited[v] = 1; //置已访问标记
cout << v << " ";
p = G->adjlist[v].firstarc; //p指向顶点v的第一个邻接点
while (p)
{
if (visited[p->adjvex] == 0) //若p->adjvex顶点未被访问,则递归访问它
{
DFS(G, p->adjvex);
}
p = p->nextarc; //p指向顶点v的下一个邻接点
}
}
广度优先遍历
void BFS(AdjGraph *G, int v)
{
int w, i;
ArcNode *p;
SqQueue(qu);
int visited[MAXV]; //定义顶点访问标记数组
for (i=0; i<G->n; i++)
{
visited[i] = 0;
}
cout << v << " ";
visited[v] = 1; //置已访问标记
enQueue(qu, v);
while (!QueueEmpty(qu))
{
deQueue(qu, w); //出队一个顶点w
p = G->adjlist[w].firstarc; //指向w的第一个邻接点
while (p) //查找w的所有邻接点
{
if (visited[p->adjvex] == 0) //若当前邻接点未被访问
{
cout << p->adjvex << " ";
visited[p->adjvex] = 1; //置已访问标记
enQueue(qu, p->adjvex); //该顶点进队
}
p = p->nextarc; //找下一个邻接点
}
}
}
最小生成树
普里姆算法(Prim)
普里姆(Prim) 算法是一种构造性算法。假设G=(V, E)是一个具有n个顶点的带权连通图,T=(U,TE)是G的最小生成树,其中U是T的顶点集,TE是T的边集,则由G构造从起始点v出发的最小生成树T的步骤如下:
- 初始化U={v},以v到其他顶点的所有边为候选边。
- 重复以下步骤(n一1)次,使得其他(n一1)个顶点被加人到U中。
- 从候选边中挑选权值最小的边加入TE,设该边在V-U中的顶点是k,将k加人U中;
- 考查当前V-U中的所有顶点j,修改候选边,若(k,j)的权值小于原来和顶点j关联的候选边,则用(k,j)取代后者作为候选边。
克鲁斯卡尔算法(Kruskal)
克鲁斯卡尔(Kruskal)算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。假设G=(V,E)是一个具有n个顶点的带权连通无向图,T=(U,TE)是G的最小生成树,则构造最小生成树的步骤如下:
- 置U的初值为V(即包含有G中的全部顶点),TE的初值为空集(即图T中的每一个顶点都构成一个分量)。
- 将图G中的边按权值从小到大的顺序依次选取,若选取的边未使生成树T形成回路,则加入TE,否则舍弃,直到TE中包含(n一1)条边为止。
拓扑排序
设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,…,vn称为一个拓扑序列。
在一个有向图中找一个拓扑序列的过程称为拓扑排序。
拓扑排序方法如下:
- 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。
- 从图中删去该顶点,并且删去从该顶点发出的全部有向边。
- 重复上述两步,直到剩余的图中不再存在没有前驱的顶点为止。
这样操作的结果有两种:
一种是图中全部顶点都被输出,即该图中所有顶点都在其拓扑序列中,这说明图中不存在回路;另一种就是图中顶点未被全部输出,这说明图中存在回路。
所以可以通过对一个有向图进行拓扑排序,看是否产生全部顶点的拓扑序列来确定该图中是否存在回路。
AOE网和关键路径
用有向无环图描述工程的预计进度, 以顶点表示事件,有向边表示活动,边e的权c(e)表示完成活动e所需的时间(如天数),或者说活动e持续时间。图中入度为0的顶点表示工程的开始事件(如开工仪式),出度为0的顶点表示工程结束事件, 称这样的有向图为边表示活动的网(activity on edge network,AOE网) 。
通常每个工程都只有一个开始事件和一个结束事件, 因此表示工程的AOE网都只有一个入度为0的顶点, 称为源点(source), 和一个出度为0的顶点, 称为汇点(converge)。如果图中存在多个入度为0的顶点,只要加一个虚拟源点,使这个虚拟源点到原来所有入度为0的点都有一条长度为0的边,从而变成只有一个源点。对存在多个出度为0的顶点的情况做类似的处理。
利用这样的AOE网能够计算完成整个工程预计需要多少时间, 并找出影响工程进度的“关键活动”,从而为决策者提供修改各活动的预计进度的依据。
在AOE网中, 从源点到汇点的所有路径中具有最大路径长度的路径称为关键路径(critical path)。完成整个工程的最短时间就是AOE网中关键路径的长度, 或者说是AOE网中一条关键路径上各活动持续时间的总和, 把关键路径上的活动称为关键活动(keyactivity)。关键活动不存在富余的时间, 而非关键活动可能存在富余的时间。通常一个AOE网可能存在多条关键路径, 但它们的长度是相同的。
因此, 只要找出AOE网中的所有关键活动也就找到了全部关键路径。
三、疑难问题及解决方案
7-4 公路村村通
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。
输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12
问题:测试点3,最大N和M,连通,答案错误。
折腾了一下午,没发现问题出在哪,后来参考了同学的代码,发现邻接矩阵初始化不对。
原初始化代码:
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
G->edges[i][j] = 0;
}
}
修改后:
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (i == j)
{
G->edges[i][j] = 0;
}else
{
G->edges[i][j] = INF;
}
}
}
终于解决了问题,至于为什么之前初始化错误但样例答案正确,不大清楚。可能是碰巧?
完整代码如下:
#include <iostream>
using namespace std;
#define MAXV 10000
#define INF 32767
typedef struct
{
int edges[MAXV][MAXV];
int n, e;
} MGraph;
int Prim(MGraph *g, int v)
{
int cnt = 0;
int lowcost[MAXV];
int MIN;
int closest[MAXV], i, j, k;
for (i=0; i<g->n; i++)
{
lowcost[i] = g->edges[v][i];
closest[i] = v;
}
for (i=1; i<g->n; i++)
{
MIN = INF;
for (j=0; j<g->n; j++)
{
if (lowcost[j] != 0 && lowcost[j] < MIN)
{
MIN = lowcost[j];
k = j;
}
}
if (MIN == INF)
{
return 1;
}
lowcost[k] = 0;
cnt += MIN;
for (j=0; j<g->n; j++)
{
if (lowcost[j] != 0 && g->edges[k][j] < lowcost[j])
{
lowcost[j] = g->edges[k][j];
closest[j] = k;
}
}
}
cout << cnt << endl;
return 0;
};
int main()
{
int n, e;
cin >> n >> e;
if (n > e+1)
{
cout << -1 << endl;
return 0;
}
MGraph *G = new MGraph;
G->n = n; G->e = e;
int i, j;
for (i = 0; i < n; i++) //初始化
{
for (j = 0; j < n; j++)
{
if (i == j)
{
G->edges[i][j] = 0;
}else
{
G->edges[i][j] = INF;
}
}
}
for (i=0; i<e; i++) //建图
{
int a, b, c;
cin >> a >> b >> c;
G->edges[a-1][b-1] = G->edges[b-1][a-1] = c;
}
if ( Prim(G, 1) ) cout << -1 << endl;
return 0;
}