数据结构---图---最小生成树---4

一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。

我们把构造连通网的最小代价生成树称为最小生成树(MinimumCostSpanningTree)

找连通网的最小生成树,经典的算法有两种:普里姆算法克鲁斯卡尔算法

普里姆算法:适合稠密图,边数非常多的情况
克鲁斯卡尔算法:适合稀疏图,边数少效率会非常高

1.普里姆(Prim)算法:以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树
在这里插入图片描述

首先:有一个权值数组(初始化为最大值)、一个索引数组(初始化为0,表示从第一个顶点开始)
总的思想:先从一个顶点开始,读取对应顶点那一行的权值(该顶点和那些点连通),找最小权值,找到该顶点对应的邻接点,然后在读取该邻接点那一行的权值(权值数组只保留最小值,权值为0表示该点已经是最小生成树中的点,不再参与比较,索引数组更新为该邻接点的下标)

//测试数据
int[] vertexs = new int[] { 0,1,2,3,4,5,6,7,8};
int[,] edges = new int[,]
{
    //0     1       2       3       4     5       6       7     8    
    { 0,    10,     MAX,    MAX,    MAX,  11,     MAX,    MAX,  MAX, },     //0
    { 10,   0,      18,     MAX,    MAX,  MAX,    16,     MAX,  12,  },     //1
    { MAX,  18,     0,      22,     MAX,  MAX,    MAX,    MAX,  8,   },     //2
    { MAX,  MAX,    22,     0,      20,   MAX,    24,     16,   21,  },     //3                                                                               
    { MAX,  MAX,    MAX,    20,     0,    26,     MAX,    7,    MAX, },     //4
    { 11,   MAX,    MAX,    MAX,    26,   0,      17,     MAX,  MAX, },     //5
    { 16,   MAX,    MAX,    24,     MAX,  17,     0,      19,   MAX, },     //6   
    { MAX,  MAX,    MAX,    16,     7,    MAX,    19,      0,   MAX, },     //7   
    { MAX,   12,     8,     21,     MAX,  MAX,    MAX,     MAX, 0,   },     //8   
};

过程:
从第一个顶点开始V0,即数组第一个,遍历从数组索引1开始:
1.先把第一个顶点放入生成树里,所以从读取第一行的权值数据,索引数组全为0,因为是V0顶点到其它顶点的边
	  { 0,    10,     MAX,    MAX,    MAX,  11,     MAX,    MAX,  MAX, },   //weight  0
      { 0,    0,      0,      0,      0,    0,      0,      0,    0, },     //index
      //遍历数组得:min=10, 对应的边:0-1(V0-V1)
