c++学习笔记:贪心算法(最短路径(Dijkstra),最小生成树(kruskal,prim))

/*
参考书:算法设计技巧与分析  M.H.Alsuwaiyel著 吴伟旭 方世昌译
----------------------------------------------------------------
1. 贪心算法
包含一个寻找局部最优解的迭代过程
----------------------------------------------------------------
2.最短路径问题(Dijkstra)
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径
参考:https://blog.csdn.net/tianhaobing/article/details/65443049
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。
若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,
那么就替换这些顶点在dis中的值。
然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
-----------------------------------------------------------------
3.最小耗费生成树(Kruskal)
参考:https://www.cnblogs.com/yoke/p/6697013.html
Kruskal算法从边的角度入手,
Kruskal是另一个计算最小生成树的算法,其算法原理如下。首先,将每个顶点放入其自身的数据集合中。
然后,按照权值的升序来选择边。当选择每条边时,判断定义边的顶点是否在不同的数据集中。
如果是,将此边插入最小生成树的集合中,同时,将集合中包含每个顶点的联合体取出,如果不是,就移动到下一条边。
重复这个过程直到所有的边都探查过。


-----------------------------------------------------------------
4.最小耗费生成树(Prim)
Prim算法从顶点的角度入手
Prim算法的步骤包括:

1. 将一个图分为两部分,一部分归为点集U,一部分归为点集V,U的初始集合为{V1},V的初始集合为{ALL-V1}。

2. 针对U开始找U中各节点的所有关联的边的权值最小的那个,然后将关联的节点Vi加入到U中,并且从V中删除(注意不能形成环)。

3. 递归执行步骤2,直到V中的集合为空。

4. U中所有节点构成的树就是最小生成树。
*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
std::default_random_engine generator;
#define INF 9999999
/*
均匀分布uniform,正态分布normal,二项分布binomial,泊松分布poisson
*/
std::uniform_int_distribution<int> dis(1, 9);

void myRandom(vector<int>&datasets, int n) {
    for (int i = 0; i < n; i++)
        datasets.push_back(dis(generator));
}

//https://blog.csdn.net/tianhaobing/article/details/65443049
//用一个record[]记录每一个点的的前驱 1的前驱是1  ,若不连通,前驱为自身
void dijkstra(int v_num = 6, int e_num = 10) {
    vector<int> X(1), Y(v_num - 1);//X表示已经确定的顶点,Y表示未确定的顶点
    X[0] = 1;
    for (int i = 0; i < v_num - 1; i++)
        Y[i] = i + 2;//2,3,4,5,6

    vector<int> dis(v_num + 1);//表示顶点到每个点的距离 没有就是INF
    vector<int> record(v_num + 1);//用于记录每个点的上一个点//通过它可以打印路径
    //dis[0] = 100; 0没有用到
    vector<vector<int>> G;
    for (int i = 0; i < v_num + 1; i++) {
        //vector temp(v_num + 1, INF);
        G.push_back(vector<int>(v_num + 1, INF));
        G[i][i] = 0;//对角线为0
                    ////////////////
        G[0][i] = i;//这两句话是为了打印好看 0 1 2 3 4 5 6 标签
        G[i][0] = i;
    }
    G[1][2] = 2;
    G[1][5] = 3;
    G[1][3] = 12;
    G[2][3] = 3;
    G[2][4] = 6;
    G[4][3] = 4;
    G[4][6] = 15;
    G[5][4] = 2;
    G[5][6] = 4;
    G[3][5] = 5;
    for (int i = 1; i <= v_num; i++) {
        dis[i] = G[1][i];
        record[i] = (dis[i] != INF ? 1 : i);
        //if (dis[i] != INF)
        //  record[i] = 1;
        //else
        //  record[i] = i;
    }
    for (auto it = G.begin(); it != G.end(); it++) {
        for (auto it2 = it->begin(); it2 != it->end(); it2++) {
            cout << *it2 << '\t';
        }
        cout << endl;
    }
    cout << "选择:1" << '\n' << "dis:\t";;
    for_each(dis.begin(), dis.end(), [&](auto &each) {cout << each << '\t'; });
    cout << endl;

    cout << "X:" << '\t';
    for_each(X.begin(), X.end(), [&](auto &each) {cout << each << '\t'; });
    cout << endl;
    cout << "Y:" << '\t';
    for_each(Y.begin(), Y.end(), [&](auto &each) {cout << each << '\t'; });
    cout << endl;
    while (!Y.empty()) {
        int d = 0;
        for (int i = 1; i < Y.size(); i++) {//从Y中找到最短的
            if (dis[Y[d]]>dis[Y[i]]) {
                d = i;
            }

        }
        //将Y[d]从Y中移到X中,并更新dis
        X.push_back(Y[d]);

        //Y.erase(Y.begin() + d);
        for (int i = 0; i < Y.size(); i++) {
            //int temp = dis[Y[d]] + G[Y[i]][Y[d]];
            int temp = dis[Y[d]] + G[Y[d]][Y[i]];
            if (temp < dis[Y[i]]) {
                dis[Y[i]] = temp;
                record[Y[i]] = Y[d];
            }
        }
        Y.erase(Y.begin() + d);

        cout << "选择:" << X.back() << '\n' << "dis:\t";
        for_each(dis.begin(), dis.end(), [&](auto &each) {cout << each << '\t'; });
        cout << endl;

        cout << "X:" << '\t';
        for_each(X.begin(), X.end(), [&](auto &each) {cout << each << '\t'; });
        cout << endl;
        cout << "Y:" << '\t';
        for_each(Y.begin(), Y.end(), [&](auto &each) {cout << each << '\t'; });
        cout << endl;
    }

    for_each(record.begin(), record.end(), [&](auto &each) {cout << each << '\t'; });
    cout << endl;
}
typedef struct _node {

    int val;   //长度

    int start; //边的起点

    int end;   //边的终点
    struct _node():val(0),start(0),end(0){}
    struct _node(int val, int start, int end) :val(val),start(start),end(end) {}

}Node;

