数据结构第八节(图(下))新浪
作者:互联网
图(下)
前面说过了对一个图,寻找最短路径的问题,这次来说最小生成树,如何把一个联通图,选出最小的边的组合而且还需满足将其所有点链接再一起,这就是最小生成树问题。
最小生成树#
什么是最小生成树#
最小生成树,首先需要满足树的性质和定义,给顶N个顶点的图,选择N-1条边,将这N个顶点连同,且满足边权重和是所有的生成树中最小的。下面是两种最小生成树的经典算法。
Prim算法#
Prim算法的每一步都会为一棵生长中的树添加一条边,该树最开始只有一个顶点,然后会添加V−1V−1个边。每次总是添加从生长树包含的节点选取还没被收录的最小权值的边,且加入这条边后不会生成环。(从小树长成大树)
通过邻接矩阵图表示的简易实现中,找到所有最小权边共需O(V2)O(V2)的运行时间。使用简单的二叉堆与邻接表来表示的话,普里姆算法的运行时间则可缩减为O(ElogV)O(ElogV)。如果使用较为复杂的斐波那契堆,则可将运行时间进一步缩短为O(E+VlogV)O(E+VlogV)。
Copy/*
* 下面实现了一个通过连接矩阵实现的prim算法
* 首先选择第1个节点作为根节点,用一个数组dist[]来保存所有节点到数的距离
* 开始的时候dist[]初始化为和第1个点相距的边
* 如果我们已经实现了把一个节点加入到树,他的dist设置为0
* 每次从dist选择一个离树最近的点
* 将其添加到树中,同时更新其他节点到树的最小距离dist
* parents数组会保存每一个节点的根节点,根节点的根节点为-1.
* 注意所生成的图时,如果边不连同一定要设置为 INFINITY。
*/
#define INFINITY 10000000
#define NotFound -1
//find min vertex
Vertex findMin(Graph G,WeightType dist[]) {
Vertex v, minv;
WeightType minDist = INFINITY;
for (int i = 0; i < G->Nvertex; i++)
{
if (dist[i] != 0 && dist[i] < minDist) {
minv = i;
minDist = dist[i];
}
}
if (minDist<INFINITY)
{
return minv;
}
return NotFound;
}
//prim
int prim(Graph G) {
WeightType dist[MAXSIZE];
Vertex parent[MAXSIZE];
//fist sclice 0 is the root of the tree
for (int i = 0; i < G->Nvertex; i++)
{
dist[i] = G->graph[0][i];
parent[i] = 0;
}
//sum of weight and the vertex number in tree
int TotalWeigth = 0,countV = 0;
//add 0 int tree and set 0 is root
countV++; dist[0] = 0; parent[0] = -1;
//loop
while (true)
{
Vertex v = findMin(G, dist);
//the graph has some problme,doesn't has min...tree
if (v == NotFound) {
break;
}
TotalWeigth += dist[v];
dist[v] = 0;
countV++;
for (int i = 0; i < G->Nvertex; i++)
{
//vertex is not incloude in tree and has edge between the v
if (dist[i] != 0 && INFINITY> G->graph[v][i]) {
//Update distance from the tree
if (dist[i] > G->graph[v][i]) {
dist[i] = G->graph[v][i];
parent[i] = v;
}
}
}
}
if (countV < G->Nvertex-1) {
return NotFound;
}
return TotalWeigth;
}
Kruskal算法#
Kruskal算法采用的则是从所有的边种寻找权值最小的边加入生成树中,但是若加入该边会与生成树形成环则不加入该边,直到树中含有V−1V−1条边为止,或者边用光了停止,这些边组成的就是该图的最小生成树。(将树合并)
Kruskal算法的时间复杂度为O(ElogE)O(ElogE),适合处理稀疏图。
需要注意的是,建立最小堆的时候,记得将容量设置为顶点的平方(翻车了一下午才看出来)。
/*
* 用连接表实现Kruskal算法在于找边,和判断找到的店是否会组成环
*/
#define MINDATA -999999
#define ElementType Edge
typedef struct HeapStruct* Heap;
struct HeapStruct
{
ElementType Elements[MAXSIZE*MAXSIZE];
int Size;
int Capacity;
};
//creat heap
Heap CreatHeap(int MaxSize) {
Heap H = (Heap)malloc(sizeof(struct HeapStruct));
H->Size = 0;
H->Capacity = MaxSize;
Edge e = (Edge)malloc(sizeof(struct ENode));
e->weight = MINDATA;
H->Elements[0] = e;
return H;
}
void Insert(Heap H, Edge e) {
int i = ++H->Size;
//if X big than his father,X become father
for (; H->Elements[i / 2]->weight > e->weight; i /= 2)
{
H->Elements[i] = H->Elements[i / 2];
}
H->Elements[i] = e;
}
//delete
ElementType Delete(Heap H) {
ElementType MinItem = H->Elements[1];
ElementType temp = H->Elements[H->Size--];
int parent, child;
for (parent = 1; parent * 2 <= H->Size; parent = child) {
child = parent * 2;
//if his right son small the the left son
if ((child != H->Size) && (H->Elements[child]->weight > H->Elements[child + 1]->weight)) {
child++;
}
//the temp is the min item int this sub tree
if (temp->weight <= H->Elements[child]->weight) {
break;
}
//move the child to the parent location
else {
H->Elements[parent] = H->Elements[child];
}
}
H->Elements[parent] = temp;
return MinItem;
}
//get root
int getRoot(int a, int set[]) {
while (set[a] >= 0) {
a = set[a];
}
return a;
}
bool marge(int a, int b, int set[]) {
int root1 = getRoot(a, set);
int root2 = getRoot(b, set);
if (root1 == root2) {
return false;
}
if (set[root1] > set[root2]) {
set[root2] += set[root1];
set[root1] = root2;
}
else {
set[root1] += set[root2];
set[root2] = root1;
}
return true;
}
int Kruskal(Graph G) {
int set[MAXSIZE];
int countE = 0, totalWeight = 0;
memset(set, -1, sizeof(set));
Heap H = CreatHeap(G->Nedge);
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = i;
e->weight = temp->Wight;
e->end = temp->Adjv;
Insert(H, e);
temp = temp->Next;
}
}
while (H->Size > 0 && countE < G->Nvertex - 1)
{
ElementType e = Delete(H);
//check
if (marge(e->begin, e->end, set)) {
countE++;
totalWeight += e->weight;
}
}
if (countE < G->Nvertex - 1) {
return -1;
}
return totalWeight;
}
拓扑排序#
拓扑排序应用以及实现#
想象一个你有一个工程,可以分为N个阶段,每个阶段,都需要它前面的阶段完成了才可以开始,如何列出来哪些业务该先做哪些该后做。并将其输出,便是拓扑排序。
注意:有向无环图(DAG)才有拓扑排序。
反应在图中,用a任务指向b任务,表示b需要在a完成以后才可以进行,所以只要某点的入度为零就可以开始工作了。
关键路径#
接着上面的,假如你有一个工程,对于这N个阶段计算出他们的最早完成时间,和最晚完成时间,可以看到有哪些阶段是可以有空闲时间,哪些阶段是一刻也不能耽误的,这就是关键路径问题。
要计算关键路径,首先要计算每个活动的最早完成时间(也就是所有的入度节点 的最早完成时间再加上任务耗费时间的最大值),可以通过拓扑排序来实现。对于每个活动的最晚完成时间,同样也可以用出度的拓扑排序来实现。
Copy/*返回一个拓扑排序
*该方法是用来处理图的邻接表形式的
*而且earlyday中保存了每个活动的最快完成时间
*整个项目需要花费多久可以通过遍历earlyday找到
*/
vector<int> CPM(Graph G, int earlyday[MAXSIZE]) {
int degree[MAXSIZE];
vector<int> v;
queue<int> q;
v.clear();
while (!q.empty())
{
q.pop();
}
//找出所有入度为0的顶点
for (int i = 0; i < G->Nvertex; i++)
{
degree[i] = G->graph[i].indergee;
if (G->graph[i].indergee == 0) {
q.push(i);
}
}
int count = 0;
while (!q.empty())
{
int k = q.front();
v.push_back(k);
q.pop();
count++;
PtrToAdjVNode temp = G->graph[k].first;
while (temp != NULL)
{
int t = earlyday[k] + temp->Wight;
if (t > earlyday[temp->Adjv]) {
earlyday[temp->Adjv] = t;
}
degree[temp->Adjv]--;
if (degree[temp->Adjv] == 0) {
q.push(temp->Adjv);
}
temp = temp->Next;
}
}
if (count < G->Nvertex) {
v.clear();
}
return v;
}
/*返回的结果为关键路径
*传入的参数 sum代表整个项目需要多久,可通过上面的方法来得到
*/
vector<int> CriticalPath(Graph G, int earlyday[MAXSIZE],int sum) {
vector<int> cpath;
int degree[MAXSIZE];
int lastday[MAXSIZE];
queue<int> q;
//有环路无法计算
//找出所有出度为0的顶点
for (int i = 0; i < G->Nvertex; i++)
{
degree[i] = G->graph[i].outdergee;
if (G->graph[i].outdergee == 0) {
q.push(i);
}
}
//将所有节点最迟完成设为总工期
for (int i = 0; i < G->Nvertex; i++)
{
lastday[i] = sum;
}
//计算所有节点最迟完成
while (!q.empty()) {
int k = q.front();
q.pop();
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
if (temp->Adjv == k) {
lastday[i] = min(lastday[i], lastday[k] - temp->Wight);
degree[i]--;
if (degree[i] == 0) {
q.push(i);
}
}
temp = temp->Next;
}
}
}
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
if (earlyday[i] == lastday[temp->Adjv] - temp->Wight) {
cpath.push_back(i);
cpath.push_back(temp->Adjv);
}
temp = temp->Next;
}
}
return cpath;
}
课后习题(3个小题)#
08-图7 公路村村通 (30point(s))#
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式:
输入数据包括城镇数目正整数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
题解:
一个简单的最小生成树问题,注意到本题所给的为稀疏图,使用Kruskal算法会更快
prim算法:
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#define WeightType int
#define MAXSIZE 1000
#define DataType int
#define Vertex int
#define NotFound -1
#define INFINITY 10000000
using namespace std;
//Use the adjacency matrix to represent the graph
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
WeightType graph[MAXSIZE][MAXSIZE];
DataType Data[MAXSIZE];
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
G->graph[i][j] = INFINITY;
}
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
G->graph[e->begin][e->end] = e->weight;
//If it is an undirected graph, you need to add the following
G->graph[e->end][e->begin] = e->weight;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin-1, end-1, weight));
}
return G;
}
//find min vertex
Vertex findMin(Graph G,WeightType dist[]) {
Vertex v, minv;
WeightType minDist = INFINITY;
for (int i = 0; i < G->Nvertex; i++)
{
if (dist[i] != 0 && dist[i] < minDist) {
minv = i;
minDist = dist[i];
}
}
if (minDist<INFINITY)
{
return minv;
}
return NotFound;
}
//prim
int prim(Graph G) {
WeightType dist[MAXSIZE];
Vertex parent[MAXSIZE];
//fist sclice 0 is the root of the tree
for (int i = 0; i < G->Nvertex; i++)
{
dist[i] = G->graph[0][i];
parent[i] = 0;
}
//sum of weight and the vertex number in tree
int TotalWeigth = 0,countV = 0;
//add 0 int tree and set 0 is root
countV++; dist[0] = 0; parent[0] = -1;
//loop
while (true)
{
Vertex v = findMin(G, dist);
//the graph has some problme,doesn't has min...tree
if (v == NotFound) {
break;
}
TotalWeigth += dist[v];
dist[v] = 0;
countV++;
for (int i = 0; i < G->Nvertex; i++)
{
//vertex is not incloude in tree and has edge between the v
if (dist[i] != 0 && INFINITY> G->graph[v][i]) {
//Update distance from the tree
if (dist[i] > G->graph[v][i]) {
dist[i] = G->graph[v][i];
parent[i] = v;
}
}
}
}
if (countV < G->Nvertex) {
return NotFound;
}
return TotalWeigth;
}
int main()
{
Graph G = BuildGraph();
printf("%d\n", prim(G));
return 0;
}
Kruskal算法:
Copy#include <cstdio>
#include <stdlib.h>
#include <string.h>
#define WeightType int
#define MAXSIZE 5001
#define DataType int
#define Vertex int
using namespace std;
//Use the adjacency List to represent the graph
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {
Vertex Adjv;
WeightType Wight;
PtrToAdjVNode Next;
};
typedef struct VNode AdjList[MAXSIZE];
struct VNode
{
PtrToAdjVNode first;
DataType Data;
};
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
AdjList graph;
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i <= G->Nvertex; i++)
{
G->graph[i].first = NULL;
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
PtrToAdjVNode newnode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newnode->Wight = e->weight;
newnode->Adjv = e->end;
newnode->Next = G->graph[e->begin].first;
G->graph[e->begin].first = newnode;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin - 1, end - 1, weight));
}
return G;
}
/*
* 用连接表实现Kruskal算法在于找边,和判断找到的店是否会组成环
*/
#define MINDATA -999999
#define ElementType Edge
typedef struct HeapStruct* Heap;
struct HeapStruct
{
ElementType Elements[MAXSIZE*MAXSIZE];
int Size;
int Capacity;
};
//creat heap
Heap CreatHeap(int MaxSize) {
Heap H = (Heap)malloc(sizeof(struct HeapStruct));
H->Size = 0;
H->Capacity = MaxSize;
Edge e = (Edge)malloc(sizeof(struct ENode));
e->weight = MINDATA;
H->Elements[0] = e;
return H;
}
void Insert(Heap H, Edge e) {
int i = ++H->Size;
//if X big than his father,X become father
for (; H->Elements[i / 2]->weight > e->weight; i /= 2)
{
H->Elements[i] = H->Elements[i / 2];
}
H->Elements[i] = e;
}
//delete
ElementType Delete(Heap H) {
ElementType MinItem = H->Elements[1];
ElementType temp = H->Elements[H->Size--];
int parent, child;
for (parent = 1; parent * 2 <= H->Size; parent = child) {
child = parent * 2;
//if his right son small the the left son
if ((child != H->Size) && (H->Elements[child]->weight > H->Elements[child + 1]->weight)) {
child++;
}
//the temp is the min item int this sub tree
if (temp->weight <= H->Elements[child]->weight) {
break;
}
//move the child to the parent location
else {
H->Elements[parent] = H->Elements[child];
}
}
H->Elements[parent] = temp;
return MinItem;
}
//get root
int getRoot(int a, int set[]) {
while (set[a] >= 0) {
a = set[a];
}
return a;
}
bool marge(int a, int b, int set[]) {
int root1 = getRoot(a, set);
int root2 = getRoot(b, set);
if (root1 == root2) {
return false;
}
if (set[root1] > set[root2]) {
set[root2] += set[root1];
set[root1] = root2;
}
else {
set[root1] += set[root2];
set[root2] = root1;
}
return true;
}
int Kruskal(Graph G) {
int set[MAXSIZE];
int countE = 0, totalWeight = 0;
memset(set, -1, sizeof(set));
Heap H = CreatHeap(G->Nedge);
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = i;
e->weight = temp->Wight;
e->end = temp->Adjv;
Insert(H, e);
temp = temp->Next;
}
}
while (H->Size > 0 && countE < G->Nvertex - 1)
{
ElementType e = Delete(H);
//check
if (marge(e->begin, e->end, set)) {
countE++;
totalWeight += e->weight;
}
}
if (countE < G->Nvertex - 1) {
return -1;
}
return totalWeight;
}
int main()
{
Graph G = BuildGraph();
printf("%d\n", Kruskal(G));
return 0;
}
08-图8 How Long Does It Take (25point(s))#
Given the relations of all the activities of a project, you are supposed to find the earliest completion time of the project.
Input Specification:
Each input file contains one test case. Each case starts with a line containing two positive integers N (≤100), the number of activity check points (hence it is assumed that the check points are numbered from 0 to N−1), and M, the number of activities. Then M lines follow, each gives the description of an activity. For the i-th activity, three non-negative numbers are given: S[i], E[i], and L[i], where S[i] is the index of the starting check point, E[i] of the ending check point, and L[i] the lasting time of the activity. The numbers in a line are separated by a space.
Output Specification:
For each test case, if the scheduling is possible, print in a line its earliest completion time; or simply output "Impossible".
Sample Input 1:
9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4
Sample Output 1:
18
Sample Input 2:
4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5
Sample Output 2:
Impossible
题解:
通过拓扑排序求整个项目的最快完成时间
注意项目可能并非起点和终点,所以去扫描一遍,很有可能最后出队的任务并非最耗时间的。
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <queue>
#define WeightType int
#define MAXSIZE 5001
#define DataType int
#define Vertex int
using namespace std;
//Use the adjacency List to represent the graph
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {
Vertex Adjv;
WeightType Wight;
PtrToAdjVNode Next;
};
typedef struct VNode AdjList[MAXSIZE];
struct VNode
{
int indergee;
int outdergee;
PtrToAdjVNode first;
DataType Data;
};
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
AdjList graph;
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i <= G->Nvertex; i++)
{
G->graph[i].first = NULL;
G->graph[i].indergee = 0;
G->graph[i].outdergee = 0;
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
PtrToAdjVNode newnode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newnode->Wight = e->weight;
newnode->Adjv = e->end;
newnode->Next = G->graph[e->begin].first;
G->graph[e->begin].first = newnode;
G->graph[e->end].indergee++;
G->graph[e->begin].outdergee++;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin, end, weight));
}
return G;
}
vector<int> CMP(Graph G,int earlyday[MAXSIZE]) {
int degree[MAXSIZE];
memset(earlyday, 0, sizeof(earlyday));
vector<int> v;
queue<int> q;
v.clear();
while (!q.empty())
{
q.pop();
}
//找出所有入度为0的顶点
for (int i = 0; i < G->Nvertex; i++)
{
degree[i] = G->graph[i].indergee;
if (G->graph[i].indergee == 0) {
q.push(i);
}
}
int count = 0;
while (!q.empty())
{
int k = q.front();
v.push_back(k);
q.pop();
count++;
PtrToAdjVNode temp = G->graph[k].first;
while (temp!=NULL)
{
int t = earlyday[k] + temp->Wight;
if (t > earlyday[temp->Adjv]) {
earlyday[temp->Adjv] = t;
}
degree[temp->Adjv]--;
if (degree[temp->Adjv]==0) {
q.push(temp->Adjv);
}
temp = temp->Next;
}
}
if (count < G->Nvertex) {
v.clear();
}
return v;
}
int main() {
Graph G = BuildGraph();
int earlyday[MAXSIZE];
vector<int> v = CMP(G,earlyday);
int maxd = earlyday[0];
if (v.size() == 0) {
printf("Impossible\n");
}
else {
for (int i = 1; i < G->Nvertex; i++)
{
maxd = max(maxd, earlyday[i]);
}
printf("%d\n",maxd);
}
}
08-图9 关键活动 (30point(s))#
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1N编号,M是子任务的数量,依次编号为1M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。
输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。
输入样例:
7 8
1 2 4
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2
输出样例:
17
1->2
2->4
4->6
6->7
题解:
通过拓扑排序先求最早时间再求最晚时间,如果某个活动的最早开始时间,加上到下一活动的时间,等于下一活动的最晚结束时间就是关键活动,因为他一天也不能耽搁,否则就会增长整个工期。
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <queue>
#define WeightType int
#define MAXSIZE 5001
#define DataType int
#define Vertex int
using namespace std;
//Use the adjacency List to represent the graph
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {
Vertex Adjv;
WeightType Wight;
PtrToAdjVNode Next;
};
typedef struct VNode AdjList[MAXSIZE];
struct VNode
{
int indergee;
int outdergee;
PtrToAdjVNode first;
DataType Data;
};
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
AdjList graph;
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i <= G->Nvertex; i++)
{
G->graph[i].first = NULL;
G->graph[i].indergee = 0;
G->graph[i].outdergee = 0;
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
PtrToAdjVNode newnode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newnode->Wight = e->weight;
newnode->Adjv = e->end;
newnode->Next = G->graph[e->begin].first;
G->graph[e->begin].first = newnode;
G->graph[e->end].indergee++;
G->graph[e->begin].outdergee++;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin-1, end-1, weight));
}
return G;
}
vector<int> CPM(Graph G, int earlyday[MAXSIZE]) {
int degree[MAXSIZE];
vector<int> v;
queue<int> q;
v.clear();
while (!q.empty())
{
q.pop();
}
//找出所有入度为0的顶点
for (int i = 0; i < G->Nvertex; i++)
{
degree[i] = G->graph[i].indergee;
if (G->graph[i].indergee == 0) {
q.push(i);
}
}
int count = 0;
while (!q.empty())
{
int k = q.front();
v.push_back(k);
q.pop();
count++;
PtrToAdjVNode temp = G->graph[k].first;
while (temp != NULL)
{
int t = earlyday[k] + temp->Wight;
if (t > earlyday[temp->Adjv]) {
earlyday[temp->Adjv] = t;
}
degree[temp->Adjv]--;
if (degree[temp->Adjv] == 0) {
q.push(temp->Adjv);
}
temp = temp->Next;
}
}
if (count < G->Nvertex) {
v.clear();
}
return v;
}
vector<int> CriticalPath(Graph G, int earlyday[MAXSIZE],int sum) {
vector<int> cpath;
int degree[MAXSIZE];
int lastday[MAXSIZE];
queue<int> q;
//有环路无法计算
//找出所有出度为0的顶点
for (int i = 0; i < G->Nvertex; i++)
{
degree[i] = G->graph[i].outdergee;
if (G->graph[i].outdergee == 0) {
q.push(i);
}
}
//将所有节点最迟完成设为总工期
for (int i = 0; i < G->Nvertex; i++)
{
lastday[i] = sum;
}
//计算所有节点最迟完成
while (!q.empty()) {
int k = q.front();
q.pop();
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
if (temp->Adjv == k) {
lastday[i] = min(lastday[i], lastday[k] - temp->Wight);
degree[i]--;
if (degree[i] == 0) {
q.push(i);
}
}
temp = temp->Next;
}
}
}
for (int i = 0; i < G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
if (earlyday[i] == lastday[temp->Adjv] - temp->Wight) {
cpath.push_back(i);
cpath.push_back(temp->Adjv);
}
temp = temp->Next;
}
}
return cpath;
}
int main() {
Graph G = BuildGraph();
int earlyday[MAXSIZE];
memset(earlyday, 0, sizeof(earlyday));
vector<int> v = CPM(G, earlyday);
int maxd = earlyday[0];
if (v.size() == 0) {
printf("0\n");
}
else {
for (int i = 1; i < G->Nvertex; i++)
{
maxd = max(maxd, earlyday[i]);
}
printf("%d\n", maxd);
v = CriticalPath(G, earlyday, maxd);
for (int i = 0; i < v.size(); i+=2)
{
printf("%d->%d\n", v[i]+1, v[i + 1]+1);
}
}
}
标签:weight,temp,int,graph,第八节,++,新浪,数据结构,Nvertex 来源: https://www.cnblogs.com/robingo/p/14128146.html