首页 > 编程语言 >贪心算法

贪心算法

时间:2023-12-09 22:26:11浏览次数:27  
标签:10000 int vertex 算法 顶点 new INF 贪心

1.贪心算法

1.电台覆盖区域求最优解问题

题目:假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号
广播台 覆盖地区
K1 “北京”, “上海”, “天津”
K2 “广州”, “北京”, “深圳”
K3 “成都”, “上海”, “杭州”
K4 “上海”, “天津”
K5 “杭州”, “大连”
/**
 * @author 缪广亮
 * @version 1.0
 */
public class GreedyAlgorithm {
    public static void main(String[] args) {
//        创建广播电台map
        HashMap<String, HashSet<String>> broadcasts = new HashMap<>();
//        将各个电台放入到broadcasts
        HashSet<String> hashSet1 = new HashSet<>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");
        HashSet<String> hashSet2 = new HashSet<>();
        hashSet2.add("北京");
        hashSet2.add("广州");
        hashSet2.add("深圳");
        HashSet<String> hashSet3 = new HashSet<>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");
        HashSet<String> hashSet4 = new HashSet<>();
        hashSet4.add("上海");
        hashSet4.add("天津");
        HashSet<String> hashSet5 = new HashSet<>();
        hashSet5.add("杭州");
        hashSet5.add("大连");

//        将各电台放入map
        broadcasts.put("K1", hashSet1);
        broadcasts.put("K2", hashSet2);
        broadcasts.put("K3", hashSet3);
        broadcasts.put("K4", hashSet4);
        broadcasts.put("K5", hashSet5);

//        allAreas存放所有的地区
        HashSet<String> allAreas = new HashSet<>();
        for (HashSet<String> areas : broadcasts.values())
//            broadcasts.values()返回的是values的一个集合,而每一个value都是一个集合用addAll
            allAreas.addAll(areas);
//        存放选择的电台集合
        ArrayList<String> selects = new ArrayList<>();
//        临时的集合,在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
        HashSet<String> tempSet = new HashSet<>();
//        定义maxKey,保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
//        如果maxKey不为null,则就会加入到selects
        String maxKey = null;
        while (allAreas.size() > 0) {//allAreas不为0,则表示还没有覆盖到所有地区
//            每次while将maxKey置空
            maxKey=null;
            for (String key : broadcasts.keySet()) {
//                每次for将tempSet清空
                tempSet.clear();
//            当前这个key能够覆盖的地区
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
//            retainAll方法是求出tempSet和allAreas集合的交集,交集会赋给tempSet
                tempSet.retainAll(allAreas);
//            如果当前集合包含的未覆盖的地区的数量,,比maxKey指向的集合地区还要多
//            重置maxKey
//                tempSet.size() > broadcasts.get(maxKey).size()) 体现贪心算法的特点 每次选择最优的
                if (tempSet.size() > 0 &&
                        (maxKey == null || tempSet.size() > broadcasts.get(maxKey).size()))
                    maxKey = key;

            }
//            maxKey不为空,就应该将maxKey加入到selects
            if (maxKey!=null){
                selects.add(maxKey);
//                将maxKey指向的广播电台覆盖的地区,从allAreas移除
                allAreas.removeAll(broadcasts.get(maxKey));
            }
        }
        System.out.println("选择的结果:"+selects);
    }
}

2.普利姆算法(prim)

应用场景-修路问题

最小生成树

修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称 MST。 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树

  1. N 个顶点,一定有 N-1 条边
  2. 包含全部顶点
  3. N-1 条边都在图中
  4. 求最小生成树的算法主要是普里姆算法和克鲁斯卡尔算法

普利姆算法的图解分析:
1.从A顶点开始处理<A,G>

A-C[7] A-G[2] A-B[5]=>
2.<A,G>开始,将A和G 顶点和他们相邻的还没有访问的顶点进行处理=》<A,G,B> A-C[7] A-B[5] G-B[3] G-E[4] G-F[6]
3.<A,G,B>开始,将A,G,B顶点和他们相邻的还没有访问的顶点进行处理=<A,G,B,E> A-CI7] G-E[4] G-F[6] B-D[9]
......
4.{A,G,B,E}->F//第4次大循环 ,对应边<E,F>权值:5

5.{A,G,B,E,F}->D//第5次大循环,对应边<F,D>权值: 4

