Dijkstra 算法
Dijkstra算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。
在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)
这个算法是通过为每个顶点 v 保留目前为止所找到的从s到v的最短路径来工作的。初始时,原点 s 的路径权重被赋为 0 (d[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把d[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大,即表示我们不知道任何通向这些顶点的路径(对于所有顶点的集合 V 中的任意顶点 v, 若 v 不为 s 和上述 m 之一, d[v] = ∞)。当算法结束时,d[v] 中存储的便是从 s 到 v 的最短路径,或者如果路径不存在的话是无穷大。
struct Node;
typedef struct Node *T;
typedef char *Inspace_char;
const int N = (1 << 10) + 1;
//M : 顶点的个数
const int M = 7;
//Node : 链表.
//ch : 字符串
//num_Node : 到该字符串的权, 也就是路径长
//next : 下一节点;
struct Node
{
Inspace_char ch;
int num_Node;
T next;
//链表初始化
Node() : next(NULL), num_Node(0) {}
};
//指向 Node 的链表
//num_Dijk : 初始为-1, 表示未被使用的链表, 1 表示使用的链表; 当然也可以使用bool
//ch_Dik : 字符串;
//next_Node : 指向链表 Node 的指针;
struct Dijk
{
Inspace_char ch_Dik;
int num_Dijk;
T next_Node;
//链表初始化
Dijk() : next_Node(new Node), num_Dijk(-1) {}
}dijk[N];
因为输入的顶点不一定是数字, 可能是字符串, 所以在此也使用散列的部分代码
//散列计算
unsigned int Hash(char *ch)
{
unsigned int hash_size = 0;
while (*ch != '\0')
hash_size = (hash_size << 10) + *ch++;
return hash_size % ((1 << 9) + 1);
}
接下来是将字符的插入, 详细的思路代码里呈现, 不过整体跟拓扑排序一样
//插入;
//ch2 : 被指向的字符串, 如 : v1 -> v2, 则ch1 = v1, ch2 = v2;
void Inset(Inspace_char ch1, Inspace_char ch2, int num)
{
//将ch1 ch2转成数;
unsigned int a = Hash(ch1);
unsigned int b = Hash(ch2);
//将ch2插入到ch1的 Dijk指向的Node链表里;
T temp = new Node;
temp->num_Node = num;
temp->ch = ch2;
temp->next = dijk[a].next_Node->next;
dijk[a].next_Node->next = temp;
//如果ch1未被使用过, 将其变为 1 代表使用
if (dijk[a].num_Dijk == -1)
{
dijk[a].num_Dijk = 1;
dijk[a].ch_Dik = ch1;
}
//如果ch2未被使用过, 将其变为 1 代表使用
if (dijk[b].num_Dijk == -1)
{
dijk[b].num_Dijk = 1;
dijk[b].ch_Dik = ch2;
}
}
最后是关键的排序了
//Dijkstra排序 : 按当前从哪个位置出发每步最优, 不一定是所有路径最优
void DijkSort(Inspace_char ch)
{
//temp : 每次都指向被保存字符串的指向 Node 的链表
//t : t = temp; 每次遍历t;
T temp = new Node, t = new Node;
//a[N] : 将每次最优的字符对应的编号保存
//sum_min : 最优的路径长;
int a[N] = { 0 }, i = 0, sum_min = 0;
//将起始的字符串保存;
a[i] = Hash(ch);
//将ch1指向 Node 的链表保存, 保存后i++;
temp = dijk[a[i++]].next_Node;
t = temp->next;
//将所有顶点保存完退出;
while (i < M)
{
int min = N;
//ch_temp : 整个链表中的权最小;
Inspace_char ch_temp;
//这里temp->next != NULL 是必要的, 不能为temp != NULL ,要保证退出去后 temp != NULL
while (temp->next)
{
if (min > temp->next->num_Node && temp->next->num_Node != 0)
{
min = Min(min, temp->next->num_Node);
ch_temp = temp->next->ch;
}
temp = temp->next;
}
//将下次最短的字符串保存起来;
a[i] = Hash(ch_temp);
sum_min += min;
//遍历 temp 链表, 将所有的含有保存过的字符串num_Node清 0 , 表示未使用
for (int j = 0; j < N; j++)
if (dijk[j].num_Dijk == 1)
{
T t = dijk[j].next_Node->next;
while (t)
{
if (t->ch == ch_temp)
t->num_Node = 0;
t = t->next;
}
}
//将刚保存的字符串所指向的 Node的链表放在temp 的最后;
//因为 temp->next == NULL ,所以temp != NULL, 保证下一个链表接在现在的链表最后;
temp->next = dijk[a[i]].next_Node->next;
temp = t;
while (temp)
{
if (a[i] == Hash(temp->ch))
temp->num_Node = 0;
temp = temp->next;
}
temp = t;
i++;
}
for (i = 0; i < M; i++)
printf("%s ", dijk[a[i]].ch_Dik);
}
源代码
#include <stdlib.h>
#include <stdio.h>
#define Min(a, b) (a > b ? b : a)
struct Node;
typedef struct Node *T;
typedef char *Inspace_char;
const int N = (1 << 10) + 1;
//M : 顶点的个数
const int M = 7;
//Node : 链表.
//ch : 字符串
//num_Node : 到该字符串的权, 也就是路径长
//next : 下一节点;
struct Node
{
Inspace_char ch;
int num_Node;
T next;
//链表初始化
Node() : next(NULL), num_Node(0) {}
};
//指向 Node 的链表
//num_Dijk : 初始为-1, 表示未被使用的链表, 1 表示使用的链表; 当然也可以使用bool
//ch_Dik : 字符串;
//next_Node : 指向链表 Node 的指针;
struct Dijk
{
Inspace_char ch_Dik;
int num_Dijk;
T next_Node;
//链表初始化
Dijk() : next_Node(new Node), num_Dijk(-1) {}
}dijk[N];
//散列计算
unsigned int Hash(char *ch)
{
unsigned int hash_size = 0;
while (*ch != '\0')
hash_size = (hash_size << 10) + *ch++;
return hash_size % ((1 << 9) + 1);
}
//插入;
//ch2 : 被指向的字符串, 如 : v1 -> v2, 则ch1 = v1, ch2 = v2;
void Inset(Inspace_char ch1, Inspace_char ch2, int num)
{
//将ch1 ch2转成数;
unsigned int a = Hash(ch1);
unsigned int b = Hash(ch2);
//将ch2插入到ch1的 Dijk指向的Node链表里;
T temp = new Node;
temp->num_Node = num;
temp->ch = ch2;
temp->next = dijk[a].next_Node->next;
dijk[a].next_Node->next = temp;
//如果ch1未被使用过, 将其变为 1 代表使用
if (dijk[a].num_Dijk == -1)
{
dijk[a].num_Dijk = 1;
dijk[a].ch_Dik = ch1;
}
//如果ch2未被使用过, 将其变为 1 代表使用
if (dijk[b].num_Dijk == -1)
{
dijk[b].num_Dijk = 1;
dijk[b].ch_Dik = ch2;
}
}
//Dijkstra排序 : 按当前从哪个位置出发每步最优, 不一定是所有路径最优
void DijkSort(Inspace_char ch)
{
//temp : 每次都指向被保存字符串的指向 Node 的链表
//t : t = temp; 每次遍历t;
T temp = new Node, t = new Node;
//a[N] : 将每次最优的字符对应的编号保存
//sum_min : 最优的路径长;
int a[N] = { 0 }, i = 0, sum_min = 0;
//将起始的字符串保存;
a[i] = Hash(ch);
//将ch1指向 Node 的链表保存, 保存后i++;
temp = dijk[a[i++]].next_Node;
t = temp->next;
//将所有顶点保存完退出;
while (i < M)
{
int min = N;
//ch_temp : 整个链表中的权最小;
Inspace_char ch_temp;
//这里temp->next != NULL 是必要的, 不能为temp != NULL ,要保证退出去后 temp != NULL
while (temp->next)
{
if (min > temp->next->num_Node && temp->next->num_Node != 0)
{
min = Min(min, temp->next->num_Node);
ch_temp = temp->next->ch;
}
temp = temp->next;
}
//将下次最短的字符串保存起来;
a[i] = Hash(ch_temp);
sum_min += min;
//遍历 temp 链表, 将所有的含有保存过的字符串num_Node清 0 , 表示未使用
for (int j = 0; j < N; j++)
if (dijk[j].num_Dijk == 1)
{
T t = dijk[j].next_Node->next;
while (t)
{
if (t->ch == ch_temp)
t->num_Node = 0;
t = t->next;
}
}
//将刚保存的字符串所指向的 Node的链表放在temp 的最后;
//因为 temp->next == NULL ,所以temp != NULL, 保证下一个链表接在现在的链表最后;
temp->next = dijk[a[i]].next_Node->next;
temp = t;
while (temp)
{
if (a[i] == Hash(temp->ch))
temp->num_Node = 0;
temp = temp->next;
}
temp = t;
i++;
}
for (i = 0; i < M; i++)
printf("%s ", dijk[a[i]].ch_Dik);
}
//保存的有向图数据
void Input_Inset()
{
Inset("v1", "v2", 2);
Inset("v1", "v4", 1);
Inset("v2", "v4", 3);
Inset("v2", "v5", 10);
Inset("v3", "v1", 4);
Inset("v3", "v6", 5);
Inset("v4", "v3", 2);
Inset("v4", "v5", 2);
Inset("v4", "v6", 8);
Inset("v4", "v7", 4);
Inset("v5", "v7", 6);
Inset("v7", "v6", 1);
}
int main()
{
Input_Inset();
DijkSort("v2");
system("pause");
return 0;
}