图 邻接表 最小生成树 最小生成树的两种算法核心思想都是贪心
普利姆算法 思路:整个算法分为三步
预准备数组初始化
找与当前边相接的最短的打印且储存
更新最小权值的数组
唯一最小生成树无论从哪个节点出发生成的树都是一样的
所以我们从0出发
这只是最基本的方法,有很大的优化空间
完整代码
void Kruskal () { EDG b[20 ]; int markz[10 ] = {0 }; int size = EDGinit(b); printf ("\n" ); sort(size, b); int start, end; for (int i = 0 ; i <= size; i++) { start = DEBUG(b[i].start, markz); end = DEBUG(b[i].end, markz); if (start != end) { markz[start] = end; printf ("(%d %d)\n" , b[i].start, b[i].end); } } }
void primeinit () { int i; shortest[0 ] = 0 ; adjek[0 ] = 0 ; for (i = 1 ; i < MAX - 1 ; i++) { shortest[i] = b.b[0 ][i]; adjek[i] = 0 ; } }
min = INF; k = 0 ; for (j = 1 ; j < MAX - 1 ; j++) { if (shortest[j] != 0 && shortest[j] < min) { min = shortest[j]; k = j; } } printf ("(%d %d)\n" , adjek[k], k); shortest[k] = 0 ;
for (j = 1 ; j < MAX - 1 ; j++) { if (shortest[j] != 0 && shortest[j] > b.b[k][j]) { shortest[j] = b.b[k][j]; adjek[j] = k; } }
注意其中的1,2是在一个大循环中实现的,因为要遍历所有的点
int j, i, k, min;k = 0 ; for (i = 1 ; i < MAX - 1 ; i++) { min = INF; k = 0 ; for (j = 1 ; j < MAX - 1 ; j++) { if (shortest[j] != 0 && shortest[j] < min) { min = shortest[j]; k = j; } } printf ("(%d %d)\n" , adjek[k], k); shortest[k] = 0 ; for (j = 1 ; j < MAX - 1 ; j++) { if (shortest[j] != 0 && shortest[j] > b.b[k][j]) { shortest[j] = b.b[k][j]; adjek[j] = k; } } }
Kruskal算法 基本思路:把每条路径的权值储存起来,然后从小到大依次选择,只要不形成回路即可
定义一个边集数组,存储起始节点和权值,定义一个可以并查集的数组来判断是否形成了循环
遍历边集数组并排序
遍历排序后到边集数组
完整代码(不包含调用的自定义函数)
void Kruskal () { EDG b[20 ]; int markz[10 ] = {0 }; int size = EDGinit(b); printf ("\n" ); sort(size, b); int start, end; for (int i = 0 ; i <= size; i++) { start = DEBUG(b[i].start, markz); end = DEBUG(b[i].end, markz); if (start != end) { markz[start] = end; printf ("(%d %d)\n" , b[i].start, b[i].end); } } }
边集数组以及存储数据
int EDGinit (EDG *p) { int i = 0 , j = 0 , head = -1 ; for (i = 0 ; i < MAX - 1 ; i++) { for (j = 0 ; j < MAX - 1 ; j++) { if (b.b[i][j] != INF && b.b[i][j] != 0 ) { (p)[++head].start = i; (p)[head].end = j; (p)[head].power = b.b[i][j]; b.b[i][j] = 0 ; b.b[j][i] = 0 ; } } } return head; }
2.边集数组的排序
使用不同的排序方式可以该边该算法的效率
void sort (int size, EDG b[]) { int i, j; EDG temp; for (i = size; i > 0 ; i--) { for (j = i - 1 ; j >= 0 ; j--) { if (b[i].power < b[j].power) { temp = b[i]; b[i] = b[j]; b[j] = temp; } } } return ; }
3.开始输出
for (int i = 0 ; i <= size; i++) { start = DEBUG(b[i].start, markz); end = DEBUG(b[i].end, markz); if (start != end) { markz[start] = end; printf ("(%d %d)\n" , b[i].start, b[i].end); } } int DEBUG (int x, int mark[]) { while (mark[x] > 0 ) { x = mark[x]; } return x; }
mark为并查集数组,将end位置放上start的值 即mark[end]为它的头,当mark[x]==0时说明其没有前一个节点,其自身就是最开始的节点
最短路径 Dijkstra算法 主要应用到某点到其余点之间的最短路
思路:广搜找最短路径
完整代码:
void Dijkstra (int v1) { int low[MAX] = {0 }; int final[MAX] = {0 }; int i, j, k, min, w; for ( i = 0 ; i < MAX - 1 ; i++) { low[i] = b.b[v1][i]; } for (i = 0 ; i < MAX - 1 ; i++) { min = INF; for (j = 0 ; j < MAX - 1 ; j++) { if (!final[j] && low[j] < min) { min = low[j]; w = j; } } final[w] = 1 ; for (j = 0 ; j < MAX - 1 ; j++) { if (!final[j] && min + b.b[w][j] < low[j]) { low[j] = min + b.b[w][j]; path[j] = w; } } } }
弗洛伊德(Floyd)算法 思想:和Dijkstra算法类似,广搜找最短,然后去找加入这个点后是否有比以前的路更短的方法,因为要记录的东西更多了所以把记录最短路径和前一个节点的数组开到了两位而已==完全不一样好嘛 ==
因为弗洛伊德算法是找每个点到每个点之间最短的路径要通过改变每个点到每个点经过的节点来进行遍历。
拓扑排序和关键路径 拓扑排序 基本思路:用栈存储了入度为0的节点,采用BFS的形式查找,将查找到的节点入度减一,如果为0,就存入栈,如果输出的节点小于总数,那么便是路径中有环
结构体的定义:
边节点:
typedef struct line { int name; int power; struct line *next ; } Line, *linep;
顶点
typedef struct node { int in; linep head; } node; node Node[MAX];
实现
void toposort (int num) { int stack [MAX] = {0 }; int top = -1 ; int temp; int count = 0 ; for (int i = 0 ; i <= num; i++) { if (Node[i].in == 0 ) { stack [++top] = i; } } linep p = NULL ; while (top != -1 ) { temp = stack [top--]; p = Node[temp].head; printf ("%d->" , temp); count++; while (p != NULL ) { Node[p->name].in--; if (Node[p->name].in == 0 ) { stack [++top] = p->name; } p = p->next; } } if (count < num) { } }
关键路径 因为是aov图所以要等前置时间完成后才能够开始下一步,所以是多路时间值取最大
在记录最晚发生实际取最小的值的原因是如果比上一个节点的最早的节点晚的画,就会造成工期拖延
比如:22:00洗脸10,22:00刷牙5分钟,22:20睡觉。
前置事件是是洗脸和刷牙,要等刷完牙才能睡觉,所以睡觉的最早开始时间应该取刷完牙的时间
而最晚的发生时间则要看洗脸的时间,因为刷牙完成了可能洗脸还没洗完
主要思路:四个提前定义的基本类型:
int ete[];int lte[];int e;int l;
int stack [MAX] = {0 }; int stack1[MAX] = {0 }; int etv[MAX] = {0 }; int ltv[MAX] = {0 }; int ete; int lte; int top = -1 ; int top1 = -1 ; int temp;
第一部分:在拓扑排序的时候记录下顺序存到另一个专门存排序顺序的栈里,便于后续的求最晚发生时间
while (top != -1 ) { temp = stack [top--]; stack1[++top1] = temp; p = Node[temp].head; while (p != NULL ) { Node[p->name].in--; if (Node[p->name].in == 0 ) { stack [++top] = p->name; } if (etv[temp] + p->power > etv[p->name]) { etv[p->name] = etv[temp] + p->power; } p = p->next; } } if (top1 < num) { }
第二部分:初始化最早时间:
for (int i = 0 ; i <= num; i++) { ltv[i] = etv[num]; }
第三部分找最找的时间
while (top1 != -1 ) { temp = stack1[top1--]; p = Node[temp].head; while (p) { if (ltv[p->name] - p->power < ltv[temp]) { ltv[temp] = ltv[p->name] - p->power; } p = p->next; } }
第四部分当最短时间和最早时间相等时便输出
for (int i = 0 ; i <= num; i++) { for (p = Node[i].head; p; p = p->next) { temp = p->name; ete = etv[i]; lte = ltv[temp] - p->power; if (ete == lte) { printf ("<%d,%d> lenth:%d\n" , i, temp, p->power); } } }