生成树:一个连通图的生成树时一个极小连通子图,包含n个顶点,但只有构成一棵树的n-1条边

注:包含n个顶点的无向完全图最多包含n^(n-2)棵生成树

注:只有无向图才有生成树(且必须为连通图),有向图对应概念为树型生成图

生成树的属性:
    1.一个连通图可以有多个生成树
    2.一个连通图的所有生成树都包含相同的顶点个数和边数
    3.生成树当中不存在环
    4.移除生成树中的任意一条边都会导致图的不连通
    5.在生成树中添加一条边会构成环
    6.对于包含n个顶点的连通图,生成树包含n个顶点和n-1条边
    7.对于包含n个顶点的无向完全图最多包含包含n^(n-2)棵生成树

最小生成树:一个带权图的生成树,该生成树边权之和最小,则称该生成树为最小生成树
注:只有无向连通带权图才有最小生成树,且最小生成树不一定唯一

1.Kruskal算法---->从边出发 //基于贪心和并查集
    1.选择权值尽可能小的n-1条边---->排序:选择排序算法
    2.选边时,选的每一条边都得是连接未连通的两个节点才行,否则成环--->通过并查集判断一条边的两个点是否属于同一个集合,若不属于,则该边可选,并合并两个不同集合

    时间复杂度:主要分为两块 选择排序O(m^2)+并查集O(m)---->O(m^2)
    注:即Kruskal算法时间复杂度取决于排序算法时间复杂度,比如使用快速排序将会变为mlogm
    (m为边的数目)
2.Prim算法----->从点出发   //基于贪心

点到生成树的距离dist:
1.已经在生成树中的点:0
2.不在生成树中的点:该点与生成树中任意若干点直接相连,其每条连接边对应的权值就是dist
注:不考虑间接相连,间接相连dist视为无穷大

点分为两类:
1.已经选中的点,已经构成一部分生成树
2.还未选中的点
选择n次:
第一次:任意选择一个点
接下来n-1一次:选则第二类中点,该点dist最小,同时通过选中的点更新与其相连的点到生成树的dist

思路:
维护一个点到生成树的最小距离dist[],初始化为无穷大
维护一个标记数组visit[]
1.任选一个点作为起点,把起点x加入到生成树中:visit[x]=1,dist[x]=0
2.更新x邻接点到生成树最小距离
3.执行n-1次循环(1)找到visit[]==0且dist[]最小的点下(2)把起点x加入到生成树中:visit[x]=1,dist[x]=0 (3)更新x邻接点到生成树最小距离 

时间复杂度:O(n^2)
注:n为点的个数

注:点多边多(稠密图)Prim算法,点多边少(稀疏图)Kruskal算法

Kruskal算法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//并查集
class Tree_Node
{
public:
    int fi;
    int data;//注:本图所有点编号与其下标对应
};
class Tree
{
public:
    Tree_Node* pN;
    int size;
public:
    Tree(int sz) :size(sz)
    {
        pN = new Tree_Node[size];
        for (int i = 0; i < size; ++i)
        {
            pN[i].data = pN[i].fi = i;
        }
    }
    int Find_fi(int k)
    {
        while (pN[k].data != pN[k].fi)
        {
            //往上找
            k = pN[k].fi;
        }
        return k;
    }
};
//无向连通带权图
//存储方式:邻接矩阵+边集数组(边集数组方便根据权值对边进行排序)
class Edge
{
public:
    int u;
    int v;//两个顶点
    int w;//权值
};