2.然后读取顶点V1那一行(第二行)的权值数据放入数组,只放入比数组现有值小的权值,然后修改索引数组的值为相应的下标,从哪一行读取的,下标就是那一行的下标,数组中为0的位置表示已经是最小生成树里的点了,不再参与。
      { 0,    0,     18,    MAX,    MAX,   11,      16,    MAX,   12, },   //weight 
      { 0,    0,      1,      0,      0,    0,      1,      0,    1, },     //index
      //min=11 0-5 (11在数组中下标5,同样下标中的索引数组的值是0,所以是0-5) 读取顶点V5的数据放入数组
      { 0,    0,      18,     MAX,    26,   0,     16,    MAX,   12, },   //weight  
      { 0,    0,      1,      0,      0,    5,      1,      0,    1, },     //index
      //min=12 1-8	读取顶点V8的数据放入数组
      { 0,    0,      8,     21,    26,   0,     16,    MAX,    0, },   //weight  
      { 0,    0,      8,      8,    0,    5,      1,      0,    1, },     //index
      //min=8 8-2	读取顶点V2的数据放入数组
      { 0,    0,      0,     21,    26,   0,     16,    MAX,    0, },   //weight  
      { 0,    0,      8,      8,    0,    5,      1,      0,    1, },     //index
      //min=16 1-6	读取顶点V6的数据放入数组
      { 0,    0,      0,     21,    26,   0,      0,     19,    0, },   //weight  
      { 0,    0,      8,      8,    0,    5,      1,      6,    1, },     //index
      //min=19 6-7	读取顶点V7的数据放入数组
      { 0,    0,      0,     16,    7,   0,      0,      0,    0, },   //weight  
      { 0,    0,      8,      7,    7,    5,      1,      6,    1, },     //index
      //min=7 7-4	读取顶点V4的数据放入数组
      { 0,    0,      0,     16,    0,   0,      0,      0,    0, },   //weight  
      { 0,    0,      8,      7,    7,    5,      1,      6,    1, },     //index
      //min=16 7-3	
      { 0,    0,      0,      0,    0,   0,      0,      0,    0, },   //weight  
      { 0,    0,      8,      7,    7,    5,      1,      6,    1, },     //index
  	/// <summary>
    /// 普里姆算法:最小生成树
    /// </summary>    
    public void MiniSpanTree_Prim()
    {
        int minWeight, i, j, minWeightIndex;
        int[] adjvex = new int[vertexs.Length];    //保存相关顶点下标
        int[] lowWeight = new int[vertexs.Length];   //保存相关顶点间边的权值
        lowWeight[0] = 0;     //初始化第一个权值为0,即V0加入生成树,lowCost的值为0,在这里就是此下标的顶点已经加入生成树
        adjvex[0] = 0;        //初始化第一个顶点下标为0,从顶点V0开始

        //读取第一行,初始化
        for (i = 1; i < vertexs.Length; i++)   //循环除下标为0外的全部顶点
        {
            lowWeight[i] = edges[0, i];     //将V0顶点与之有边的权值存入数组
            adjvex[i] = 0;                  //初始化都为V0的下标
        }

        //构造最小生成树
        for (i = 1; i < vertexs.Length; i++)
        {
            minWeight = int.MaxValue;   //初始化最小权值为极大值,通常设置为不可能的大数字,如:32765、65535等
            j = 1;                      //顶点下标循环变量
            minWeightIndex = 0;         //用来存储最小权值的顶点下标

            //遍历每一行的数据
            while (j < vertexs.Length)
            {
                if (lowWeight[j] != 0 && lowWeight[j] < minWeight)
                {
                    minWeight = lowWeight[j];       //让当前权值成为最小值
                    minWeightIndex = j;             //将当前最小值的下标存入k
                }
                j++;
            }

            Debug.Log("打印当前顶点边中权值最小边:" + adjvex[minWeightIndex] + "---" + minWeightIndex);

            lowWeight[minWeightIndex] = 0;     //将当前顶点的权值设置为0,表示此顶点已经完成任务

            for (j = 1; j < vertexs.Length; j++)       //循环所有顶点
            {
                if (lowWeight[j] != 0 && edges[minWeightIndex, j] < lowWeight[j])
                {
                    //若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值
                    lowWeight[j] = edges[minWeightIndex, j];         //将较小权值存入lowCost
                    adjvex[j] = minWeightIndex;                      //将下标为k的顶点存入adjvex
                }
            }
        }
    }

在这里插入图片描述
2.克鲁斯卡尔(Kruskal)算法:以边为目标去构建,因为权值在边上,直接去找最小权值的边来构建生成树也是很自然的想法,只不过构建时要考虑是否会形成环路而已。

用到图的存储结构中的边集数组结构

	/// <summary>
    /// 克鲁斯卡尔算法:最小生成树
    /// </summary>
    /// <param name="graph">邻接矩阵</param>
    public void MiniSpanTree_Kruskal()
    {
        int i, n, m;       
        int[] parent = new int[vertexArr.Length];   //定义一数组用来判断边与边是否形成环路

        for (i = 0; i < vertexArr.Length; i++)
        {
            parent[i] = 0;      //初始化数组值为0
        }

        for (i = 0; i < edgeList.Count; i++)     //循环每一条边
        {
            n = Find(parent, edgeList[i].begin);
            m = Find(parent, edgeList[i].end);

            if (n != m)     //假如n与m不等,说明此边没有与现有生成树形成环路
            {
                parent[n] = m;  //将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中

                Debug.Log("(" + edgeList[i].begin + "," + edgeList[i].end + ") " + edgeList[i].weight);
            }
        }
    }

    //查找连线顶点的尾部下标
    private int Find(int[] parent, int f)
    {
        while (parent[f] > 0)
            f = parent[f];
        return f;
    }  

在这里插入图片描述
完整代码:

邻接矩阵最小生成树:看最后的一部分代码即可

/// <summary>
/// 泛型邻接矩阵
/// </summary>
public class AdjacentMatrix<T>
{
    #region 数据
    //数据
    private T[] vertexs;        //顶点数组:数据
    private int[,] edges;       //边数组:权值  
    private int maxNumVertex;   //最大顶点数                              
    #endregion

    #region 操作

