Dijkstra 算法

将结点分成两个集合:已确定最短路长度的点集(记为$S$ 集合)的和未确定最短路长度的点集(记为$T$ 集合)。一开始所有的点都属于$T$集合。初始化$dis(s) = 0$,其他点的均为$INF$。

然后重复这些操作:

  1. 从 $T$ 集合中,选取一个最短路长度最小的结点,移到 $S$ 集合中。
  2. 对那些刚刚被加入 $S$ 集合的结点的所有出边执行松弛操作。

直到 $T$ 集合为空,算法结束。

暴力:

不使用任何数据结构进行维护,每次 2 操作执行完毕后,直接在 $T$ 集合中暴力寻找最短路长度最小的结点。2 操作总时间复杂度为$O(m)$ ,1 操作总时间复杂度为$O(n^2)$ ,全过程的时间复杂度为 $O(n^2)$。

struct edge {
    int v, w;
};

vector<edge> e[maxn];
int dis[maxn], vis[maxn];

void dijkstra(int n, int s) {
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    for (int i = 1; i <= n; i++) {
        int u = 0, mind = 0x3f3f3f3f;
        for (int j = 1; j <= n; j++)
            if (!vis[j] && dis[j] < mind)
                u = j, mind = dis[j];
        vis[u] = true;
        for (auto ed : e[u]) {
            int v = ed.v, w = ed.w;
            if (dis[v] > dis[u] + w)
                dis[v] = dis[u] + w;
        }
    }
}
// 邻接矩阵
#define N 505
#define INF 0x3f3f3f3f
int g[N][N]; // memset(g, 0x3f, sizeof(g));
int dis[N], vis[N];
void dijkstra(int n, int s) {
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    for (int i = 0; i < n; i++) {
        int u = 0, mind = INF;
        for (int j = 0; j < n; j++)
            if (!vis[j] && dis[j] < mind)
                u = j, mind = dis[j];
        vis[u] = 1;
        for (int v = 0; v < n; v++) {
            if (v == u) continue;
            if (dis[v] > dis[u] + g[u][v])
                dis[v] = dis[u] + g[u][v];
        }
    }
}

优先队列:

和二叉堆类似,但使用优先队列时,如果同一个点的最短路被更新多次,因为先前更新时插入的元素不能被删除,也不能被修改,只能留在优先队列中,故优先队列内的元素个数是 $O(m)$ 的,时间复杂度为 $O(mlogm)$。

struct edge {
    int v, w;
};

struct Node {
    int dis, u;
    bool operator<(const Node& o) const { return dis > o.dis; }
};

vector<edge> e[maxn];
int dis[maxn], vis[maxn];
priority_queue<Node> q;

void dijkstra(int n, int s) {
    memset(dis, 63, sizeof(dis));
    dis[s] = 0;
    q.push({0, s});
    while (!q.empty()) {
        int u = q.top().u;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (auto ed : e[u]) {
            int v = ed.v, w = ed.w;
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                q.push({dis[v], v});
            }
        }
    }
}

二叉堆:

维护$n$个元素的小根堆,对于更新图中点$i$的最短路,我们直接定位到这个点在堆数组$dis$中的位置$mp[i]$并对其采取上浮操作。保证每个元素只在堆中存在一次。而在优先队列优化中,一个点可能会多次进入队列。

const int INF = 0x3f3f3f3f;

template <int PMAX = 105, int EMAX = 10005> 
struct Dijkstra {
    int ecnt;
    struct Edge {
        int to, w, next;
    } edge[EMAX];//边集
    int head[PMAX];//head[i], i点出边链表

    void init() {
        for (int i = 0; i < PMAX; i++) head[i] = -1;
        ecnt = 0;
    }

    void add_edge(int u, int v, int w) {
        edge[ecnt].to = v; //终点
        edge[ecnt].w = w; //权值
        edge[ecnt].next = head[u];
        head[u] = ecnt++;
        /*
            before 
            head[u] -> v1 -> v2 -> ... -> -1
            after
            head[u] -> v -> v1 -> v2 -> ... -> -1
        */
    }

    /*
    for (int j = head[i]; j != -1; j = edge[j].next) //遍历i点的出边
        printf("%d %d\\n", edge[j].to, edge[j].w);
    */  

    // mp[i] 图上节点i,对应的堆中的下标
    // o[i] 堆中下标i,对应的图上节点编号
    int dis[PMAX], o[PMAX], mp[PMAX]; // o[i] = j, mp[j] = i
    int hsz;
    // 交换堆中p1和p2下标的值
    void swap(int p1, int p2) {
        
        int t = dis[p1];
        dis[p1] = dis[p2];
        dis[p2] = t;

        t = o[p1];
        o[p1] = o[p2];
        o[p2] = t;

        mp[o[p1]] = p1;
        mp[o[p2]] = p2;
    }

    // 下沉
    void h_down_node(int pos) {
        for (int i=pos*2+1; i<hsz; pos=i, i=i*2+1) {
            if (i+1<hsz && dis[i] > dis[i+1]) i = i+1; 
            if (dis[pos] > dis[i]) {
                swap(pos, i);
            } else break;
        }
    }

    // 上浮
    void h_up_node(int pos) {
        while (pos && dis[pos-1>>1] > dis[pos]) {
            swap(pos, pos-1>>1);
            pos = pos-1>>1;
        }
    }

    // 删除堆顶
    void heap_pop() {
        assert(hsz>0);
        swap(0, --hsz);
        h_down_node(0);
    }

    // n个点,从s出发
    void dijkstra(int n, int s) {
        // 用小根堆维护dis
        memset(dis, 0x3f, sizeof(dis));
        dis[s] = 0;
        for (int i=0; i<n; i++) mp[i] = o[i] = i;
        hsz = n;
        // 初始化n个元素的堆,只有mp[s]是0,其他是无穷大,直接上浮mp[s]
        h_up_node(s);
        while (hsz) {
            int u = o[0];
            heap_pop();
            for (int j = head[u]; j != -1; j = edge[j].next) {
                int v = edge[j].to, w = edge[j].w;
                if (mp[v] < hsz && dis[mp[v]] > dis[mp[u]] + w) {
                    dis[mp[v]] = dis[mp[u]] + w;
                    h_up_node(mp[v]);
                }
            }
        }
        // for (int i=0; i<n; i++) {
        //     cout << i << " = " << dis[mp[i]] << endl;
        // }
    }
    Dijkstra() {
        init();
    }
};

Floyd 算法

多源最短路,求任意两个结点之间的最短路。时间复杂度$O(n^3)$,常数小。

其思想是动态规划。