class Graph
{
public:
    int size1;//点的个数
    int size2;//边的个数
    int** graph;
    Edge* e;
public:
    Graph(int sz1, int sz2) :size1(sz1), size2(sz2)
    {
        graph = new int* [sz1];
        e = new Edge[sz2];
        if (e == nullptr || graph == nullptr)
        {
            cout << "开辟失败" << endl;
        }
        else
        {
            for (int i = 0; i < sz1; ++i)
            {
                graph[i] = new int[sz1];
                if (graph[i] == nullptr)
                {
                    cout << "开辟失败" << endl;
                }
            }
            //初始化图
            for (int i = 0; i < sz1; ++i)
            {
                for (int j = 0; j < sz1; ++j)
                {
                    if (i == j)
                    {
                        graph[i][j] = 0;//自边权值为零
                    }
                    else
                    {
                        graph[i][j] = 999999;//没边权值为一个较大数
                    }
                }
            }
            //输入边
            int x(0);
            int y(0);
            int wi(0);
            for (int i = 0; i < sz2; ++i)
            {
                cin >> x >> y >> wi;
                graph[x][y] = graph[y][x] = wi;
                e[i].u = x;
                e[i].v = y;
                e[i].w = wi;
            }
        }
    }
    //根据权值对边进行排序(选择排序)
    void Sort_Edge()
    {
        Edge tmp;
        int min;
        for (int i = 0; i < size2; ++i)
        {
            min = i;
            for (int j = i + 1; j < size2; ++j)
            {
                if (e[j].w < e[min].w)
                {
                    min = j;
                }
            }
            tmp = e[i];
            e[i] = e[min];
            e[min] = tmp;
        }

    }
    //克鲁斯卡尔算法
    void Kruskal()
    {
        Tree tr(size1);//并查集
        cout << "以下为最小生成树的边" << endl;
        int n(0);//记录选边次数
        int fu(0);
        int fv(0);//两个顶点的祖先
        for (int i = 0; i < size2; ++i)
        {
            fu = tr.Find_fi(e[i].u);
            fv = tr.Find_fi(e[i].v);
            if (fu != fv)
            {
                n++;
                //并查集合并
                tr.pN[fu].fi = fv;
                cout << e[i].u << " " << e[i].v << " " << e[i].w << endl;
            }
            if (n == size1 - 1)
            {
                break;
            }
        }
    }
};
int main()
{
    Graph g(9,15);
    g.Sort_Edge();
    g.Kruskal();
    return 0;
}
/*
测试用例:讲义上的图
9 15
0 1 3
0 5 4
1 6 6
6 5 7
1 2 8
1 8 5
2 8 2
2 3 12
8 3 11
6 3 14
6 7 9
5 4 18
3 7 6
7 4 1
3 4 10
0
*/

Prim算法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//无向连通带权图
//存储方式:邻接矩阵
class Graph
{
public:
    int size1;//点的个数
    int size2;//边的个数
    int** graph;
    bool* visit;//标记数组
    int* dist;//到最小生成树距离数组
public:
    Graph(int sz1,int sz2):size1(sz1),size2(sz2)
    {
        graph = new int* [sz1];
        visit = new bool[sz1];
        dist = new int[sz1];
        if (dist == nullptr || visit == nullptr || graph == nullptr)
        {
            cout << "开辟失败" << endl;
        }
        else
        {
            for (int i = 0; i < sz1; ++i)
            {
                graph[i] = new int[sz1];
                if (graph[i] == nullptr)
                {
                    cout << "开辟失败" << endl;
                }
            }
            //初始化
            for (int i = 0; i < sz1; ++i)
            {
                for (int j = 0; j < sz1; ++j)
                {
                    if (i == j)
                    {
                        graph[i][j] = 0;//自边权值为零
                    }
                    else
                    {
                        graph[i][j] = 999999;//没边权值为一个较大数
                    }
                }
                visit[i] = false;
                dist[i] = 999999;
            }
            //输入边
            int x(0);
            int y(0);
            int wi(0);
            for (int i = 0; i < sz2; ++i)
            {
                cin >> x >> y >> wi;
                graph[x][y] = graph[y][x] = wi;
            }
            prim();
        }
    }
    void prim()
    {
        //1.输入起点
        int x(0);
        cin >> x;
        visit[x] = true;
        dist[x] = 0;
        //2.
        for (int i = 0; i < size1; ++i)
        {
            dist[i] = m_min(dist[i], graph[x][i]);
        }
        //3.
        int t(0);//dist[t]最小
        for (int i = 0; i < size1 - 1; ++i)
        {
            int min_n(999999);
            for (int j = 0; j < size1; ++j)
            {
                if (visit[j] == false && dist[j] < min_n)
                {
                    t = j;
                    min_n = dist[j];
                }
            }
            cout << "点:" << t << "权值为" << min_n << "的边" << endl;
            //将找到的点加入并标记
            visit[t] = true;
            dist[t] = 0;
            //更新下标为t的点的邻接点
            for (int i = 0; i < size1; ++i)
            {
                //没有选择过才能更新
                if (visit[i] == false)
                {
                    dist[i] = m_min(dist[i],graph[t][i]);
                }
            }
        }
    }
    inline int m_min(int n1,int n2)
    {
        return n1 < n2 ? n1 : n2;
    }
};
int main()
{
    Graph g(9, 15);
    g.prim();
    return 0;
}
/*
测试用例:讲义上的图
9 15
0 1 3
0 5 4
1 6 6
6 5 7
1 2 8
1 8 5
2 8 2
2 3 12
8 3 11
6 3 14
6 7 9
5 4 18
3 7 6
7 4 1
3 4 10
0
*/