程序员必须知道的10大基础实用算法及其讲解(Javascript代码实现)

注:本文的文字解释部分并非原创,我只是对其实现用JavaScript代码来实现,以供大家学习和参考。如有错误还请不吝指出。

原文地址:程序员必须知道的10大基础实用算法及其讲解

算法一:快速排序算法

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。


快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

算法步骤:


1 从数列中挑出一个元素,称为 “基准”(pivot),

2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。


递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。


代码实现:
 1 function quickSort(ary){
 2     var len = ary.length;
 3     if(len < 3){
 4         return ary;
 5     }
 6     var baseIndex = Math.floor(len/2),
 7         base = ary[baseIndex];
 8     var smallerAry = [],
 9         biggerAry = [];
10     for(var i=len-1,cur;i>-1;i--){
11         cur=ary[i];
12         if(i === baseIndex){
13             continue;
14         }
15         cur=ary[i];
16         cur < base ? (smallerAry[smallerAry.length] = cur):(biggerAry[biggerAry.length]=cur);
17     }
18     smallerAry[smallerAry.length] = base;
19     return quickSort(smallerAry).concat(quickSort(biggerAry));
20 }
21 console.log(quickSort([2,2,2,2,2,2,2,2,1,3,5,6,8,3,2,1,3,5,6,7,0]));// [0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 5, 5, 6, 6, 7, 8]

程序员必须知道的10大基础实用算法及其讲解 - 第1张  | 快课网

详细介绍:快速排序

算法二:堆排序算法


堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。


堆排序的平均时间复杂度为Ο(nlogn) 。

算法步骤:


1.创建一个堆H[0..n-1]
2.把堆首(最大值)和堆尾互换
3. 把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置
4. 重复步骤2,直到堆的尺寸为1
代码:
 1 // 堆排序
 2     var h = [],// 用来存放堆的数组
 3         n;// 用来存储堆中元素的个数,也就是堆的大小
 4     // 交换函数,交换堆中两个元素的值
 5     function swap(x,y){
 6         var t;
 7         t = h[x];
 8         h[x] = h[y];
 9         h[y] = t;
10     }
11     // 向下调整函数
12     function siftdown(i){
13         // 传入一个需要向下调整的节点编号i,这里传入1,即从堆的定点开始向下调整
14         var flag = 0,// flag用来标记是否需要继续向下调整
15             t;
16         // 当i节点有儿子(其实至少有左儿子)并且需要继续调整的时候循环就执行
17         while(i*2 <= n && flag === 0){
18             // 首先判断它和左儿子的关系,并用t记录值较小的节点编号
19             var leftChildIndex = i*2+1,
20                 rightChildIndex = i*2+2;
21             if(h[i] > h[leftChildIndex]){
22                 t = leftChildIndex;
23             }else{
24                 t = i;
25             }
26             // 如果它有右儿子,在对右儿子进行讨论
27             if(rightChildIndex<=n){
28                 // 如果右儿子的值更小,更新较小的节点编号
29                 if(h[t] > h[rightChildIndex]){
30                     t = rightChildIndex;
31                 }
32             }
33             
34             // 如果发现最小的节点编号不是自己,说明子节点中有比父节点更小的
35             if(t !== i){
36                 swap(t,i);// 交换他们
37                 i = t;// 更新i为刚才与他交换的儿子节点的编号,便于接下来继续向下调整
38             }else{
39                 flag = 1;
40             }
41         }
42     }
43     
44     // 建立堆的函数
45     function create(){
46         var i;
47         // 从最后一个非叶节点到第一个节点依次进行向上调整
48         for(i=n/2;i>=0;i--){
49             siftdown(i);
50         }
51         console.error(h);
52     }
53     
54     // 删除最大的元素
55     function deleteMax(){
56         var t;
57         t = h[0];// 用一个临时变量记录堆顶点的值
58         console.info(t);
59         h[0] = h[n];// 将堆最后一个点赋值到堆顶
60         n--;// 堆的元素数减1
61         siftdown(0);// 向下调整(相当于重建堆)
62         return t;
63     }
64     function sort(ary){
65         h = ary;
66         var num = ary.length,
67             i;
68         n=num-1;
69         // 建堆
70         create();
71         var sortedAry = [];
72         for(i=0;i<=num;i++){
73             sortedAry.push(deleteMax());
74         }
75         return sortedAry;
76     }
77     console.log(sort([5,36,7,22,17,46]));// [5, 7, 17, 22, 36, 46, 46] 

程序员必须知道的10大基础实用算法及其讲解 - 第2张  | 快课网

详细介绍:堆排序

算法三:归并排序

 

归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
算法步骤:
1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针达到序列尾
5. 将另一序列剩下的所有元素直接复制到合并序列尾

 