6.[A,G,B,E,F,D}->C//第6次大循环,对应边<A,C权值: 7

/**
 * @author 缪广亮
 * @version 1.0
 */
public class PrimAlgorithm {
    public static void main(String[] args) {
        char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int vertex = data.length;
//        邻接矩阵的关系使用二维数组表示,10000表示两个点不连通
        int[][] weight = new int[][]{
                {10000, 5, 7, 10000, 10000, 10000, 2},
                {5, 10000, 10000, 9, 10000, 10000, 3},
                {7, 10000, 10000, 10000, 8, 10000, 10000},
                {10000, 9, 10000, 10000, 10000, 4, 10000},
                {10000, 10000, 8, 10000, 10000, 5, 4},
                {10000, 10000, 10000, 4, 5, 10000, 6},
                {2, 3, 10000, 10000, 4, 6, 10000}};
        MGraph graph = new MGraph(vertex);
        MinTree minTree = new MinTree();
        minTree.createGraph(graph, vertex, data, weight);
        minTree.showGraph(graph);
        minTree.prim(graph,0);


    }
}

//创建最小成树
class MinTree {
//    创建邻接矩阵

    /**
     * @param graph  图对象
     * @param vertex 顶点个数
     * @param data   各个顶点的值
     * @param weight 邻接矩阵
     */
    public void createGraph(MGraph graph, int vertex, char[] data, int[][] weight) {
        for (int i = 0; i < vertex; i++) {
            graph.data[i] = data[i];
            for (int j = 0; j < vertex; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }
//    prim算法

    /**
     * @param graph 图
     * @param v     从第几个顶点开始
     */
    public void prim(MGraph graph, int v) {
//        标记顶点是否已经被访问过
        int[] isVisited = new int[graph.vertex];
//        将该数组初始化
//        将当前这个顶点标记为已访问
        isVisited[v] = 1;
//        h1和h2记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000;//先将该值初始化成大值,在后面遍历会被替换
        for (int k = 1; k < graph.vertex; k++) {//k从1开始,因为prim算法是graph.vertex-1条边
//            这个是确定每一次生成的子图,哪个顶点和这次遍历的结点距离最近
            for (int i = 0; i < graph.vertex; i++) {//i结点表示被访问过的结点
                for (int j = 0; j < graph.vertex; j++) {//j结点表示还没有访问过的结点
                    if (isVisited[i] == 1 && isVisited[j] == 0 && graph.weight[i][j] < minWeight) {
//                        替换minWeight(寻找已经访问过的结点和未访问过的节点间的权值最小的边 )
                        minWeight = graph.weight[i][j];
                        //记录最小值的下标i、j
                        h1 = i;
                        h2 = j;
                    }
                }
            }
//            找到一条边是最小
            System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
//            将当前这个结点标记为已访问
            isVisited[h2] = 1;
//            minWeight重置
            minWeight = 10000;

        }
    }

    //    显示邻接矩阵
    public void showGraph(MGraph graph) {
        for (int[] link : graph.weight) {
            System.out.println(Arrays.toString(link));
        }
    }
}

class MGraph {
    int vertex;
    char[] data;//存放结点的数组
    int[][] weight;//存放边,邻接矩阵

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

3.克鲁斯卡尔(kruskal)

基本介绍

克鲁斯卡尔(Kruskal)算法,求加权连通图最小生成树的算法

基本思想:按权值从小到大顺序选择n-1条边,保证n-1条边不够成回路

具体做法:先构造一个只有n顶点的森林,然后按权值从小到大连通网中选择边加入森林中,保证森林不产生回路,直到森林变为一棵树为止

/**
 * @author 缪广亮
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class KruskalCase {
    private int edgeNum;
    private char[] vertex;
    private int[][] matrix;
    //    使用INF表示两个顶点不能联通
    private static final int INF = Integer.MAX_VALUE;

    public static void main(String[] args) {
        char[] vertex = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int matrix[][] = {
                /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
                /*A*/ {0, 12, INF, INF, INF, 16, 14},
                /*B*/ {12, 0, 10, INF, INF, 7, INF},
                /*C*/ {INF, 10, 0, 3, 5, 6, INF},
                /*D*/ {INF, INF, 3, 0, 4, INF, INF},
                /*E*/ {INF, INF, 5, 4, 0, 2, 8},
                /*F*/ {16, 7, 6, INF, 2, 0, 9},
                /*G*/ {14, INF, INF, INF, 8, 9, 0}};
        KruskalCase kruskalCase = new KruskalCase(vertex, matrix);
        kruskalCase.printMatrix();
        EData[] edges = kruskalCase.getEdges();
        kruskalCase.sortEdges(edges);
        System.out.println(Arrays.toString(edges));
        kruskalCase.kruskal();
    }

