数据结构算法爬坑日记十八

贪心算法

思想:使用贪心算法进行求解时,也是将一个大问题分为若干小问题进行求解,在求解时,每一步都使用当前问题
的最优解,从而希望最终的结果也是最优的. 贪心算法得到的结果不一定是最优的结果,但都是与最优解接近的
结果

求解集合覆盖问题
问题描述:有若干个电台,每个电台覆盖若干个地区,找出覆盖所有地区的最少电台组合,如果使用穷举法,在电台
数量大时,组合的复杂度就是指数级,效率有限

代码实现
//贪心算法求解集合覆盖
public class Greedy {
    public static void main(String[] args) {
        HashMap<String, HashSet<String>> radios = new HashMap<>();//存放所有的电台
        //创建五个电台并添加每个电台覆盖的地区
        HashSet<String> k1 = new HashSet<>();
        HashSet<String> k2 = new HashSet<>();
        HashSet<String> k3 = new HashSet<>();
        HashSet<String> k4 = new HashSet<>();
        HashSet<String> k5 = new HashSet<>();
        k1.add("北京");
        k1.add("上海");
        k1.add("天津");
        k2.add("广州");
        k2.add("北京");
        k2.add("深圳");
        k3.add("成都");
        k3.add("上海");
        k3.add("杭州");
        k4.add("上海");
        k4.add("天津");
        k5.add("杭州");
        k5.add("大连");
        radios.put("k1", k1);
        radios.put("k2", k2);
        radios.put("k3", k3);
        radios.put("k4", k4);
        radios.put("k5", k5);
        List<String> results = new ArrayList<>();//存放已选择的电台
        Set<String> allAreas = new HashSet<>();//存放所有地区
        //将所有地区加入到集合中
        Set<String> strings = radios.keySet();
        for (String string : strings) {
            allAreas.addAll(radios.get(string));
        }
        Set<String> temp = new HashSet<>();//存放当前电台覆盖未覆盖的地区
        String maxKey;//每次遍历覆盖最多未覆盖地区电台的key值
        int maxSetSize;//存放最大key值电台覆盖未覆盖地区的数量
        while (allAreas.size() > 0) {
            maxKey = null;
            maxSetSize = 0;//将最大key值覆盖未覆盖地区数量重置
            for (String key : strings) {
                temp.clear();//每次进行新的遍历前将原电台的信息清空
                //求出当前电台覆盖未覆盖的地区
                Set<String> areas = radios.get(key);
                temp.addAll(areas);
                temp.retainAll(allAreas);
                //如果当前电台覆盖未覆盖地区数不为0并且大于最大key值的覆盖未覆盖地区的数量,
                // 或者此时最大key值未定,就将当前电台key值当初最大key
                if (temp.size() > 0 && (maxKey == null || temp.size() > maxSetSize)) {
                    maxKey = key;
                    maxSetSize = temp.size();//调换最大key值电台覆盖未覆盖地区的数量的值
                }
            }
            //将已覆盖地区从全部地区中删除并记录已选择电台加入
            allAreas.removeAll(radios.get(maxKey));
            results.add(maxKey);
        }
        //查看此次电台的选择
        System.out.println(results);
    }
}

Prim算法

基于贪心算法解决最小生成树问题
修路问题:
问题描述:有若干个村庄,已知任意两条村庄直接的路的长度,相同距离修路价格一定,求如何修路可使价格最少
并且所有的村庄都能互相到达
思路:将若干村庄可以看出一张网图,问题的解就是求该图的最小生成树,利用prim算法从一个顶点开始先找出该
点所有与之相连的顶点之间的边的最小权值的点将两点相连,再找与该两点相连的点的最小权值边再将此点与之
相连,循环此操作直到将所以的点都连上问题即可解答

代码实现
 ** 图类
 //prim图
public class PrimGraph {
    int vertexes;//图的顶点数
    char[] data;//存放顶点数据
    int[][] weight;//邻接矩阵

    public PrimGraph(int vertexes) {
        this.vertexes = vertexes;
        data = new char[vertexes];
        weight = new int[vertexes][vertexes];
    }
}

 ** 最小生成树类
 //图的最小生成树
public class MinTree {

    //初始化原始图
    public void minTree(PrimGraph graph, char[] data, int[][] weight) {
        graph.vertexes = data.length;
        graph.data = data;
        graph.weight = weight;
    }

    //prim算法求最小生成树 参数为原始图信息和开始节点
    public void prim(PrimGraph graph, int startIndex) {
        int minValue = 100;//存放最短边权值
        //记录最短路径时候对应邻接矩阵的行和列
        int row = -1;
        int column = -1;
        int[] isVisited = new int[graph.vertexes];//存放顶点是否被访问过
        isVisited[startIndex] = 1;//将开始节点标识为被访问过
        //此层循环控制顶点的遍历,需要进行总顶点数-1次遍历
        for (int k = 1; k < graph.vertexes; k++) {
            //此层循环控制已访问顶点的遍历
            for (int i = 0; i < graph.vertexes; i++) {
                if (isVisited[i] == 1) { //当外层循环到已访问顶点时进行与未访问顶点进行匹配
                    //此层循环控制未访问顶点的遍历
                    for (int j = 0; j < graph.vertexes; j++) {
                        if (isVisited[j] == 0) {//当内层循环到未访问顶点时与外层进行匹配
                            if (graph.weight[i][j] < minValue) {
                                //找到之后保存该位置的信息
                                minValue = graph.weight[i][j];
                                row = i;
                                column = j;
                            }
                        }
                    }
                }
            }
            System.out.println("连接顶点" + graph.data[row] + "和顶点" + graph.data[column] + " 权值为" + minValue);
            isVisited[column] = 1;//找到之后将该点标志为以访问
            minValue = 100;
        }
    }
}

 ** 测试类
 public class PrimTest {
    public static void main(String[] args) {
        char[] data = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int vertexes = data.length;
        int[][] weight = new int[][]{
                {100, 5, 7, 100, 100, 100, 2},
                {5, 100, 100, 9, 100, 100, 3},
                {7, 100, 100, 100, 8, 100, 100},
                {100, 9, 100, 100, 100, 7, 100},
                {100, 100, 8, 100, 100, 5, 4},
                {100, 100, 100, 4, 5, 100, 6},
                {2, 3, 100, 100, 4, 6, 100}
        };
        MinTree minTree = new MinTree();
        PrimGraph graph = new PrimGraph(vertexes);
        minTree.minTree(graph, data, weight);
        minTree.prim(graph, 0);
    }
}

