Java 数据结构与算法系列精讲之贪心算法

yizhihongxing

Java 数据结构与算法系列精讲之贪心算法

什么是贪心算法?

在计算机科学中,贪心算法是一种通过选择局部最优解来实现全局最优解的优化算法。贪心算法在解决某些最优化问题时非常有效,贪心算法能够达到接近最优解,有时甚至能够达到最优解。

贪心算法解题步骤:

  1. 建立算法模型
  2. 找出最优解的子结构
  3. 设计贪心选择策略
  4. 实现贪心选择策略为一个最优解
  5. 证明贪心算法的正确性

贪心算法应用场景

贪心算法应用场景非常广泛,常见的有:

  • 任务调度问题
  • 找零钱问题
  • 最短路径问题
  • Huffman编码问题

任务调度问题

一般而言,在任务调度问题中,我们将调度任务的过程分为两步

  1. 任务按照截止日期进行排序
  2. 考虑当前可行的任务集合中权重最大的

以下是一个任务调度问题的例子:

假设有n个任务需要完成,每个任务结束的期限为 di,完成该任务可以得到的价值为 vi。而且一个任务只能完成一次。为了让收益最高,应该如何安排完成任务的时间呢?

以下是该问题的贪心算法解题步骤:

  1. 按照任务的截止日期从小到大排序, 如果截止期限相等,按完成任务的收益从大到小排序。
  2. 依次考虑每个任务,如果该任务的截止时间在当前时间之前,那么该任务不能执行。否则,该任务可以执行,在可行任务集合中,选择具有最大权重的任务进行执行。

