算法学习——贪心法

贪心算法是一种解决问题的策略,它通过每次选择局部最优解来逼近全局最优解。在本文中,我们探讨了贪心算法的本质特征,如与DP和分治的区别,以及它的快速但不保证最优解的特点。内容涵盖了贪心算法的设计关键,以及如何在程序中实现。具体实例包括Dijkstra算法用于求解有向无负权图的单源最短路径,以及两种最小生成树的构造方法:Kruskal和Prim算法。

本质特征(区分dp、分治)

只选择一个子问题

 

特点

1.快

2.不能保证获得最优解,可以作为近似解,一般,在特殊情况下可保证最优解。

3.适合于组合优化问题

 

设计关键

贪心选择策略(分解方案)

 

程序写法

S = s0,C = c0; // 部分解S,候选集C

while(!complete(s)){
    x = select(c); // 贪心选择主体    
    if(flexible(x)){ // 可行函数,判断贪心选择结果是否满足约束条件
       S = S ∪ {x} // 解扩展
         }
    C = C - {x}; //C中参数重新计算以及候选集调整
    }

 

例子

1、Dijkstra算法——求单源最短路径(有向无负权图)

注意:实现时dist[] 的初始值若源点和i之间无边,则一定要为无穷大(例如XX.MAX_VALUE等),若为0,则后面会更新不了,导致结果出错

实现一:邻接矩阵实现

public class Greedy {


    float[][] arr; // 表示边的权,若没有连接的边则用最大值表示
    boolean[] s; // 表示已找到最短路径的点集合
    int[] pre; // 最短路径中某个点对应的前驱节点
    float[] dist; // 从源到所有其他顶点的最短路径
    int m; // 图中边的条数
    int n; // 图中的点的个数
    int v; // 源


    public static void main(String[] args){

        new Greedy().run();
    }

    public void run(){

        Scanner scanner = new Scanner(System.in);
        v = 0;
        n = scanner.nextInt();
        m = scanner.nextInt();
        arr = new float[n][n];
        s = new boolean[n];
        pre = new int[n];
        dist = new float[n];
        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                arr[i][j] = Float.MAX_VALUE;
            }
        }
        for(int i = 0;i < m;i++){
            int a = scanner.nextInt()-1;
            int b = scanner.nextInt()-1;
            float c = scanner.nextFloat();
            arr[a][b] = c;
//            arr[b][a] = c; 若为无向图则加上
        }
        dijkstra();
        System.out.println(Arrays.toString(dist));
    }



    public void dijkstra(){

        // 初始化
        for(int i = 0;i < n;i++){
          dist[i] = arr[v][i];
          if(dist[i] == Float.MAX_VALUE){
              pre[i] = -1;
          }else {
              pre[i] = v;
          }
        }

        dist[v] = 0;
        s[v] = true;


        for(int i = 0;i < n;i++){
            float min = Float.MAX_VALUE;
            int u = v;
            // 找出未访问过的最短路径的点
            for(int j = 0;j < n;j++){
                if(!s[j] && dist[j] < min){
                    min = dist[j];
                    u = j;
                }
            }
            // 加入到已找到最短路径的点集合S中
            s[u] = true;

            // 若存在以j为中间节点,到源点v的最短距离变小的未找到最短距离的点,则更新距离
            for(int j = 0;j < n;j++){
                if(!s[j] && arr[u][j] < Float.MAX_VALUE){
                    float newDis = dist[u] + arr[u][j];
                    if(newDis < dist[j]){
                        dist[j] = newDis;
                        pre[j] = u;
                    }
                }
            }
        }
    }
}

 

实现二:邻接矩阵,优先队列实现(不知道这个方法有没有错就是了...)

public class Greedy2 {


    public class Edge{
        int end;
        float cost;

        public Edge(int end,float cost){
            this.end = end;
            this.cost = cost;
        }
    }

    public class Node implements Comparable<Node>{
        int index;
        float dis;

        public Node(int index,float dis){
            this.index = index;
            this.dis = dis;
        }

        @Override
        public int compareTo(Node o) {
            if(this.dis < o.dis){
                return -1;
            }
            if(this.dis == o.dis){
                return 0;
            }
            return 1;
        }
    }

    PriorityQueue<Node> queue;
    LinkedList<Edge>[] list; // 表示边的权,若没有连接的边则用最大值表示
    boolean[] s; // 表示已找到最短路径的点集合
    int[] pre; // 最短路径中某个点对应的前驱节点
    float[] dist; // 从源到所有其他顶点的最短路径
    int m; // 图中边的条数
    int n; // 图中的点的个数
    int v; // 源


    public static void main(String[] args){

        new Greedy2().run();
    }

    public void run(){

        Scanner scanner = new Scanner(System.in);
        v = 0;
        n = scanner.nextInt();
        m = scanner.nextInt();
        queue = new PriorityQueue<>();
        s = new boolean[n];
        pre = new int[n];
        dist = new float[n];
        list = new LinkedList[m];
        for(int i = 0;i < n;i++){
            list[i] = new LinkedList<>();
            dist[i] = Float.MAX_VALUE;
            pre[i] = -1;
        }
        for(int i = 0;i < m;i++){
            int a = scanner.nextInt()-1;
            int b = scanner.nextInt()-1;
            float c = scanner.nextFloat();
            list[a].add(new Edge(b,c));
//            list[b].add(new Edge(a,c)); 若为无向图则加上
        }
        queue.add(new Node(v,0));
        dijkstra();
        System.out.println(Arrays.toString(dist));
    }