    public KruskalCase(char[] vertex, int[][] matrix) {
//        初始化顶点和边的个数
        int vLen = vertex.length;
//        初始化顶点,复制拷贝的方式
        this.vertex = new char[vLen];
        for (int i = 0; i < vLen; i++) {
            this.vertex[i] = vertex[i];
        }
//        初始化边
        this.matrix = new int[vLen][vLen];
        for (int i = 0; i < vLen; i++) {
            for (int j = 0; j < vLen; j++) {
                this.matrix[i][j] = matrix[i][j];
            }
        }
//        统计边
        for (int i = 0; i < vLen; i++) {
            for (int j = i + 1; j < vLen; j++) {
                if (this.matrix[i][j] != INF) {
                    edgeNum++;
                }
            }
        }
    }

    //    打印邻接矩阵
    public void printMatrix() {
        System.out.println("邻接矩阵为:\n");
        for (int i = 0; i < vertex.length; i++) {
            for (int j = 0; j < vertex.length; j++) {
                System.out.printf("%12d", matrix[i][j]);
            }
            System.out.println();
        }
    }
    public void kruskal(){
        int index=0;//表示最后结果数组的索引
//        用于保存“已有最小生成树”中每个顶点在最小生成树中的终点
        int[] ends=new int[edgeNum];
//        创建结果数组,保存最后的最小生成树
        EData[] res = new EData[edgeNum];
//        获取图中所有的边集合
        EData[] edges = getEdges();
//        按照边的权值大小进行排序
        sortEdges(edges);
//        遍历edges数组,将边添加到最小生成树中时,判断准备加入的边是否形成了回路
        for (int i = 0; i < edgeNum; i++) {
//            获取到第i条边的第一个顶点(起点)
            int p1=getPosition(edges[i].start);//p1=4
//            获取到第i条边的第二个顶点(终点)
            int p2=getPosition(edges[i].end);//p2=5
//            获取p1这个顶点在已有最小生成树中的终点
            int m=getEnd(ends,p1);//m=4
//            获取p2这个顶点在已有最小生成树中的终点
            int n=getEnd(ends,p2);//n=5
//            是否构成回路
            if (m!=n) {//没有构成回路
                ends[m] = n;//设置m在“已有最小生成树”中的终点
                res[index++] = edges[i];//有一条边加入到res数组
            }
        }
//        统计并打印最小生成树,输出res
        System.out.println("最小生成树为");
        for (int i = 0; i < index; i++) {
            System.out.print(res[i]+" ");
        }

    }

    /**
     * 对边进行排序处理,冒泡排序
     *
     * @param edges 边的集合
     */
    public void sortEdges(EData[] edges) {
        for (int i = 0; i < edges.length - 1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight > edges[j + 1].weight) {
                    EData temp = edges[j];
                    edges[j] = edges[j + 1];
                    edges[j + 1] = temp;
                }
            }
        }
    }

    /**
     * @param ch 顶点的值,比如'A'
     * @return 返回ch顶点对应的下标,否则-1
     */
    public int getPosition(char ch) {
        for (int i = 0; i < vertex.length; i++) {
            if (vertex[i] == ch)
                return i;
        }
        return -1;
    }

    /**
     * 获取图中的边,放到EData[]数组中,后面我们需要遍历该数组
     * 通过matrix邻接矩阵来获取
     *
     * @return EData[]类型的数组
     */
    public EData[] getEdges() {
        int index = 0;
        EData[] edges = new EData[edgeNum];
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (matrix[i][j] != INF)
                    edges[index++] = new EData(vertex[i], vertex[j], matrix[i][j]);
            }
        }
        return edges;
    }

    /**
     * 获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相同
     * @param ends:数组就是记录了各个顶点对应的终点是哪个,ends数组是在遍历过程中,逐步形成
     * @param i:表示传入的顶点对应的下标
     * @return 返回的就是下标为i的这个顶点对应的终点的下标
     */
    public int getEnd(int[] ends,int i){
        while (ends[i]!=0)
            i=ends[i];
        return i;
    }

}