    /// <summary>
    /// 创建无向网图的邻接矩阵表示
    /// </summary>    
    /// <param name="vertexs">顶点数据</param>
    /// <param name="edges">边数据:权值</param>   
    public AdjacentMatrix(T[] vertexs, int[,] edges)
    {
        maxNumVertex = vertexs.Length;
        this.vertexs = new T[maxNumVertex];
        this.edges = new int[maxNumVertex, maxNumVertex];
        Array.Copy(vertexs, this.vertexs, vertexs.Length);
        Array.Copy(edges, this.edges, edges.Length);
    }

    /// <summary>
    /// 创建无向网图的邻接矩阵表示
    /// </summary>    
    /// <param name="vertexs">顶点数据</param>
    /// <param name="edges">边数据:权值</param>
    /// <param name="maxNumVertex">最大顶点数组容量</param>
    public AdjacentMatrix(T[] vertexs, int[,] edges, int maxNumVertex)
    {
        maxNumVertex = maxNumVertex < vertexs.Length ? vertexs.Length : maxNumVertex;

        this.vertexs = new T[maxNumVertex];
        this.edges = new int[maxNumVertex, maxNumVertex];
        this.maxNumVertex = maxNumVertex;
        Array.Copy(vertexs, this.vertexs, vertexs.Length);
        Array.Copy(edges, this.edges, edges.Length);
    }

    private bool[] visited;

    /// <summary>
    /// 邻接矩阵的深度优先遍历
    /// </summary>
    /// <param name="gl"></param>
    public void DFSTraverse()
    {
        visited = new bool[maxNumVertex];

        for (int i = 0; i < vertexs.Length; i++)
        {
            visited[i] = false; //初始化所有顶点都是未访问的状态
        }

        for (int i = 0; i < vertexs.Length; i++)
        {
            //对未访问过的顶点调用DFS,若是连通图,只会执行一次
            if (!visited[i])
            {
                DFS(i);
            }
        }
    }

    /// <summary>
    /// 邻接矩阵的深度优先递归算法
    /// </summary>
    /// <param name="gl"></param>
    /// <param name="i"></param>
    private void DFS(int i)
    {
        visited[i] = true;

        //这里对顶点的操作,这里简单的打印
        Debug.Log(vertexs[i]);

        for (int j = 0; j < vertexs.Length; j++)
        {
            //edges[i, j] == 1表示两顶点是连通的
            //如果是权值,可以判断: edges[i,j]!=0 && edges[i,j]! = MaxValue
            //MaxValue自己定,可以用int.MaxValue,long.MaxValue,float.MaxValue等
            //具体权值用int long 还是float自己定
            if (edges[i, j] == 1 && !visited[j])
            {
                DFS(j);
            }
        }
    }