代码实现:
 1 // 数组ary从p到q是有顺序的,从q到r是有顺序的
 2     function merge(ary,p,q,r){
 3         var a = [],
 4             b = [];
 5         for(var i = p;i<=q;i++){
 6             a[a.length] = ary[i];
 7         }
 8         for(i = q+1;i<r+1;i++){
 9             b[b.length] = ary[i];
10         }
11         a[a.length] = Infinity;
12         b[b.length] = Infinity;
13         var newAry = [],
14             m = 0,
15             n = 0,
16             len;
17         for(i = 0,len = r-p+1;i<len;i++){
18             if(a[m] <= b[n]){
19                 ary[p+i] = a[m];
20                 m++;
21             }else{
22                 ary[p+i] = b[n];
23                 n++;
24             }
25         }
26         return ary;
27     }
28    
29     console.log(merge([12,9,10,1,3],1,2,4));
30    
31     function mergeSort(ary,p,r){
32         var q;
33         if(r - p === 1 || (r === p)){
34             q = p;
35             return merge(ary,p,q,r);
36         }else{
37             q = Math.ceil((p+r)/2);
38             mergeSort(ary,p,q);
39             mergeSort(ary,q+1,r);
40             return merge(ary,p,q,r);
41         }
42     }
43     var ary = [2,5,3,1,4,6,8,9,7,10,23,12,7],
44         len = ary.length;
45     console.log(mergeSort(ary,0,len-1)); //[1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 12, 23]

程序员必须知道的10大基础实用算法及其讲解 - 第3张  | 快课网

算法四:二分查找算法

二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查 找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果 在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn) 。

代码实现:

 1 function helfSearch(ary,num){
 2         var len = ary.length,
 3             middle = Math.floor(len/2),
 4             mNum = ary[middle];
 5         if(len === 0){
 6             return null
 7         }else if(mNum === num){
 8             return middle;
 9         }else if(mNum > num){
10             return helfSearch(ary.slice(0,middle),num);
11         }else{
12             return helfSearch(ary.slice(middle+1),num);
13         }
14     }
15     console.log(helfSearch([1,2,3,4,5,6,7,8],3));

详细介绍:二分查找算法

算法五:BFPRT(线性查找算法)

BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分析,BFPRT 可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂度,五位算法作 者做了精妙的处理。

算法步骤:

1. 将n个元素每5个一组,分成n/5(上界)组。

2. 取出每一组的中位数,任意排序方法,比如插入排序。

3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k小的元素。

终止条件:n=1时,返回的即是i小元素。

 1 /*
 2     为了简单起见,这里只是单纯的选取第一个元素作为枢纽元素。这样选取枢纽,就难避免使得算法容易退化。
 3     */
 4     function k_big(arr,low,high,k){
 5         var pivot  = arr[low];
 6         var high_tmp = high;
 7         var low_tmp = low;
 8         while(low < high){
 9             //从右向左查找,直到找到第一个小于枢纽元素为止
10             while (low < high && arr[high] >= pivot)
11             {
12                 --high;
13             }
14             arr[low] = arr[high];
15             //从左向右查找,直到找到第一个大于枢纽元素为止
16             while (low < high && arr[low] <= pivot)
17             {
18                 ++low;
19             }
20             arr[high] = arr[low];
21         }
22         arr[low] = pivot;
23 
24         if (low == k - 1)
25         {
26             return arr[low];
27         }else if(low > k - 1)
28         {
29             return k_big(arr,low_tmp,low-1,k);
30         }else
31         {
32             return k_big(arr,low+1,high_tmp,k);
33         }
34     }
35     var arr=[20,0,6,82,15,18,7,46,-29,12,32,52,20];
36     var i;
37     k_big(arr,0,9,4);
38     console.error(arr.slice(0,4),arr);// [-29, 0, 6, 7] [-29, 0, 6, 7, 12, 18, 15, 20, 46, 82, 32, 52, 20] 

详细介绍:

寻找最小(最大)的k个数

线性查找相关算法

算法六:DFS(深度优先搜索)

深度优先搜索算法(Depth-First-Search),是搜索算法的一种。它沿着树的深度遍历树的节点,尽可能 深的搜索树的分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如 果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。DFS属于盲目搜索。

深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。

深度优先遍历图算法步骤:

1. 访问顶点v;

2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;

3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

上述描述可能比较抽象,举个实例:

DFS 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;再从 w1 出发,访问与 w1邻 接但还没有访问过的顶点 w2;然后再从 w2 出发,进行类似的访问,… 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。

接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

代码如下:

 

 1 var a = [],
 2         book = [],
 3         n;
 4     function dfs(step){
 5         var i;
 6         if(step === n){// 如果站在第n+1个盒子面前,则表示前n个盒子已经放好扑克牌
 7             console.log(a);
 8             return a;            
 9         }
10         // 此时站在第step个盒子面前,应该放哪张牌呢?
11         // 按照1、2、3..n的顺序一一尝试
12         for(i=0;i<n;i++){
13             // 判断扑克牌是否还在手上
14             if(book[i] == undefined){// 表示i号扑克牌还在手上
15                 a[step] = i;
16                 book[i] = 1;// i号扑克牌已经不在手上
17                 dfs(step+1);
18                 book[i] = undefined;
19             }
20         }
21     }
22     n = 2;
23     dfs(0);

 

