算法分析与设计总结---贪心算法


前言

算法分析与设计课程学习后的一些总结,本文主要介绍贪心算法。


一、简介

基本概念

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
贪心选择性质
贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
贪心算法通常以自顶向下的方式进行,一迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。动态规划通常以自底向上的方式解决子问题。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

基本思路

1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。

适用的问题

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。

实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

二、贪心算法应用

1.活动安排问题

问题定义
问题:有n个活动的集合A={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。
求解:安排尽量多项活动在该场地进行,即求A的最大相容子集。
在这里插入图片描述
算法设计
活动安排问题的贪心算法思路

  • 将各个活动按照活动结束时间fi排序,且f1<=f2<=f3…
  • 选择活动1,要求该活动的结束时间最早
  • 从2开始按顺序比较各个活动,选择第一个与活动1相容的活动i
  • 从i+1开始按顺序考察各个活动,选择第一个与活动 i 相容的活动 j

**每次选择与现有活动相容的结束时间最早的活动 **
由于输入的活动以其完成时间的非减序排列,所以算法每次总是选择具有最早完成时间的相容活动。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。

对活动以其完成时间的非减序排列。(意义:使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。)

将第一次活动结束时间f[1]与后面活动开始时间s[2]相比较,若s[2]<f[1]则继续比较,直到s[4]>f[1],选中此活动。再用活动4的结束时间f[4]与其后活动的开始时间比较……同理类推,直到比较完成为止,最后选出合条件的活动1,活动4,活动8和活动11,它们将依次被安排使用该场地。

代码如下:

void GreedySelector(int n, Type s[], Type f[], bool A[])
{
    A[1]=true;
    int j=1;//记录最近一次加入A中的活动
 
    for (int i=j+1;i<=n;i++)//依次检查活动i是否与当前已选择的活动相容
    {
        if (s[i]>=f[j])
        { 
            A[i]=true;
            j=i;
        }
        else
        {
            A[i]=false;
        }
    }
}

2.哈夫曼

算法设计
构造一棵哈夫曼树的方法如下:
①由给定的n个权值, n个权值分别设为 w1、w2、…、wn,构造n棵只有1个叶子结点的二叉树,从而得到一个二叉树的集合F={T1,T2,…Tn}。
②在F中选取根节点的权值最小和次小的两颗二叉树作为左、右子树构造一棵新的二叉树,这颗新的二叉树根节点的权值为其左、右子树根节点权值之和。即合并两棵二叉树为一棵二叉树。
③重复步骤②,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。
例如给定a~d四个字符,它们的权值集合为w={100,10,50,20}
首先构造出哈夫曼树,过程如下图:
在这里插入图片描述
接下来对字符进行编码并求出WPL
在这里插入图片描述

代码如下:

public static Node createHuffmanTree(int[] arr){
        // 创建一个集合,存入创建的节点
        List<Node> nodeList = new ArrayList<>();
        for (int item : arr) {
            nodeList.add(new Node(item));
        }
        // 因为每次都会remove一些节点,最终会在list中剩下一个节点,这个节点就是根节点
        while (nodeList.size() > 1){
            // 从小到达排序list
            Collections.sort(nodeList);
            // 取出前两个最小的,第一个作为左节点,第二个作为右节点
            Node leftNode = nodeList.get(0);
            Node rightNode = nodeList.get(1);
            // 将权重+路径和赋值给父节点,将父节点的左右节点挂上
            Node parentNode = new Node(leftNode.getValue()+rightNode.getValue());
            parentNode.setLeft(leftNode);
            parentNode.setRight(rightNode);
            // 移除最小的两个节点,将父节点放入list集合中,进行下一轮
            nodeList.remove(leftNode);
            nodeList.remove(rightNode);
            nodeList.add(parentNode);
        }
        // 返回最终的根节点
        return nodeList.get(0);
}


滴答

提示:本文是对互联网上搜集内容的一些体会,侵权请联系。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值