    /// <summary>
    /// 邻接矩阵:广度优先遍历
    /// </summary>
    /// <param name="graph"></param>
    public void BFSTraverse()
    {
        Queue<int> queue = new Queue<int>();  //初始化辅助队列
        visited = new bool[maxNumVertex];

        for (int i = 0; i < vertexs.Length; i++)
        {
            visited[i] = false;
        }

        for (int i = 0; i < vertexs.Length; i++)   //对每一个顶点做循环
        {
            if (!visited[i])            //若是未访问过就处理
            {
                visited[i] = true;      //设置当前顶点访问过

                //这里对顶点的操作,这里简单的打印
                Debug.Log(vertexs[i]);

                queue.Enqueue(i);       //将此顶点入队列

                while (queue.Count > 0) //当前队列有元素
                {
                    i = queue.Dequeue();    //出队列

                    for (int j = 0; j < vertexs.Length; j++)
                    {
                        //edges[i, j] == 1表示两顶点是连通的
                        //如果是权值,可以判断: edges[i,j]!=0 && edges[i,j]! = MaxValue
                        //MaxValue自己定,可以用int.MaxValue,long.MaxValue,float.MaxValue等
                        //具体权值用int long 还是float自己定
                        if (edges[i, j] == 1 && !visited[j])
                        {
                            visited[j] = true;

                            //这里对顶点的操作,这里简单的打印
                            Debug.Log(vertexs[j]);

                            queue.Enqueue(j);
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// 普里姆算法:最小生成树
    /// </summary>    
    public void MiniSpanTree_Prim()
    {
        int minWeight, i, j, minWeightIndex;
        int[] adjvex = new int[vertexs.Length];    //保存相关顶点下标
        int[] lowWeight = new int[vertexs.Length];   //保存相关顶点间边的权值
        lowWeight[0] = 0;     //初始化第一个权值为0,即V0加入生成树,lowCost的值为0,在这里就是此下标的顶点已经加入生成树
        adjvex[0] = 0;        //初始化第一个顶点下标为0,从顶点V0开始

        //读取第一行,初始化
        for (i = 1; i < vertexs.Length; i++)   //循环除下标为0外的全部顶点
        {
            lowWeight[i] = edges[0, i];     //将V0顶点与之有边的权值存入数组
            adjvex[i] = 0;                  //初始化都为V0的下标
        }

        //构造最小生成树
        for (i = 1; i < vertexs.Length; i++)
        {
            minWeight = int.MaxValue;   //初始化最小权值为极大值,通常设置为不可能的大数字,如:32765、65535等
            j = 1;                      //顶点下标循环变量
            minWeightIndex = 0;         //用来存储最小权值的顶点下标

            //遍历每一行的数据
            while (j < vertexs.Length)
            {
                if (lowWeight[j] != 0 && lowWeight[j] < minWeight)
                {
                    minWeight = lowWeight[j];       //让当前权值成为最小值
                    minWeightIndex = j;             //将当前最小值的下标存入k
                }
                j++;
            }

            Debug.Log("打印当前顶点边中权值最小边:" + adjvex[minWeightIndex] + "---" + minWeightIndex);

            lowWeight[minWeightIndex] = 0;     //将当前顶点的权值设置为0,表示此顶点已经完成任务

            for (j = 1; j < vertexs.Length; j++)       //循环所有顶点
            {
                if (lowWeight[j] != 0 && edges[minWeightIndex, j] < lowWeight[j])
                {
                    //若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值
                    lowWeight[j] = edges[minWeightIndex, j];         //将较小权值存入lowCost
                    adjvex[j] = minWeightIndex;                      //将下标为k的顶点存入adjvex
                }
            }
        }
    }
}

边集数组:

/// <summary>
/// 边集数组
/// </summary>
public class EdgeArrary<T>
{
    public class Edge : IComparable
    {
        public int begin;
        public int end;
        public int weight;

        public int CompareTo(object obj)
        {
            Edge temp = obj as Edge;

            if (weight < temp.weight)
            {
                return -1;
            }
            else if (weight > temp.weight)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

    private T[] vertexArr;
    private List<Edge> edgeList;    //采用List方便

    /// <summary>
    /// 创建边集数组
    /// </summary>
    /// <param name="vertexData">顶点数据</param>
    /// <param name="edgeData">边数据</param>
    public EdgeArrary(T[] vertexData, Edge[] edgeData)
    {
        vertexArr = vertexData;
        edgeList = new List<Edge>();
        edgeList.AddRange(edgeData);
    }

    /// <summary>
    /// 创建边集数组
    /// </summary>
    /// <param name="vertexData">顶点数据</param>
    /// <param name="edges">邻接矩阵</param>
    public EdgeArrary(T[] vertexData, int[,] edges)
    {
        vertexArr = vertexData;
        edgeList = new List<Edge>();

        //把邻接矩阵转换为边集数组
        for (int i = 0; i < vertexData.Length; i++)
        {
            for (int j = 0; j < vertexData.Length; j++)
            {
                if (edges[i, j] != 0 && edges[i, j] != int.MaxValue)
                {
                    edgeList.Add(new Edge() { begin = i, end = j, weight = edges[i, j] });
                }
            }
        }
        edgeList.Sort();	//从小到大排序
    }

    /// <summary>
    /// 克鲁斯卡尔算法:最小生成树
    /// </summary>
    /// <param name="graph">邻接矩阵</param>
    public void MiniSpanTree_Kruskal()
    {
        int i, n, m;       
        int[] parent = new int[vertexArr.Length];   //定义一数组用来判断边与边是否形成环路

        for (i = 0; i < vertexArr.Length; i++)
        {
            parent[i] = 0;      //初始化数组值为0
        }

        for (i = 0; i < edgeList.Count; i++)     //循环每一条边
        {
            n = Find(parent, edgeList[i].begin);
            m = Find(parent, edgeList[i].end);

            if (n != m)     //假如n与m不等,说明此边没有与现有生成树形成环路
            {
                parent[n] = m;  //将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中

                Debug.Log("(" + edgeList[i].begin + "," + edgeList[i].end + ") " + edgeList[i].weight);
            }
        }
    }

    //查找连线顶点的尾部下标
    private int Find(int[] parent, int f)
    {
        while (parent[f] > 0)
            f = parent[f];
        return f;
    }   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值