int FIND(int x, vector<vector<int>> &VSet) {
    //找到顶点x在Vset中的位置
    for (int i = 0; i < VSet.size(); i++) {
        for (int j = 0; j < VSet[i].size(); j++) {
            if (VSet[i][j] == x)
                return i;
        }
    }
    return 0;
}
void UNION(int x, int y, vector<vector<int>> &VSet) {
    //把x,y所在的两个点集合并
    int p1 = FIND(x, VSet);
    int p2 = FIND(y, VSet);
    VSet[p1].insert(VSet[p1].end(), VSet[p2].begin(), VSet[p2].end());
    VSet.erase(VSet.begin() + p2);
}
//最小生成树 kruskal 顶点数,边数
void kruskal(int v_num = 6, int e_num = 9) {
    vectoredges;//初始的边的集合
    vectorT;//选中的边的集合
    edges.push_back(Node(1, 1, 2));
    edges.push_back(Node(2, 1, 3));
    edges.push_back(Node(6, 2, 3));
    edges.push_back(Node(11, 2, 4));
    edges.push_back(Node(9, 3, 4));
    edges.push_back(Node(13, 3, 5));
    edges.push_back(Node(7, 4, 5));
    edges.push_back(Node(3, 4, 6));
    edges.push_back(Node(4, 5, 6));

    vector<vector<int>> VSet(v_num);//初始化点集
    for (int i = 0; i < v_num; i++) {
        VSet[i].push_back(i + 1);
    }

    sort(edges.begin(), edges.end(), [](auto&edge1, auto&edge2) {return edge1.val < edge2.val; });
    for_each(edges.begin(), edges.end(), [](auto&edge1) {cout << edge1.val << '\t'; });
    cout << endl;

    while (T.size() < v_num - 1) {//最终有v_num-1条边
        if (!edges.empty()) {
            int p1 = FIND(edges[0].start, VSet);
            int p2 = FIND(edges[0].end, VSet);
            if (p1 != p2) {
                T.push_back(edges[0]);
                UNION(edges[0].start, edges[0].end, VSet);
            }
            //else {
                edges.erase(edges.begin());//如果这两个点已经在一起了,就将这条边删除
            //}
        }
    }
    for_each(T.begin(), T.end(), [](auto&edge1) {cout << edge1.val << '\t'; });
    cout << endl;

}

void prim(int v_num=6,int e_num=9) {
    vector<vector<int> > G(v_num + 1, vector<int>(v_num + 1, INF));
    vector<int>X,Y;//顶点集合
    vectorT;//选中的边的集合
    X.push_back(1);//X中是已经选中的点
    for (int i = 0; i < v_num - 1; i++) {
        Y.push_back(i + 2);//Y中是剩余的点
    }
    G[1][2] = G[2][1] = 1;
    G[1][3] = G[3][1] = 2;
    G[2][3] = G[3][2] = 6;
    G[2][4] = G[4][2] = 11;
    G[4][3] = G[3][4] = 9;
    G[3][5] = G[5][3] = 13;
    G[4][5] = G[5][4] = 7;
    G[4][6] = G[6][4] = 3;
    G[5][6] = G[6][5] = 4;

    while (!Y.empty()) {
        //找到最小权重的边,顶点分别在X,Y中
        Node min_edge(G[X[0]][Y[0]], X[0],Y[0]);
        for (int i = 0; i < X.size(); i++) {
            for (int j = 0; j < Y.size(); j++) {
                if (min_edge.val > G[X[i]][Y[j]]) {
                    min_edge.val = G[X[i]][Y[j]];
                    min_edge.start = X[i];
                    min_edge.end = Y[j];
                }
            }
        }
        //X<--X U {y}  Y<--Y-{y}
        T.push_back(min_edge);
        X.push_back(min_edge.end);
        auto it = Y.begin();
        for (; it != Y.end(); it++) {
            if (*it == min_edge.end)
                break;
        }
        Y.erase(it);
    }
    for_each(T.begin(), T.end(), [](auto&edge1) {cout << edge1.val << '\t'; });
    cout << endl;
}
int main() {
    //dijkstra();
    //kruskal();
    prim();
    system("pause");
    return 0;
}

Dijkstra
c++学习笔记:贪心算法(最短路径(Dijkstra),最小生成树(kruskal,prim))_第1张图片
c++学习笔记:贪心算法(最短路径(Dijkstra),最小生成树(kruskal,prim))_第2张图片

你可能感兴趣的:(c++)