野生前端的数据结构练习(12)贪心算法

参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/GreedyAlogrithm

一.贪心算法

贪心算法属于比较简单的算法,它总是会选择当下最优解,而不去考虑单次递归时是否会对未来造成影响,也就是说不考虑得到的解是否是全局最优。在很多实际问题中,寻找全局最优解的代价是非常大的,这时候就可以通过求次优解来解决问题,这种思想其实在软件工程中很常见,例如React中著名的DOM Diff算法中需要对比两棵DOM树,树的完全对比时间复杂度为O(n^3),而React团队通过只比较同层节点的策略将问题简化为O(n),也就是说得到的结果从全局角度来说并不一定是绝对最优的,但是它可以在大多数情况下表现并不差。

下面通过几个简单例子来理解贪心算法(题目来自《数据结构与算法Javascript描述》一书)。

二.贪心算法求解背包问题

贪心算法求解背包问题实际上很像人工求解,如果你是一个大盗,自己带着背包在宝库挑东西,总不能拿一张草稿纸出来开始动态规划或手动递归吧,那等你求出结果来估计警察也来了,可能的做法例如:

  1. 不做衡量,拿起什么放什么,放不进去或者没有更多东西了,然后离开。
  2. 从最贵的开始挑,能放进去就放,放不进去就接着挑别的,最后背包塞不下更多或者没东西了,然后离开。
  3. 简单计算一下性价比(一般可以以【价值】/【体积】来衡量),然后按性价比从高到低开始挑,能放进去就放,放不进去就下一个,最后背包塞不下更多或者没东西了,离开。

这几种方式都可以作为贪心算法的一部分参与计算,得到的结果也不一定是一样的,但都可以认为是一种局部最优解,同时也可能是全局最优解。

示例算法实现:

/**
 * 贪心算法求解背包问题局部最优解
 */
function ksack(values, weights, capacity, n) {
    var load = 0;
    var i =0;
    var w = 0;
    while (load < capacity && i < n){
        if(weights[i] <= (capacity-load)){
            w += values[i];
            load += weights[i];
        }else{
            var r = (capacity - load) / weights[i];
            w += r * values[i];
            load += weights[i];
        }
        i++;
    }
    return w;
}

var items = ["A","B","C","D"];
var values = [50, 140, 60 ,60];
var weights = [5, 20, 10, 12];
var n = 4;
var capacity = 30;

console.log(ksack(values,weights, capacity, n))

三.最长公共子串

书中第14章练习题1:使用暴力技巧求解最长公共子串

3.1 题解:

暴力求解公共字符串的方法,从较短的字符串整体开始找起,逐步缩小步长,例如长字符串为long_str,较短的字符串称为short_str,假设short_str的长度为6,先查看short_str是否整个包含在long_str中,如果是则返回,如果不是,再将步长设置为5,然后查看short_str[0,5), short_str[1,6)这两个字符串是否在long_str中,以此类推,过程和找零问题很相似。

3.2 参考代码:

/**
 * 贪心算法寻找公共子串
 */
function greedy_lsc(str1, str2) {
    //保证str2是长度较短的序列
    if (str1.length < str2.length) {
        let temp = str1;
        str1 = str2;
        str2 = temp;
    }
    let stepLength = str2.length;
    //从长到短枚举
    while(stepLength >= 0){
        for(let i = 0; i < str2.length - stepLength; i++){
            //相当于拿一个不断缩短的尺子逐段截取来查看截取的片段是否被长字符串包含,
            //一旦找到则就是最长公共子串
            let checking = str2.slice(i, i+stepLength);
            if (contains(str1,checking)) {
                return checking;
            }
        }
        stepLength--;
    }
}

//str2是否是str1的子串
function contains(str1, str2) {
    return str1.indexOf(str2) !== -1;
}

//测试
let str1 = ‘aabcdefsssefce‘;
let str2 = ‘abssefsssse‘;

console.log(greedy_lsc(str1,str2));

四.找零问题

书中第14章练习题3:使用贪心算法求解找零问题,要求不能用10美分,需要找零30美分。

4.1 题解:

书中有例题,没什么难度,就不展开讲了,仅提供参考代码。

4.2 参考代码:

/**
 * 贪心算法求解零钱问题
 * 要求:不能使用10美分
 */

function makeChange(money, coins) {
    let remain = money;
    if (remain / 25 > 0) {
        coins[2] = parseInt(remain / 25, 10);
        remain = remain - coins[2]*25;
    }
    if (remain / 5 > 0) {
        coins[1] = parseInt(remain / 5 , 10);
        remain = remain - coins[1]*5;
    }
    coins[0] = remain;
}

