前端小白的算法之路

时隔多日终于解决了埋在心头的一道难题,霎时云开雾散,今天把一路而来碰到的疑惑和心得都记录下来,也算是开启了自己探索算法的大门。

问题背景

曾经有一个年少轻狂的职场小白,在前端圈子里摸爬滚打将近两年,本计划在js的道路上越走越远,以至于每天沉浸在js红皮书里不能自拔,突然有一天脑抽想找leader比划两下,于是出现了下面的对话,小白:leader,您最近在干嘛?手里有需要亟待解决的难题吗?leader:咦,确实有哎,咱的项目随着业务的不断发展,日均PV也越来越多,公司的两台机器已经快满足不了需求,现在需要解决一下机器的问题。小白:那还不简单,就是多搞几台机器,四核换八核,可以并行处理就OK了。leader:小伙子想法很美好啊,钱从哪来?那我先问你个简单的问题[1],你写个算法出来。于是这个问题应用而生,小白也开始了苦苦的算法中。。。

问题阐述

假设一台双核处理器可以并行处理任务,它们的处理速度都为1k/s,每个任务均以k为单位,如[300, 600, 300, 500, 1000, 700, 300],且每个任务不能拆分必须由单独的核来执行,求一堆任务的最短时间算法?

(如果你对这个问题感兴趣或者觉得自己很NB,可以停下来试着写一下这个算法,不要偷看我的代码哈??高手略过??)

算法之路

看到这个问题,第一反应很简单,无非就是先排个序,然后看情况再分配任务,于是有了下面的第一版程序:

let arr = [300, 600, 300, 500, 1000, 700, 300];function task(arr) {    let left = [];    let right = [];    let lefts = 0;    let rights = 0;    let flag = true; // 第一次累加最大值 第二次累加最小值 平分两组任务    // 平分两组任务    let newArr = arr.sort((a, b) => b - a);    if (flag) {        left.push(newArr[0]);        right.push(newArr[1]);        newArr = newArr.slice(2);    } else {        left.push(newArr[newArr.length - 1]);        right.push(newArr[newArr.length - 2]);        newArr = newArr.slice(0, newArr.length - 2);    }    // 开关循环 第一次加最大值 第二次加最小值 依次累加    flag = !flag;     // 两组任务分别之和    lefts = left.reduce((a, b) => a + b);    rights = right.reduce((a, b) => a + b);    // 只剩下一个任务或0个任务时,最终结果计算    if (newArr.length <= 1) {        if (newArr.length == 1) {            if ((lefts - rights) > newArr[0]) {                return lefts;            } else {                right.push(newArr[0]);                rights = right.reduce((a, b) => a + b);                return rights;            }        } else {            if (lefts < rights) {                return rights;            } else {                return lefts;            }        }    }    // 递归调用实现循环    return task(newArr);};alert("最短时间为:" + task(arr) + ‘s‘);

基本思路就是先把一堆任务排序,然后开始分配,第一次给第一台机子最大值,第二台机子次大值,第二次给第一台机子最小值,第二台机子次小值,依次递归调用累加,直至最后结束,如果是奇数个任务最后剩下一个任务的话,需要把这个任务分给时间较小的一组,最后返回一组时间较大的即是最终所需的最短时间。

显然这个程序是有问题的,于是开始了研究,多天之后依旧没有给出正确的答案,凭借一己之力显然不能解决,然后开始在segmentfault上提问,没想到很快就有人回复了,是NP-hard问题。近似算法参见partition problem

看到回复后迫不及待的开始百度Google,竟然让我大吃一惊,2000年,美国克莱数学研究所公布了世界七大数学难题,又称千禧年大奖难题。其中P与NP问题被列为这七大世界难题之首,看到这大大激发了我对这一问题的研究热情,于是开始了NP问题的研究。

NP-hard,其中NP是指非确定性多项式(non-deterministic polynomial,缩写NP)。所谓的非确定性是指,可用一定数量的运算去解决多项式时间内可解决的问题。NP-hard问题通俗来说是其解的正确性能够被“很容易检查”的问题,这里“很容易检查”指的是存在一个多项式检查算法。相应的,若NP中所有问题到某一个问题是图灵可归约的,则该问题为NP困难问题。

旅行推销员问题就是最著名的NP问题之一,当然我要解决的这个问题(多线程多机调度问题)也属于NP问题之一,一般使用贪心算法来解决,于是我就开始了贪心算法之路。

算法描述

贪心算法:(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

思想: 贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。

过程:

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

解决思路

多线程问题主要是多个服务器可以并行处理多个任务,寻求处理所有任务的情况下,用掉最少时间的问题。因为任务并不局限于在某一个服务器上处理,而且任务不能拆分,所以还是要综合考虑怎么分配任务,属于多线程问题。

核心思路:(n代表任务,m代表机器)

  1. 将n个独立的任务按照时间从大到小排序;
  2. 如果n<=m,则需要的最短时间就是n个任务当中的最大时间;
  3. 如果n>m,则先给每个机器依次分配任务,第一次就分配了m个作业;
  4. 然后循环第一次分配的m个任务时间,选取处理时间最短的机器分配第m+1个任务;
  5. 依次循环所有机器所需时间,并选取最短时间的机器分配下一个任务;
  6. 最后比较返回最长时间的机子时间则为所需的最短时间。

实现过程:

程序设计

第二版程序:

let arr = [700, 400, 300, 500, 100, 900];function task(arr) {    // 1. 任务排序    let newArr = arr.sort((a, b) => b - a);    // 2. 两组各取最大值和次大值    let left = [newArr[0]];    let right = [newArr[1]];    newArr = newArr.slice(2);    // 3. 分别计算两组所用的时间    let lefts = newArr[0];    let rights = newArr[1];    // 4. 比较哪一组时间少就依次把下一个任务分给少的那组    newArr.forEach((item, index) => {        if (lefts < rights) {            left.push(item);        } else {            right.push(item);        }        // 分别计算每组所用的时间        lefts = left.reduce((a, b) => a + b);        rights = right.reduce((a, b) => a + b);    });    // 5. 返回较大值则是所用最短时间    return Math.max(lefts, rights);};alert("最短时间为:" + task(arr) + ‘s‘);

以上的第二版程序还是以最初的问题双核处理器(相当于两个机子)实现的,经测试正确通过,于是又拓展了多线程多机器的常见问题,就有了最终版的程序。

第三版程序:

let tasks = [300, 600, 300, 500, 1000, 700, 300];function task(tasks, nums) {    // 1. 对任务进行从大到小排序    tasks = tasks.sort((a, b) => b - a);    // 2. 第一次给nums个机器分配前nums个任务    let machine = JSON.parse(JSON.stringify(Array(nums).fill([])));    tasks.forEach((item, index) => {        if(index < nums) {            machine[index].push(item);        }    });    // 3. 分别计算每个机器执行任务的时间    let times = Array(nums);    machine.forEach((item, index) => {        times[index] = item.reduce((a, b) => a + b);    });    // 4. 全部任务去掉第一次分配的nums个任务    tasks = tasks.slice(nums);    // 5. 比较哪台机器用的时间少就给哪台机器分配下一个任务    tasks.forEach((item, index) => {        // 给最短时间的机器分配任务        times.some((items, indexs) => {            if(items == Math.min(...times)) {                machine[indexs].push(item);                return true;            }        });        // 分别计算每台机器的执行时间        machine.forEach((items, indexs) => {            times[indexs] = items.reduce((a, b) => a + b);        });    });    // 6. 返回所有机器中时间最长的即是所有任务执行的最短时间    return Math.max(...times);};alert("最短输出时间为:" + task(tasks, 3) + ‘s‘);

哈哈,终于可以松口气了,这一路下来也是历尽艰辛,在此非常感谢清华大学的@萝卜的指点迷津,一语惊醒梦中人,让我找到了解法,虽然不是最优的算法,也让我醍醐灌顶,打开了探索算法的大门。以上代码是用JavaScript实现的(你可以用你熟悉的语言实现一下哈??),其他语言也是一样的逻辑,所以做前端的千万不要在js的世界里妄自尊大,要站在CTO的角度放眼全局,尤其是多熟悉一些算法,这样的话编程思维更有逻辑性,解决问题能力更强,在公司的不可替代性也就更大了。

反思总结

  1. 算法是计算机科学领域最重要的基石之一,因为计算机语言和开发平台日新月异,但万变不离其宗的是最基础的算法和理论,比如数据结构、算法设计、编译原理、计算机操作系统和数据库原理等等。在“开复学生网”上,有位同学生动地把这些基础课程比喻为“内功”,把新的语言、技术、标准比拟为“外功”。整天赶时髦的人最后只懂得招式,没有功力,是不可能成为武林高手的。由此知道了算法的重要性,以后要多加学习。
  2. 善于向别人请教,计算机这个领域博大精深,自己不懂的还有很多很多,就比如这次脑子里就没有贪心算法这种思想,只能硬碰运气试答案,显然是浪费时间瞎折腾,遇到研究好久都没答案的问题一定要多加请教。
  3. 善于归纳总结,积少成多,厚积薄发。
最后以村上春树的一句话送给大家共勉:不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。

原文地址:https://www.cnblogs.com/lewiscutey/p/8313325.html

时间: 2024-12-21 23:10:36

前端小白的算法之路的相关文章

前端小白的学习之路--HTML学习

HTML的补充学习 1. meta与base <meta http-equiv="refresh" content="2" > 2秒刷新一次 <base href="https://www.baidu.com" target="_blank"> 设置默认跳转地址以及跳转方式 <link rel="icon" sizes="any" mask href=&qu

零基础Python学习路线,小白的进阶之路!

近几年Python的受欢迎程度可谓是扶摇直上,当然了学习的人也是愈来愈多.一些学习Python的小白在学习初期,总希望能够得到一份Python学习路线图,小编经过多方汇总为大家汇总了一份Python学习路线图.对于一个零基础的想学习python的朋友来说,学习方法很重要, 学习方法不对努力白费 一定要有一个正确的学习线路与方法零基础Python学习路线,小白的进阶之路!零基础Python学习路线,小白的进阶之路!必学知识:[Linux基础][Python基础语法][Python字符串][文件操作

[1] 算法之路 - 选择排序

选择排序 – 算法 1. 将要排序的对象分作2部份,一个是已排序的,一个是未排序的 2.  从后端未排序部份选择一个最小值,并放入前端已排序部份的最后一个 e.g: 排序前:70 80 31 37 10 1 48 60 33 80 [1] 80 31 37 10 7048 60 33 80 选出最小值1 [1 10] 31 37 80 7048 60 33 80 选出最小值10 [1 10 31] 37 80 7048 60 33 80 选出最小值31 [1 10 31 33] 80 7048

[4] 算法之路 - 插入排序之Shell间隔与Sedgewick间隔

题目 插入排序法由未排序的后半部前端取出一个值,插入已排序前半部的适当位置,概念简单但速度不快. 排序要加快的基本原则之一: 是让后一次的排序进行时,尽量利用前一次排序后的结果,以加快排序的速度,Shell排序法即是基于此一概念来改良插入排序法. 解法 Shell排序法最初是D.L Shell于1959所提出,假设要排序的元素有n个,则每次进行插入排序时并不是所有的元素同时进行时,而是取一段间隔. Shell排序算法 – n/2间隔 Shell首先将间隔设定为n/2,然后跳跃进行插入排序,再来将

前端:HTML5学习之路(一)

第1章 HTML5基础 关于HTML5基础这一部分的内容没有明显边界.各种HTML5教材关于HTML5基础知识的介绍大同小异,这里不做过多赘述. 1. 我们要把HTML5简单用起来,首先要学会新建HTML5文档.每个网页都包含doctype.html.head和body元素,以下是一个简单的HTML5文档示例(用到了a.article.em.h1.img和p这6种常见的元素): 1 <!doctype html> 2 <html> 3 <head> 4 <meta

作为一名初级前端小白,写在年初的一些话

刚开始,还是吐槽一下这个标题吧···原本是打算写在年末的(也就是昨天),奈何大年夜的太忙(2.6才在回家的路上,第二天就大年三十了,基本没什么时间写这篇吐槽了,又熬不动夜),所以就拖到今天了. 其实最初,还是想讲一下从大学刚毕业(2015.06滚出校园),到2016年,新的一年,这一段时间的感受吧. [不忘初心] 好吧,不管是学校里的经历,还是毕业后找工作多么多么辛苦就不废话了(毕竟高中没好好学习,大学是普通的二本,然后大学后又是没好好学习,讲好听点就是拖延症,讲实话就是懒,没长记性),回顾那4

[2] 算法之路 - 选择之堆排序

题目: 选择排序法的概念简单,每次从未排序部份选一最小值,插入已排序部份的后端,其时间主要花费于在整个未排序部份寻找最小值,如果能让搜寻最小值的方式加 快,选择排序法的速率也就可以加快 Heap排序法让搜寻的路径由树根至最后一个树叶,而不是整个未排序部份,从而可以加快排序的过程,因而称之为改良的选择排序法. 整个堆排序的过程分建堆.取值.调整为新的堆三个过程.分别如下示:(以最小堆积树为例.关于HeapTree请参阅数据结构与算法) 建堆 - 算法 1.  加至堆积树的元素会先放置在最后一个树叶

[3] 算法之路 - 插入排序

插入排序 – 算法 1.将排序部分分成两部分 2.每次从后面部分取最前面的数插入到前面部分的适当位置 该处提供两个插入排序版本,指定间隔插入与插入排序.后面对指定间隔排序提到Shell排序中的n/2间隔与Sedgewick间隔 例如: 排序前:92 77 67 8 6 84 55 85 43 67 [77 92] 67 8 6 8455 85 43 67 将77插入92前 [67 77 92] 8 6 8455 85 43 67 将67插入77前 [8 67 77 92] 6 8455 85 4

[6] 算法之路 - 双向冒泡排序之Shaker

Shaker排序 –算法 1. 气泡排序的双向进行,先让气泡排序由左向右进行,再来让气泡排序由右往左进行,如此完成一次排序的动作 2. 使用left与right两个旗标来记录左右两端已排序的元素位置. 一个排序的例子如下所示: 排序前:45 19 77 81 13 28 18 1977 11 往右排序:19 45 77 13 28 18 19 7711 [81] 向左排序:[11] 19 45 77 13 28 1819 77 [81] 往右排序:[11] 19 45 13 28 18 19[7