详细介绍:深度优先搜索

算法七:BFS(广度优先搜索)

广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。BFS同样属于盲目搜索。一般用队列数据结构来辅助实现BFS算法。

算法步骤:

1. 首先将根节点放入队列中。

2. 从队列中取出第一个节点,并检验它是否为目标。

  • 如果找到目标,则结束搜寻并回传结果。
  • 否则将它所有尚未检验过的直接子节点加入队列中。

3. 若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。

4. 重复步骤2。

代码如下:

 1 function TreeNode(value){
 2     this.value = value;
 3     
 4 }
 5 function makeBinaryTreeByArray(array,index){
 6     if(index<array.length){
 7         var value=array[index];
 8         if(value!=0){
 9             var t=new TreeNode(value);
10             array[index]=0;
11             t.left=makeBinaryTreeByArray(array,index*2);
12             t.right=makeBinaryTreeByArray(array,index*2+1);
13             return t;
14         }
15     }
16     return null;
17 }
18 
19 function BinaryTree(ary){
20     this.left = new TreeNode(),
21     this.right = new TreeNode(),
22     this.root = makeBinaryTreeByArray(ary,1);
23     
24     
25 }
26 // 广度优先遍历
27 BinaryTree.prototype.levelOrderTraversal = function(){
28     if(this.root==null){
29         console.error("empty tree");
30         return;
31     }
32     var queue = [];
33     queue.push(this.root);
34     while(queue.length!==0){
35         var node=queue.pop();
36         console.info('wid',node.value+"    ");
37         if(node.left!=null){
38             queue.push(node.left);
39         }
40         if(node.right!=null){
41             queue.push(node.right);
42         }
43     }
44     console.log("\n");
45 };
46 // 深度优先遍历
47 BinaryTree.prototype.depthOrderTraversal = function(){
48         if(this.root==null){
49             console.error("empty tree");
50             return;
51         }       
52         var stack=[];
53         stack.push(this.root);       
54         while(stack.length > 0){
55             var node=stack.pop();
56             console.info('deep',node.value+"    ");
57             if(node.right!=null){
58                 stack.push(node.right);
59             }
60             if(node.left!=null){
61                 stack.push(node.left);
62             }           
63         }
64     console.log("\n");
65 };
66 var arr=[0,13,65,5,97,25,0,37,22,0,4,28,0,0,32,0];
67 var tree=new BinaryTree(arr);
68 tree.levelOrderTraversal();
69 tree.depthOrderTraversal();

 

程序员必须知道的10大基础实用算法及其讲解 - 第4张  | 快课网

详细介绍:广度优先搜索

算法八:Dijkstra算法

戴克斯特拉算法(Dijkstra’s algorithm)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。迪科斯彻算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

该算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示 G 中所有顶点的集合。每一个图中的边,都是两个顶点所形成 的有序元素对。(u, v) 表示从顶点 u 到 v 有路径相连。我们以 E 表示G中所有边的集合,而边的权重则由权重函 数 w: E → [0, ∞] 定义。因此,w(u, v) 就是从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之 间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t的最低权 重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra算法是目 前已知的最快的单源最短路径算法。

算法步骤:

1. 初始时令 S={V0},T={其余顶点},T中顶点对应的距离值

若存在<V0,Vi>,d(V0,Vi)为<V0,Vi>弧上的权值

若不存在<V0,Vi>,d(V0,Vi)为∞

2. 从T中选取一个其距离值为最小的顶点W且不在S中,加入S

3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值

重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止

程序员必须知道的10大基础实用算法及其讲解 - 第5张  | 快课网

详细:Dijkstra算法

算法九:动态规划算法

动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。

动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题 非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题 解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

关于动态规划最经典的问题当属背包问题。

算法步骤:

1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规 划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格 中简单地查看一下结果,从而获得较高的效率。

详细参考:

从全球导航到输入法:谈谈动态规划

动态规划

算法十:朴素贝叶斯分类算法

朴素贝叶斯分类算法是一种基于贝叶斯定理的简单概率分类算法。贝叶斯分类的基础是概率推理,就是在各种条件的存在不确定,仅知其出现概率的情况下,如何完 成推理和决策任务。概率推理是与确定性推理相对应的。而朴素贝叶斯分类器是基于独立假设的,即假设样本每个特征与其他特征都不相关。

朴素贝叶斯分类器依靠精确的自然概率模型,在有监督学习的样本集中能获取得非常好的分类效果。在许多实际应用中,朴素贝叶斯模型参数估计使用最大似然估计方法,换言之朴素贝叶斯模型能工作并没有用到贝叶斯概率或者任何贝叶斯模型。

尽管是带着这些朴素思想和过于简单化的假设,但朴素贝叶斯分类器在很多复杂的现实情形中仍能够取得相当好的效果。

详细参考:

贝叶斯网络

朴素贝叶斯分类算法

 

 

 

posted @ 2016-07-18 20:04  淡烘糕  阅读(461)  评论(0)    收藏  举报