下面是任务调度问题的java代码示例:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TaskSchedule {

    static class Task {
        int deadline;
        int weight;

        public Task(int deadline, int weight) {
            this.deadline = deadline;
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "Task{" +
                    "deadline=" + deadline +
                    ", weight=" + weight +
                    '}';
        }
    }

    public static List<Task> selectTask(List<Task> tasks) {
        Collections.sort(tasks, new Comparator<Task>() {
            @Override
            public int compare(Task o1, Task o2) {
                return o1.deadline != o2.deadline ?
                        Integer.compare(o1.deadline, o2.deadline) : Integer.compare(o2.weight, o1.weight);
            }
        });

        List<Task> result = new ArrayList<>();
        boolean[] slot = new boolean[tasks.size()];
        for (Task task : tasks) {
            for (int i=Math.min(task.deadline, tasks.size()) - 1; i>=0; i--) {
                if (!slot[i]) {
                    result.add(task);
                    slot[i] = true;
                    break;
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<Task> list = new ArrayList<>();
        list.add(new Task(5, 10));
        list.add(new Task(4, 20));
        list.add(new Task(2, 15));
        list.add(new Task(3, 30));
        list.add(new Task(4, 18));
        list.add(new Task(5, 5));
        List<Task> result = selectTask(list);
        for (Task task : result) {
            System.out.println(task);
        }
    }
}

运行上面的代码片段,输出结果如下:

Task{deadline=2, weight=15}
Task{deadline=3, weight=30}
Task{deadline=4, weight=18}
Task{deadline=5, weight=10}

该算法的复杂度为O(n^2),但是由于计算机的速度非常快,因此计算时间非常短。

零钱问题

零钱问题是指如何用最少的数量的硬币来凑出给定的金额。并且硬币的数量无限。

以下是该问题的贪心算法解题步骤:

  1. 建立存储结果的list,同时建立一个coins数组,存储硬币的面值
  2. 建立变量amount,用于存储当前剩余的金额
  3. 对于每一种硬币,显然选择数量最少的硬币是一个贪心的选择
  4. 因此,每次选择当前硬币最大的硬币面值coin,判断coin是否小于amount,如果是,则将coin添加到结果集中,同时amount减去coin
  5. 重复步骤4,在所有的硬币面值中,反复选择当前面值最大的硬币,直到amount为0

以下是零钱问题的java代码示例:

import java.util.ArrayList;
import java.util.List;

public class CoinChange {

    public static List<Integer> coinChange(int[] coins, int amount) {
        List<Integer> result = new ArrayList<>();
        int[] counts = new int[coins.length];
        for (int i=coins.length - 1; i>=0; i--) {
            int count = amount / coins[i];
            counts[i] = count;
            amount -= count * coins[i];
        }
        for (int i = 0; i < counts.length; i++) {
            for (int j = 0; j < counts[i]; j++) {
                result.add(coins[i]);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] coins = {1, 5, 10, 25};
        int amount = 46;
        List<Integer> result = coinChange(coins, amount);
        System.out.println(result);
    }
}

运行上面的代码片段,输出结果如下:

[25, 10, 10, 1]

该算法的复杂度为O(n),非常高效。

结论

贪心算法是一种非常有用的算法,它能够在大多数情况下产生最优解或近似最优解。但是,贪心算法的正确性需要通过证明来实现,同时贪心算法往往需要先建立数学模型然后再建立算法。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java 数据结构与算法系列精讲之贪心算法 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • 用C语言举例讲解数据结构中的算法复杂度结与顺序表

    让我来为你讲解“用C语言举例讲解数据结构中的算法复杂度结与顺序表”的完整攻略。具体如下: 一、算法复杂度 1.1 什么是算法复杂度 算法复杂度是衡量算法运行效率的重要指标。包括时间复杂度和空间复杂度。时间复杂度指算法解决问题所用的时间,通常用大O符号表示;空间复杂度指算法解决问题所需的内存空间大小。 1.2 如何分析算法复杂度 可以从以下三个方面来分析算法复…

    数据结构 2023年5月17日
    00
  • C++数据结构的队列详解

    C++数据结构的队列详解 队列是什么? 队列是一种先进先出(First In First Out, FIFO)的数据结构,类似于现实生活中的排队等待服务。 队列中的数据按照先进先出的原则被处理。新的元素只能被插入在队列的末尾,而旧的元素只能从队列的开头被删除。因此,队列的末尾称为队尾,队列的开头被称为队头。 队列的实现方式 数组实现方式 队列可以使用数组实现…

    数据结构 2023年5月17日
    00
  • C语言实现带头结点的链表的创建、查找、插入、删除操作

    C语言实现带头结点的链表的创建、查找、插入、删除操作攻略 一、链表基本概念 链表是一种动态的数据结构,它可以在运行时动态地分配内存,支持数据的插入、删除等操作。链表(Linked List)由多个节点(Node)组成,每个节点包含两部分,一个是数据部分(Data),另一个是指向下一个节点的指针(Next)。 二、带头结点的链表 带头结点的链表是一种特殊的链表…

    数据结构 2023年5月17日
    00
  • C语言近万字为你讲透树与二叉树

    C语言近万字为你讲透树与二叉树 什么是树? 树是一种用来组织数据的非线性数据结构,它由一个根节点和若干个子节点组成,并且每个节点可能有若干个子节点。 什么是二叉树? 二叉树是一种特殊的树,它的每个节点最多只有两个子节点,并且分别称为左子节点和右子节点,左子节点在二叉树中永远排在右子节点的前面。 二叉树的遍历方式 二叉树的遍历方式有三种: 前序遍历(preor…

    数据结构 2023年5月17日
    00
  • C++线性表深度解析之动态数组与单链表和栈及队列的实现

    C++线性表深度解析之动态数组与单链表和栈及队列的实现 动态数组的实现 动态数组是一种可以动态扩展的数组结构,它的容量可以随着需要而动态增加。在C++中,使用vector类可以实现动态数组的功能。vector类相当于动态分配了一块内存空间,在使用时可以根据需要进行动态扩展。下面是一个示例代码: #include <vector> #include…

    数据结构 2023年5月17日
    00
  • PAT甲级真题1020.树的遍历

    翻译和代码思路:Acwing 一个二叉树,树中每个节点的权值互不相同。 现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。 输入格式 第一行包含整数 N,表示二叉树的节点数。 第二行包含 N个整数,表示二叉树的后序遍历。 第三行包含 N 个整数,表示二叉树的中序遍历。 输出格式 输出一行 N个整数,表示二叉树的层序遍历。 数据范围 1<=N<…

    算法与数据结构 2023年4月17日
    00
  • 数据结构之哈夫曼树与哈夫曼编码

    一、背景 编码是信息处理的基础(重新表示信息)。 普通的编码是等长编码,例如7位的ASCIL编码,对出现频率不同的字符都使用相同的编码长度。但其在传输和存储等情况下编码效率不高。 可使用不等长编码,来压缩编码:高频字符编码长度更短,低频字符编码长度更长。   [例] 将百分制的考试成绩转换成五分制的成绩 按顺序分别编码。 按频率分别编码(高频短编码,类似于香…

    算法与数据结构 2023年4月17日
    00
  • Python 树表查找(二叉排序树、平衡二叉树)

    下面是 Python 树表查找(二叉排序树、平衡二叉树)的完整攻略: 什么是树表查找 树表查找是一种数据结构,用于在数据集合中快速查找、插入和删除数据。树表查找的基本思想是利用特定的树形结构,在不断比较和移动中找到目标数据。常见的树表查找有二叉排序树和平衡二叉树。 二叉排序树(Binary Search Tree) 二叉排序树是一种特殊的二叉树结构,它满足以…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部