《趣学算法》的java实践(1)贪心算法

最优装载问题

问题描述

海盗船载重量为c,每件古董载重量为 w i w_i wi,海盗要怎样才能把尽可能多的宝物装上船?

Java实现

import java.util.Arrays;
import java.util.Scanner;

public class BestLoadingProblem {

    public static void main(String[] args) {
        double[] w;
        double c;
        int n;
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入载重量c及古董个数n:");
        c = scanner.nextDouble();
        n = scanner.nextInt();
        w = new double[n];
        System.out.print("请输入每个古董的质量,用空格分开:");
        for (int i = 0; i < n; i++) {
            w[i] = scanner.nextDouble();
        }
        Arrays.sort(w);
        double tmp = 0.0;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            tmp += w[i];
            if (tmp <= c) {
                ans++;
            } else {
                break;
            }
        }
        System.out.println("能装载的古董最大数量为Ans="+ans);
    }
}

注:Java中数组的排序可以通过调用Arrays类的sort函数来执行。

背包问题

问题描述

假设山洞中有n种宝物,每种宝物有一定重量w和响应的价值v,宝物可以分割。毛驴的运载能力为m,怎样才能使毛驴运走的宝物价值更大?

Java实现

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class KnapsackProblem {
    static class Treasure {
        public Treasure(double w, double v) {
            this.w = w;
            this.v = v;
            this.p = v / w;
        }

        double w;
        double v;
        double p;
    }

    public static void main(String[] args) {
        int n;
        double m;
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入宝物数量n及毛驴载重量m:");
        n = scanner.nextInt();
        m = scanner.nextDouble();
        Treasure[] treasures = new Treasure[n];
        System.out.print("请输入每个宝物的重量和价值,用空格分开:");
        for (int i = 0; i < n; i++) {
            treasures[i] = new Treasure(scanner.nextDouble(), scanner.nextDouble());
        }
        Arrays.sort(treasures, new Comparator<Treasure>() {
            @Override
            public int compare(Treasure o1, Treasure o2) {
                double difference = o2.p - o1.p;
                if (difference > 0.001) {   
                    return 1;
                } else if (difference < -0.001) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        double sum = 0.0;
        for (int i = 0; i < n; i++) {
            Treasure treasure = treasures[i];
            if (m > treasure.w) {
                m -= treasure.w;
                sum += treasure.v;
            } else {
                sum += m * treasure.p;
                break;
            }
        }
        System.out.println("能装载的宝物的最大价值Maximum value=" + sum);
    }
}

注:

  1. 为了更简单便捷地定义宝物的数据结构,这里使用了静态内部类的语法,静态内部类和外部类相对独立,不用实例化外部类即可使用。LinkedList的源码中就是用静态内部类Node来储存链表中的每个节点。
  2. 自定义对象数组无法直接调用Arrays类的sort函数进行排序,常见的解决方法是让自定义对象实现Comparable接口或者调用需要传入一个Comparator对象作为参数的sort函数,这里选用的是后者。
  3. Comparator是泛型接口,里面的compare函数实现了对泛型对象的比较。compare函数返回整数,正数、0、负数分别代表前一个参数的对象大于、等于、小于后一个参数的对象。因为sort函数是升序排序,所以要调换比较的顺序。
  4. 因为浮点数的精度问题,这里选择了一种常见的利用差值的比较办法,差值的绝对值在指定范围内则可以近似认为两个浮点数相等。
  5. 为了简化代码,在给sort函数传入Comparator对象时使用了匿名类的语法。匿名类实现了一种不需要提供任何的类名直接实例化内部类的语法。

会议安排问题

问题描述

有n个会议,每个会议都有开始时间和结束时间,如何安排会议才能尽可能在有限的时间内召开更多的会议?

Java实现

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class MeetingArrangementProblem {

    static class Meet {
        int begin;
        int end;
        int num;

        public Meet(int begin, int end, int num) {
            this.begin = begin;
            this.end = end;
            this.num = num;
        }
    }

    private int n, ans;
    private Meet[] meets;

    public void init() {
        int s, e;
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入会议总数n:");
        this.n = scanner.nextInt();
        this.meets = new Meet[n];
        System.out.println("请输入每个会议的开始时间和结束时间,用空格分开:");
        for (int i = 0; i < n; i++) {
            s = scanner.nextInt();
            e = scanner.nextInt();
            meets[i] = new Meet(s, e, i + 1);
        }
    }

    public void solve() {
        Arrays.sort(meets, new Comparator<Meet>() {
            @Override
            public int compare(Meet o1, Meet o2) {
                if (o1.end == o2.end) {
                    return o2.begin - o1.begin;
                } else {
                    return o1.end - o2.end;
                }
            }
        });
        System.out.println("排完序的会议时间如下:");
        int i;
        System.out.printf("%-8s %-7s %-7s%n", "会议编号", "开始时间", "结束时间");
        for (i = 0; i < n; i++) {
            Meet meet = meets[i];
            System.out.printf("%-10d %-10d %-10d%n", meet.num, meet.begin, meet.end);
        }
        System.out.println("-----------------------------");
        System.out.println("选择会议的过程:");
        System.out.println("\t选择第" + meets[0].num + "个会议");
        ans = 1;
        int last = meets[0].end;
        for (i = 1; i < n; i++) {
            Meet meet = meets[i];
            if (meet.begin >= last) {
                ans++;
                last = meet.end;
                System.out.println("\t选择第" + meet.num + "个会议");
            }
        }
        System.out.println("最多可以安排"+ans+"个会议");
    }

    public static void main(String[] args) {
        MeetingArrangementProblem problem = new MeetingArrangementProblem();
        problem.init();
        problem.solve();
    }
}

注:关于sort函数,因为设计上应该是按照结束时间升序排列,结束时间相同按照开始时间降序排列,才有了上面的Compare函数。

最短路径问题

问题描述

有n个城市,其中两两连接的路径有m条,问从一点出发到其他个点的最短路径是多少?

Java实现

import java.util.Arrays;
import java.util.Scanner;
import java.util.Stack;

public class ShortestPathProblem {
    private static int n, m;
    private static int[][] map;
    private static int[] dist;
    private static int[] p;
    private static boolean[] flag;

    private static void dijkstra(int u) {
        for (int i = 1; i <= n; i++) {
            dist[i] = map[u][i];
            flag[i] = false;
            if (dist[i] == Integer.MAX_VALUE) {
                p[i] = -1;
            } else {
                p[i] = u;
            }
        }
        dist[u] = 0;
        flag[u] = true;
        for (int i = 1; i <= n; i++) {
            int temp = Integer.MAX_VALUE, t = u;
            for (int j = 1; j <= n; j++) {
                if (!flag[j] && dist[j] < temp) {
                    t = j;
                    temp = dist[j];
                }
            }
            if (t == u) {
                return;
            }
            flag[t] = true;
            for (int j = 1; j <= n; j++) {
                if (!flag[j] && map[t][j] < Integer.MAX_VALUE) {
                    if (dist[j] > (dist[t] + map[t][j])) {
                        dist[j] = dist[t] + map[t][j];
                        p[j] = t;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        int u, v, w, st;
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入城市的个数:");
        n = scanner.nextInt();

        map = new int[n + 1][n + 1];
        dist = new int[n + 1];
        p = new int[n + 1];
        flag = new boolean[n + 1];

        System.out.print("请输入城市之间路线的个数:");
        m = scanner.nextInt();
        System.out.println("请输入城市u,v之间的路线以及距离w:");
        for (int i = 1; i <= n; i++) {
            Arrays.fill(map[i], Integer.MAX_VALUE);
        }
        for (; m > 0; m--) {
            u = scanner.nextInt();
            v = scanner.nextInt();
            w = scanner.nextInt();
            map[u][v] = w;
        }
        System.out.print("请输入小明所在的位置:");
        st = scanner.nextInt();
        dijkstra(st);
        System.out.println("小明所在的位置:" + st);
        for (int i = 1; i <= n; i++) {
            System.out.print("小明:" + st + " -> " + i);
            if (dist[i] == Integer.MAX_VALUE) {
                System.out.println(" 没有路径可以抵达");
            } else {
                System.out.println(" 最短距离:" + dist[i]);
            }
        }
        findPath(st);
    }

    public static void findPath(int u) {
        int x;
        Stack<Integer> s = new Stack<>();
        System.out.println("源点为:" + u);
        for (int i = 1; i <= n; i++) {
            x = p[i];
            if (x == -1 && i != u) {
                System.out.println("源点没有通往" + i + "的路径");
                continue;
            }
            while (x != -1) {
                s.push(x);
                x = p[x];
            }
            System.out.print("源点到" + i + "的最短路径为:");
            while (!s.isEmpty()) {
                System.out.print(s.pop() + "->");
            }
            System.out.println(i+" 最短距离为:" + dist[i]);
        }
    }
}

哈夫曼编码问题

问题描述

输入n个字符和其出现的频率,如何为每一个字符分配一个哈夫曼编码?

Java实现

import java.util.ArrayList;
import java.util.Objects;
import java.util.Scanner;

public class HuffmanCodingProblem {
    private final ArrayList<Node> nodeList = new ArrayList<>();
    private Node huffmanTree;

    static class Node {
        Node parent;
        Node leftChild;
        Node rightChild;
        final double weight;
        String huffmanCode;
        final char charValue;

        public Node(char charValue, double weight) {
            this.weight = weight;
            this.charValue = charValue;
        }
    }

    public static void main(String[] args) {
        HuffmanCodingProblem problem = new HuffmanCodingProblem();
        problem.getInput();
        problem.solve();

    }

    private void solve() {
        createHuffmanTree();
        generateHuffmanCode();
        printHuffmanCode();
    }

    private void printHuffmanCode() {
        for (Node node:nodeList){
            System.out.println(node.charValue+": Huffman code is: "+node.huffmanCode);
        }
    }

    private void generateHuffmanCode() {
        recursiveGenerateHuffmanCode(huffmanTree, "");
    }

    private void recursiveGenerateHuffmanCode(Node root, String code) {
        if (root == null) {
            return;
        }
        root.huffmanCode = code;
        recursiveGenerateHuffmanCode(root.leftChild, code + "0");
        recursiveGenerateHuffmanCode(root.rightChild, code + "1");
    }

    private void createHuffmanTree() {
        ArrayList<Node> tempList = new ArrayList<>(nodeList);

        int cnt = 0;
        while (tempList.size() != 1) {
            cnt++;
            Node left = null, right = null;
            double weight1, weight2;
            weight1 = weight2 = Double.MAX_VALUE;
            for (Node node : tempList) {
                if (node.weight < weight1 && node.parent == null) {
                    if (left != null) {
                        right = left;
                        weight2 = right.weight;
                    }
                    left = node;
                    weight1 = node.weight;
                } else if (node.weight < weight2 && node.parent == null) {
                    right = node;
                    weight2 = node.weight;
                }
            }
            System.out.println("第" + cnt + "次选择 左右节点的权值分别为 " + weight1 + " " + weight2);
            Node parent = new Node('\0', weight1 + weight2);
            parent.leftChild = left;
            parent.rightChild = right;
            Objects.requireNonNull(left).parent = parent;
            Objects.requireNonNull(right).parent = parent;
            tempList.add(parent);
            tempList.remove(left);
            tempList.remove(right);
        }
        huffmanTree = tempList.get(0);
    }

    private void getInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please input n:");
        int n = scanner.nextInt();
        for (int i = 0; i < n; i++) {
            System.out.println("Please input value and weight of leaf node " + (i + 1));
            Node node = new Node(scanner.next().charAt(0), scanner.nextDouble());
            nodeList.add(node);
        }
    }
}

最小生成树问题

问题描述

已知n个节点m条边的无向连通带权图,求基于某节点i的该图的最小生成树?

Java实现

Prim算法

import java.util.Scanner;

public class MinimumSpanningTreeProblemPrimVer {
    private int n;
    private int u0;
    private int[][] c;
    private int[] lowCost, closest;
    private boolean[] s;

    private void solve(){
        System.out.println("数组lowCost的内容为:");
        int sumCost = 0;
        for(int i=1;i<=n;i++){
            sumCost+=lowCost[i];
            System.out.print(lowCost[i]+" ");
        }
        System.out.println("\n最小的花费是:"+sumCost);
    }

    private void generateByPrim() {
        s[u0] = true;
        int i, j;
        for (i = 1; i <= n; i++) {
            if (i != u0) {
                lowCost[i] = c[u0][i];
                closest[i] = u0;
                s[i] = false;
            } else {
                lowCost[i] = 0;
            }
        }
        for (i = 1; i <= n; i++) {
            int temp = Integer.MAX_VALUE;
            int t = u0;
            for (j = 1; j <= n; j++) {
                if ((!s[j]) && (lowCost[j] < temp)) {
                    t = j;
                    temp = lowCost[j];
                }
            }
            if (t == u0) {
                break;
            }
            s[t] = true;
            for (j = 1; j <= n; j++) {
                if ((!s[j]) && (c[t][j] < lowCost[j])) {
                    lowCost[j] = c[t][j];
                    closest[j] = t;
                }
            }
        }
    }

    private void getInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入节点数n和边数m:");
        n = scanner.nextInt();
        c = new int[n + 1][n + 1];
        s = new boolean[n + 1];
        closest = new int[n + 1];
        lowCost = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                c[i][j] = Integer.MAX_VALUE;
            }
        }
        int m = scanner.nextInt();
        System.out.print("请输入节点数u,v和边的权值w:");
        int u, v, w;
        for (int i = 1; i <= m; i++) {
            u = scanner.nextInt();
            v = scanner.nextInt();
            w = scanner.nextInt();
            c[u][v] = c[v][u] = w;
        }
        System.out.print("请输入任意节点u0:");
        u0 = scanner.nextInt();

    }

    public static void main(String[] args) {
        MinimumSpanningTreeProblemPrimVer problem = new MinimumSpanningTreeProblemPrimVer();
        problem.getInput();
        problem.generateByPrim();
        problem.solve();
    }
}

Kruskal算法

public class MinimumSpanningTreeProblemKruskalVerVer {
    private int n;
    private int u0;
    private Set<Set<Integer>> nodeSet = new HashSet<>();
    private List<Edge> edgeList = new ArrayList<>();
    private List<Edge> minimumEdge = new ArrayList<>();

    static class Edge {
        int point1, point2;
        int weight;

        public Edge(int point1, int point2, int weight) {
            this.point1 = point1;
            this.point2 = point2;
            this.weight = weight;
        }
    }

    private void solve() {
        System.out.println("最小生成树的边分别为:");
        int sumCost = 0;
        for (Edge edge : minimumEdge) {
            System.out.println(edge.point1 + "号节点 " + edge.point2 + "号节点 " + edge.weight);
            sumCost+=edge.weight;
        }
        System.out.println("\n最小的花费是:" + sumCost);
    }

    private void generateByKruskal() {
        for (int i = 1, j = 0; i <= n - 1; i++) {
            Edge edge = edgeList.get(j);
            boolean flag = false;
            Set<Integer> set1 = null, set2 = null;
            for (Set<Integer> set : nodeSet) {
                if (set.contains(edge.point1)) {
                    set1 = set;
                }
                if (set.contains(edge.point2)) {
                    set2 = set;
                }
                if (set1 != null && set1 == set2) {
                    flag = true;
                    break;
                }
            }
            if (flag) {
                j++;
                i--;
            } else {
                set1.addAll(set2);
                minimumEdge.add(edge);
                nodeSet.remove(set2);
            }
        }
    }

    private void getInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入节点数n和边数m:");
        n = scanner.nextInt();
        for (int i = 1; i <= n; i++) {
            Set<Integer> set = new HashSet<>();
            set.add(i);
            nodeSet.add(set);
        }
        int m = scanner.nextInt();
        System.out.print("请输入节点数u,v和边的权值w:");
        int u, v, w;
        for (int i = 1; i <= m; i++) {
            u = scanner.nextInt();
            v = scanner.nextInt();
            w = scanner.nextInt();
            Edge edge = new Edge(u, v, w);
            edgeList.add(edge);
        }
        edgeList.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight - o2.weight;
            }
        });
        System.out.print("请输入任意节点u0:");
        u0 = scanner.nextInt();
    }

    public static void main(String[] args) {
        MinimumSpanningTreeProblemKruskalVerVer problem = new MinimumSpanningTreeProblemKruskalVerVer();
        problem.getInput();
        problem.generateByKruskal();
        problem.solve();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八云黧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值