    public void dijkstra(){

        // 初始化

        LinkedList curList = list[v];
        for(int i = 0;i < curList.size();i++){
            Edge temp = (Edge) curList.get(i);
            dist[temp.end] = temp.cost;
            pre[temp.end] = v;
        }
        dist[v] = 0;
        s[v] = true;

        while (!queue.isEmpty()){
            // 选取最小值
            Node curNode = queue.poll();
            s[curNode.index] = true;
            int u = curNode.index;

            // 更新
            LinkedList<Edge> uList = list[u];
            for(int i = 0;i < uList.size();i++){
                Edge curEdge = uList.get(i);
                float newDis = dist[u] + curEdge.cost;
                if(!s[curEdge.end] && newDis <= dist[curEdge.end]){
                    dist[curEdge.end] = newDis;
                    pre[curEdge.end] = u;
                    queue.add(new Node(curEdge.end,newDis));
                }
            }
        }



    }
}

 

2、最小生成树

描述:求无向连通带权图的耗费最小的生成树(生成树中总权值最小)

 

1.Kruskal算法(最短边策略)

/**
 * Kruskal算法+并查集
 *
 */
public class MinTree {


    /**
     * i的父亲father[i]
     */
    int[] father;

    /**
     * 寻找x的父亲
     */
    public int find(int x){
        if(father[x] == x){
            return x;
        }

        return father[x] = find(father[x]);
    }

    /**
     * 将x和y合并在一个集合
     * @param x
     * @param y
     */
    public void union(int x,int y){
        int fatherX = find(x);
        int fatherY = find(y);
        if(fatherX > fatherY){
            father[fatherX] = fatherY;
        }else{
            father[fatherY] = fatherX;
        }
    }

    public class Edge implements Comparable<Edge>{
        int start;
        int end;
        int cost;
        public Edge(int start,int end,int cost){
            this.start = start;
            this.end = end;
            this.cost = cost;
        }

        @Override
        public int compareTo(Edge o) {
            return this.cost - o.cost;
        }
    }

    int n;

    ArrayList<Edge> list;

    /**
     * 存储结果的集合
     */
    ArrayList<Edge> res;

    public static void main(String[] args){

        new MinTree().run();
    }


    public void run(){

        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        int m = scanner.nextInt();
        list = new ArrayList<>();
        father = new int[n];
        res = new ArrayList<>(n);
        for(int i = 0;i < n;i++){
            father[i] = i;
        }
        for(int i = 0;i < m;i++){
            int a = scanner.nextInt()-1;
            int b = scanner.nextInt()-1;
            int c = scanner.nextInt();
            Edge edge = new Edge(a,b,c);
            list.add(edge);
        }

        list.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                if(o1.cost == o2.cost){
                    return o1.start - o2.start;
                }
                return o1.cost - o2.cost;
            }
        });

        kruskal();

        for(int i = 0;i < res.size();i++){
            System.out.println(res.get(i).start + " " + res.get(i).end + " ");
        }

    }


    /**
     * Kruskal主体
     */
    public void kruskal(){

        for(int i = 0;i < list.size();i++){
            Edge curEdge = list.get(i);
            // 如果这个节点已经在一个集合,就不能再连
            if(find(curEdge.start) == find(curEdge.end)){
                continue;
            }
            // 把该边加入选择的边集
            res.add(curEdge);
            union(curEdge.start,curEdge.end);
        }
    }
}

 

2、Prim算法

public class MinTree2 {

    int[][] graph;

    int[] lowcost;

    int[] pre;

    StringBuilder res;

    int n;





    public static void main(String[] args){
        new MinTree2().run();
    }

    public void run(){
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        int m = scanner.nextInt();
        graph = new int[n][n];
        lowcost = new int[n];
        pre = new int[n];
        res = new StringBuilder();

        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                graph[i][j] = Integer.MAX_VALUE;
            }
        }
        for(int i = 0;i < m;i++){
            int a = scanner.nextInt()-1;
            int b = scanner.nextInt()-1;
            int c = scanner.nextInt();
            graph[a][b] = c;
        }

        prim();
        System.out.println(res.toString());

    }

    public void prim(){

        // 初始化
        for(int i = 1;i < n;i++){
                lowcost[i] = graph[0][i];
                pre[i] = 0;

        }
        lowcost[0] = 0;

        for(int i = 1;i < n;i++){

            int minP = 0;
            int minCost = Integer.MAX_VALUE;

            // 找出距离已找出最短边的点集最小的点
            for(int j = 1;j < n;j++){
                if(lowcost[j] != 0 && lowcost[j] < minCost){
                    minCost = lowcost[j];
                    minP = j;
                }
            }

            if(minP == 0){
                return;
            }
            // 加入已访问的集合
            lowcost[minP] = 0;

            res.append(minCost + "\n");

          // 更新未找出最小边点的集合
            for(int j = 1;j < n;j++){
                if(lowcost[j] != 0 && lowcost[j] > graph[minP][j]){
                    lowcost[j] = graph[minP][j];
                    pre[j] = minP;
                }
            }
        }



    }


}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值