//创建类EData,他的对象实例就表示一条边
class EData {
    char start;//一条边的起点
    char end;//一条边的终点
    int weight;//边的权重

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

    @Override
    public String toString() {
        return "EData{" +
                "start=" + start +
                ", end=" + end +
                ", weight=" + weight +
                '}';
    }
}

标签:10000,int,vertex,算法,顶点,new,INF,贪心
From: https://www.cnblogs.com/mglblog/p/17891898.html

相关文章

  • 代码随想录算法训练营第7天 | lc344、lc541、卡码54、lc151、卡码55
    (本合集全部为Go语言实现)相关文章链接:344题解541题解卡码54题解151题解卡码55题解相关视频链接:Leetcode344状态:秒了实现过程中的难点:对撞双指针个人写法funcreverseString(s[]byte){fori,j:=0,len(s)-1;i<j;i,j=i+1,j-1{s[i],s[j]......
  • 基于PSD-ML算法的语音增强算法matlab仿真
    1.算法运行效果图预览   2.算法运行软件版本matlab2022A 3.算法理论概述      PSD-ML(PowerSpectralDensityMaximumLikelihood)算法是一种基于最大似然估计的语音增强算法,通过对语音信号的功率谱密度进行估计,并利用估计结果对原始语音信号进行滤波处理,以达......
  • 【教3妹学编程-算法题】需要添加的硬币的最小数量
    3妹:2哥2哥,你有没有看到新闻,有人中了2.2亿彩票大奖!2哥 :看到了,2.2亿啊,一生一世也花不完。3妹:为啥我就中不了呢,不开心呀不开心。2哥 :得了吧,你又不买彩票,还是脚踏实地的好~3妹:小富靠勤,中富靠德,大富靠命,可能是我命不好。2哥 :哎,想我口袋只有几个硬币,叮咚作响。3妹:说到硬币,我......
  • 【Cpp 基础】泛型算法 stable_sort() 的应用
    最近在刷牛客的题。经常遇到排序问题,经常有一个附加的规则:相同的数值的,按照录入的顺序排序。可是C++的sort()的底层是快速排序,并不能保证相同数值的顺序不改变。所以最后我不得不自己写冒泡排序。(冒泡排序不改变相同数值的录入顺序)写了那么多的排序,但是其实C++里封装有排序函数......
  • 常见算法的复杂度
    算法 平均时间复杂度 最差空间复杂度快速排序nlognlogn归并排序nlognntimsort  nlogn  n堆排序nlogn  1冒......
  • 机器学习的算法——线性回归
    1.回归问题的定位我们知道机器学习分为有监督学习和无监督学习,无监督学习主要是聚类方面的算法,而有监督问题主要分为回归和分类两类而这线性回归就属于有监督学习,且属于其中的回归类问题,另外有一种逻辑回归,他却是属于分类问题的一部分。2.线性回归(1)大体思路首先它是利用......
  • 【算法】【线性表】搜索旋转排序数组(有重复数据)
    1 题目跟进“搜索旋转排序数组”,假如有重复元素又将如何?是否会影响运行时间复杂度?如何影响?为何会影响?写出一个函数判断给定的目标值是否出现在数组中。样例1:输入:A=[]target=1输出:false 解释:数组为空,1不在数组中。样例2:输入:A=[3,4,4,5,7,0,1,2]t......
  • 直播系统源码,常见的混音算法有哪些?
    声音是由于物体的振动对周围的空气产生压力而传播的一种压力波,转成电信号后经过抽样,量化,仍然是连续平滑的波形信号,量化后的波形信号的频率与声音的频率对应,振幅与声音的音量对应,在直播系统源码中,量化的语音信号的叠加等价于空气中声波的叠加,所以当采样率一致时,混音可以实现为将各......
  • 1.理论、算法、协议
    1.CAP理论CAP也就是Consistency(一致性)、Availability(可用性)、PartitionTolerance(分区容错性)这三个单词首字母组合。在理论计算机科学中,CAP定理(CAPtheorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:一致性(Consistency):所有节点访问......
  • 电台覆盖区域的贪心算法
    1.贪心算法电台覆盖区域求最优解问题题目:假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。如何选择最少的广播台,让所有的地区都可以接收到信号广播台覆盖地区K1“北京”,“上海”,“天津”K2“广州”,“北京”,“深圳”K3“成都”,“......