Kruskal算法

基于贪心算法解决最小生成树问题
与prim算法可以解决同样的问题,prim算法是基于顶点,每次选出最优的一点的边,kruskal是基于边,每次选出
所有边的最优边,基本思路是先将所有边依次从小到大排序进选择,把所有顶点看成一个森林,每次遍历查看是否
符合将边添加入森林的条件(就是判断是否与已知最小生成树内的边形成回路),循环此操作直到将所以顶点连起
来形成一棵树,这棵树便是该图的最小生成树
判断回路的思路是判断将要添加的边的两顶点在已知最小生成树中是否具有相同的顶点

代码实现
 ** 边的信息
 //边的信息,记录边的两个顶点和边的权值
public class EdgeInfo {
    char start;
    char end;
    int weight;

    public EdgeInfo(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "<"+start+","+end+","+weight+">";
    }
}

** Kruskal类
//kruskal实现
public class Kruskal {
    int edgeNum;//边的个数
    char[] vertex;//顶点数组
    int[][] commons;//邻接矩阵

    public static void main(String[] args) {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int[][] commons = new int[][]{ //100表示两点无连接
                {0, 12, 100, 100, 100, 16, 14},
                {12, 0, 10, 100, 100, 7, 100},
                {100, 10, 0, 3, 5, 6, 100},
                {100, 100, 3, 0, 4, 7, 100},
                {100, 100, 5, 4, 0, 2, 8},
                {16, 7, 6, 100, 2, 0, 9},
                {14, 100, 100, 100, 8, 9, 0}
        };
        Kruskal myKruskal = new Kruskal(vertex, commons);
        myKruskal.kruskal();
    }

    public Kruskal(char[] vertex, int[][] commons) {
        this.vertex = vertex;
        this.commons = commons;
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (commons[i][j] != 100) {
                    edgeNum++;
                }
            }
        }
    }

    //对边进行排序
    public void sort(EdgeInfo[] edgeInfos) {
        for (int i = 0; i < edgeInfos.length - 1; i++) {
            for (int j = 0; j < edgeInfos.length - 1 - i; j++) {
                if (edgeInfos[j].weight > edgeInfos[j + 1].weight) {
                    EdgeInfo temp = edgeInfos[j + 1];
                    edgeInfos[j + 1] = edgeInfos[j];
                    edgeInfos[j] = temp;
                }
            }
        }
    }

    //获取指定顶点下标
    public int getVertexIndex(char info) {
        for (int i = 0; i < vertex.length; i++) {
            if (vertex[i] == info) {
                return i;
            }
        }
        return -1;
    }

    //获取图中边的数组
    public EdgeInfo[] getEdgeInfos() {
        int index = 0;
        EdgeInfo[] e = new EdgeInfo[edgeNum];
        for (int i = 0; i < commons.length; i++) {
            for (int j = i + 1; j < commons[0].length; j++) {
                if (commons[i][j] != 100) {
                    e[index] = new EdgeInfo(vertex[i], vertex[j], commons[i][j]);
                    index++;
                }
            }
        }
        return e;
    }

    //获取顶点在当前最小生成树类的终点下标
    // ends为当前最小生成树的所有顶点终点下标,i为需要查找的顶点下标
    public int getEnd(int[] ends, int i) {
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;
    }

    //求最小生成树
    public void kruskal() {
        int index = 0;//结果边的索引
        EdgeInfo[] res = new EdgeInfo[vertex.length - 1];//结果边集
        int[] ends = new int[vertex.length];//存放当前最小生成树的终点集
        EdgeInfo[] edges = getEdgeInfos();//所以边
        sort(edges);
        //依次遍历所以边查看是否可以添加
        for (int i = 0; i < edgeNum; i++) {
            //获取该边的起始点和结束点,因为在向边集中添加边时
            // 我们总是将下标小的放在前,所以可直接确定开始和结束点
            int start = getVertexIndex(edges[i].start);
            int end = getVertexIndex(edges[i].end);
            //获得两点在当前生成树中的终点下标
            int startEndIndex = getEnd(ends, start);
            int endEndIndex = getEnd(ends, end);
            if (startEndIndex != endEndIndex) {
                //当前两点未构成回路,加入结果边集并更新终点集
                ends[startEndIndex] = endEndIndex;
                res[index] = edges[i];
                index++;
            }
        }
        System.out.println("结果为 " + Arrays.toString(res));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值