/**
 * 显示结果
 */
function showChange(coins) {
   if (coins[2] > 0) {
     console.log(‘25美分-‘ + coins[2]);
   }
   if (coins[1] > 0) {
     console.log(‘5美分-‘ + coins[1]);
   }
    if (coins[0] > 0) {
     console.log(‘1美分-‘ + coins[0]);
   }
}

var origAmt = 30;
var coins = [];
makeChange(origAmt, coins);
showChange(coins);

原文地址:http://blog.51cto.com/13869008/2324125

时间: 2024-08-02 09:03:49

野生前端的数据结构练习(12)贪心算法的相关文章

野生前端的数据结构练习(11)动态规划算法

一.动态规划算法 dynamic programming被认为是一种与递归相反的技术,递归是从顶部开始分解,通过解决掉所有分解出的问题来解决整个问题,而动态规划是从问题底部开始,解决了小问题后合并为整体的解决方案,从而解决掉整个问题. 动态规划在实现上基本遵循如下思路,根据边界条件得到规模较小时的解,小规模问题合并时依据递推关系式进行,也就是说较大规模的问题解可以由较小问题的解合并计算得到.最经典易懂的就是使用递归算法和动态规划算法两种不同的方式来计算斐波那契数列或求阶乘的对比,动态规划算法的特

野生前端的数据结构基础练习(3)——链表

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/List 链表的基本知识 特点: 链表由节点组成,每个节点增加一个对象的引用指向它的后继节点.链表也就是将一个线性表转换为一个存储空间上不连续,而在抽象层面可连续访问的表. 用途: 更快的插入和删除,因为只需要操作插入删除位置相邻元素即可,

野生前端的数据结构练习(1)——栈

习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/Stack 基本练习 根据栈的特性实现一个Stack类,并在后续题目中需要用栈时使用它. 编写一个函数unitTrans(num, unit),num为一个10进制数字,unit要转换的进制数,求转换结果. 编写一个函数recursion(num),num为一个10进制数字,要求输出n

野生前端的数据结构基础练习(2)——队列

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/Queue 队列的基本知识 特点: 先进先出. 用途: 模拟流程或其他带有抽象排队属性的事物或逻辑,例如时间循环队列,发布订阅模式的回调队列等等. 基本方法 enqueue()在队尾插入一个元素 dequeue()从队头删除一个元素 get

野生前端的数据结构基础练习(5)——散列

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/Hash 散列的基本知识 定义 哈希表是一种根据关键码去寻找值的数据映射结构,最直观的应用就是字典(现实的字典,不是数据结构的字典概念). 特点: 插入,删除,取用较快,查找较慢(例如查询最值,需要借助其他数据结构来提升效率). 散列函数应

野生前端的数据结构基础练习(6)——集合

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/Set [TOC] 集合的基本知识 定义: 集合Set是一种不包含不同元素的数据结构,主要特性包括无序性和单一性,即集合中的成员是无序的,同时也是不重复的. 基本练习 实现一个自定义的cSet类(避免与原生的Set类冲突),包含以下方法:

野生前端的数据结构基础练习(7)——二叉树

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/btree 一.二叉树的基本知识 基本概念 一棵树最上面的点称为根节点,如果一个节点下面连接多个节点,那么该节点称为父节点,下面的节点称为子节点,二叉树的每一个节点最多有2个子节点,一个节点子节点的个数称为度,二叉树每个节点的度只能是0,1

野生前端的数据结构基础练习(8)——图

网上的相关教程非常多,基础知识自行搜索即可. 习题主要选自Orelly出版的<数据结构与算法javascript描述>一书. 参考代码可见:https://github.com/dashnowords/blogs/tree/master/Structure/graph 一.图的基本知识 基本概念 图是由边的集合和点的集合组成的.如果图的边有方向(或者说图中的顶点对是有序的)则成为有向图,如果边没有方向则称为无向图. 基本建模 图可以用来对现实中许多事物进行建模.比如交通流量,计算机网络等. 二

野生前端的数据结构练习(10)希尔排序,归并排序,快速排序

一.希尔排序 shell sort也称缩小增量排序,是对插入排序算法的改进,其工作原理是定义一个间隔序列来表示排序过程中进行比较的元素之间有多远的间隔,每次将具有相同间隔的数分为一组,进行插入排序,大部分场景中,间隔是可以提前定义好的,也可以动态生成.在较大的数据集上,希尔排序对于插排的优化效果是非常明显的. ./** * 希尔排序示例代码 */ function shellSort(gaps, arr) { for(let g = 0; g < gaps.length; g++){ let h