一个算法只是一个把确定的数据结构的输入转化为一个确定的数据结构的输出的function。算法内在的逻辑决定了如何转换。

基础算法

一、排序

1、冒泡排序

  1. //冒泡排序function bubbleSort(arr) {
  2. for(var i = 1, len = arr.length; i < len - 1; ++i) {
  3. for(var j = 0; j <= len - i; ++j) {
  4. if (arr[j] > arr[j + 1]) {
  5. let temp = arr[j];
  6. arr[j] = arr[j + 1];
  7. arr[j + 1] = temp;
  8. }
  9. }
  10. }
  11. }

2、插入排序

  1. //插入排序 过程就像你拿到一副扑克牌然后对它排序一样
  2. function insertionSort(arr) {
  3. var n = arr.length;
  4. // 我们认为arr[0]已经被排序,所以i从1开始
  5. for (var i = 1; i < n; i++) {
  6. // 取出下一个新元素,在已排序的元素序列中从后向前扫描来与该新元素比较大小
  7. for (var j = i - 1; j >= 0; j--) {
  8. if (arr[i] >= arr[j]) { // 若要从大到小排序,则将该行改为if (arr[i] <= arr[j])即可
  9. // 如果新元素arr[i] 大于等于 已排序的元素序列的arr[j],
  10. // 则将arr[i]插入到arr[j]的下一位置,保持序列从小到大的顺序
  11. arr.splice(j + 1, 0, arr.splice(i, 1)[0]);
  12. // 由于序列是从小到大并从后向前扫描的,所以不必再比较下标小于j的值比arr[j]小的值,退出循环
  13. break;
  14. } else if (j === 0) {
  15. // arr[j]比已排序序列的元素都要小,将它插入到序列最前面
  16. arr.splice(j, 0, arr.splice(i, 1)[0]);
  17. }
  18. }
  19. }
  20. return arr;
  21. }

当目标是升序排序,最好情况是序列本来已经是升序排序,那么只需比较n-1次,时间复杂度O(n)。最坏情况是序列本来是降序排序,那么需比较n(n-1)/2次,时间复杂度O(n^2)。

所以平均来说,插入排序的时间复杂度是O(n^2)。显然,次方级别的时间复杂度代表着插入排序不适合数据特别多的情况,一般来说插入排序适合小数据量的排序。

3、快速排序

  1. //快速排序
  2. function qSort(arr) {
  3. //声明并初始化左边的数组和右边的数组
  4. var left = [], right = [];
  5. //使用数组第一个元素作为基准值
  6. var base = arr[0];
  7. //当数组长度只有1或者为空时,直接返回数组,不需要排序
  8. if(arr.length <= 1) return arr;
  9. //进行遍历
  10. for(var i = 1, len = arr.length; i < len; i++) {
  11. if(arr[i] <= base) {
  12. //如果小于基准值,push到左边的数组
  13. left.push(arr[i]);
  14. } else {
  15. //如果大于基准值,push到右边的数组
  16. right.push(arr[i]);
  17. }
  18. }
  19. //递归并且合并数组元素
  20. return [...qSort(left), ...[base], ...qSort(right)];
  21. //return qSort(left).concat([base], qSort(right));}

补充:

在这段代码中,我们可以看到,这段代码实现了通过pivot区分左右部分,然后递归的在左右部分继续取pivot排序,实现了快速排序的文本描述,也就是说该的算法实现本质是没有问题的。

虽然这种实现方式非常的易于理解。不过该实现也是有可以改进的空间,在这种实现中,我们发现在函数内定义了left/right两个数组存放临时数据。随着递归的次数增多,会定义并存放越来越多的临时数据,需要Ω(n)的额外储存空间。

因此,像很多算法介绍中,都使用了原地(in-place)分区的版本去实现快速排序,我们先介绍什么是原地分区算法。

原地(in-place)分区算法描述

  • 从数列中挑出一个元素,称为"基准"(pivot),数组第一个元素的位置作为索引。

  • 遍历数组,当数组数字小于或者等于基准值,则将索引位置上的数与该数字进行交换,同时索引+1

  • 将基准值与当前索引位置进行交换

通过以上3个步骤,就将以基准值为中心,数组的左右两侧数字分别比基准值小或者大了。这个时候在递归的原地分区,就可以得到已排序后的数组。

原地分区算法实现

  1. // 交换数组元素位置
  2. function swap(array, i, j) {
  3. var temp = array[i];
  4. array[i] = array[j];
  5. array[j] = temp;
  6. }
  7. function partition(array, left, right) {
  8. var index = left;
  9. var pivot = array[right]; // 取最后一个数字当做基准值,这样方便遍历
  10. for (var i = left; i < right; i++) {
  11. if (array[i] <= pivot) {
  12. swap(array, index, i);
  13. index++;
  14. }
  15. }
  16. swap(array, right, index);
  17. return index;
  18. }

因为我们需要递归的多次原地分区,同时,又不想额外的地址空间所以,在实现分区算法的时候会有3个参数,分别是原数组array,需要遍历的数组起点left以及需要遍历的数组终点right。

最后返回一个已经排好序的index值用于下次递归,该索引对应的值一定比索引左侧的数组元素小,比所有右侧的数组元素大。

再次基础上我们还是可以进一步的优化分区算法,我们发现 <=pivot可以改为<pivot,这样可以减少一次交换

原地分区版快速排序实现

  1. function quickSort(array) {
  2. function swap(array, i, j) {
  3. var temp = array[i];
  4. array[i] = array[j];
  5. array[j] = temp;
  6. }
  7. function partition(array, left, right) {
  8. var index = left;
  9. var pivot = array[right]; // 取最后一个数字当做基准值,这样方便遍历
  10. for (var i = left; i < right; i++) {
  11. if (array[i] < pivot) {
  12. swap(array, index, i);
  13. index++;
  14. }
  15. }
  16. swap(array, right, index);
  17. return index;
  18. }
  19. function sort(array, left, right) {
  20. if (left > right) {
  21. return;
  22. }
  23. var storeIndex = partition(array, left, right);
  24. sort(array, left, storeIndex - 1);
  25. sort(array, storeIndex + 1, right);
  26. }
  27. sort(array, 0, array.length - 1);
  28. return array;
  29. }

二、字符串

1、回文字符串

  1. //判断回文字符串
  2. function palindrome(str) {
  3. var reg = /[W_]/g;
  4. var str0 = str.toLowerCase().replace(reg, "");
  5. var str1 = str0.split("").reverse().join("");
  6. return str0 === str1;
  7. }

2、翻转字符串

  1. function reverseString(str) {
  2. return str.split("").reverse().join("");
  3. }

3、字符串中出现最多次数的字符

  1. function findMaxDuplicateChar(str) {
  2. var cnt = {}, //用来记录所有的字符的出现频次
  3. c = ''; //用来记录最大频次的字符
  4. for (var i = 0; i < str.length; i++) {
  5. var ci = str[i];
  6. if (!cnt[ci]) {
  7. cnt[ci] = 1;
  8. } else {
  9. cnt[ci]++;
  10. }
  11. if (c == '' || cnt[ci] > cnt[c]) {
  12. c = ci;
  13. }
  14. }
  15. console.log(cnt) return c;
  16. }

三、数组

1、数组去重

  1. //数组去重
  2. function uniqueArray(arr) {
  3. var temp = [];
  4. for (var i = 0; i < arr.length; i++) {
  5. if (temp.indexOf(arr[i]) == -1) {
  6. temp.push(arr[i]);
  7. }
  8. }
  9. return temp;
  10. //or
  11. return Array.from(new Set(arr));
  12. }

四、查找

1、二分查找

  1. //二分查找
  2. function binary_search(arr, l, r, v) {
  3. if (l > r) {
  4. return -1;
  5. }
  6. var m = parseInt((l + r) / 2);
  7. if (arr[m] == v) {
  8. return m;
  9. } else if (arr[m] < v) {
  10. return binary_search(arr, m+1, r, v);
  11. } else {
  12. return binary_search(arr, l, m-1, v);
  13. }
  14. }

将二分查找运用到之前的插入排序中,形成二分插入排序,据说可以提高效率。但我测试的时候也许是数据量太少,并没有发现太明显的差距。。大家可以自己试验一下~(譬如在函数调用开始和结束使用console.time('插入排序耗时')和console.timeEnd('插入排序耗时'))

五、树的搜索/遍历

1、深度优先搜索

  1. //深搜 非递归实现
  2. function dfs(node) {
  3. var nodeList = [];
  4. if (node) {
  5. var stack = [];
  6. stack.push(node);
  7. while(stack.length != 0) {
  8. var item = stack.pop();
  9. nodeList.push(item);
  10. var children = item.children;
  11. for (var i = children.length-1; i >= 0; i--) {
  12. stack.push(children[i]);
  13. }
  14. }
  15. } return nodeList;
  16. }
  17. //深搜 递归实现
  18. function dfs(node, nodeList) {
  19. if (node) {
  20. nodeList.push(node);
  21. var children = node.children;
  22. for (var i = 0; i < children.length; i++) {
  23. dfs(children[i], nodeList);
  24. }
  25. }
  26. return nodeList;
  27. }

2、广度优先搜索

  1. //广搜 非递归实现
  2. function bfs(node) {
  3. var nodeList = [];
  4. if (node != null) {
  5. var queue = [];
  6. queue.unshift(node);
  7. while (queue.length != 0) {
  8. var item = queue.shift();
  9. nodeList.push(item);
  10. var children = item.children;
  11. for (var i = 0; i < children.length; i++)
  12. queue.push(children[i]);
  13. }
  14. }
  15. return nodeList;
  16. }
  17. //广搜 递归实现
  18. var i=0;
  19. //自增标识符
  20. function bfs(node, nodeList) {
  21. if (node) {
  22. nodeList.push(node);
  23. if (nodeList.length > 1) {
  24. bfs(node.nextElementSibling, nodeList); //搜索当前元素的下一个兄弟元素
  25. }
  26. node = nodeList[i++];
  27. bfs(node.firstElementChild, nodeList); //该层元素节点遍历完了,去找下一层的节点遍历
  28. } return nodeList;
  29. }

高阶函数衍生算法

1、filter去重

filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。也可以这么理解,filter的回调函数把Array的每个元素都处理一遍,处理结果返回false则过滤结果去除该元素,true则留下来

用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

其实这个筛选函数有多个参数,filter(function (element, index, self),演示一个使用filter去重,像这样:

  1. var r,
  2. arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
  3. r = arr.filter(function (element, index, self) {
  4. return self.indexOf(element) === index;
  5. //拿到元素,判断他在数组里第一次出现的位置,是不是和当前位置一样,
  6. //一样的话返回true,不一样说明重复了,返回false。
  7. });

2、sort排序算法

排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个对象呢?

直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。

值得注意的例子:

  1. // 看上去正常的结果:
  2. ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
  3. // apple排在了最后:
  4. ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
  5. // 无法理解的结果:
  6. [10, 20, 1, 2].sort(); // [1, 10, 2, 20]

解释原因

第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。

第三个排序结果,简单的数字排序都能错。

这是因为Array的sort()方法默认把所有元素先转换为String再排序,结果’10’排在了’2’的前面,因为字符’1’比字符’2’的ASCII码小。

因此我们把结合这个原理:

  1. var arr = [10, 20, 1, 2];
  2. arr.sort(function (x, y) {    
  3. if (x < y) {
  4. return -1;
  5. }
  6. if (x > y) {
  7. return 1;
  8. } return 0;
  9. });
  10. console.log(arr); // [1, 2, 10, 20]

上面的代码解读一下:传入x,y,如果x<y,返回-1,x与前面排,如果x>y,返回-1,x后面排,如果x=y,无所谓谁排谁前面。

还有一个,sort()方法会直接对Array进行修改,它返回的结果仍是当前Array,一个例子:

  1. var a1 = ['B', 'A', 'C'];var a2 = a1.sort();
  2. a1; // ['A', 'B', 'C']
  3. a2; // ['A', 'B', 'C']
  4. a1 === a2; // true, a1和a2是同一对象

原创系列推荐

1. JavaScript 重温系列(22篇全)

2. ECMAScript 重温系列(10篇全)

3. JavaScript设计模式 重温系列(9篇全)

4. 正则 / 框架 / 算法等 重温系列(16篇全)

5. Webpack4 入门手册(共 18 章)(上)

6. Webpack4 入门手册(共 18 章)(下)

7. 59篇原创系列汇总

点这,与大家一起分享本文吧

【算法】342- JavaScript常用基础算法的更多相关文章

  1. javascript常用排序算法实现

    毕业后,由于工作中很少需要自已去写一些排序,所以那些排序算法都忘得差不多了,不过排序是最基础的算法,还是不能落下啦,于是找了一些资料,然后用Javascript实现了一些常用的算法,具体代码如下: & ...

  2. javascript常用经典算法实例详解

    javascript常用经典算法实例详解 这篇文章主要介绍了javascript常用算法,结合实例形式较为详细的分析总结了JavaScript中常见的各种排序算法以及堆.栈.链表等数据结构的相关实现与 ...

  3. ACM算法模板 · 一些常用的算法模板-模板合集(打比赛专用)

    ACM算法模板 · 一些常用的算法模板-模板合集(打比赛专用)

  4. javascript常用排序算法总结

    算法是程序的灵魂.虽然在前端的开发环境中排序算法不是很经常用到,但常见的排序算法还是应该要掌握的.我在这里从网上整理了一下常见排序算法的javascript实现,方便以后查阅. 归并排序: 1 fun ...

  5. javascript常用数组算法总结

    1.数组去重 方法1: JavaScript //利用数组的indexOf方法 function unique (arr) { var result = []; for (var i = 0; i & ...

  6. 从一段简单算法题来谈二叉查找树(BST)的基础算法

    先给出一道很简单,喜闻乐见的二叉树算法题: 给出一个二叉查找树和一个目标值,如果其中有两个元素的和等于目标值则返回真,否则返回假. 例如: Input: 5 / \ 3 6 / \ \ 2 4 7 T ...

  7. 转载部长一篇大作:常用排序算法之JavaScript实现

    转载部长一篇大作:常用排序算法之JavaScript实现 注:本文是转载实验室同门王部长的大作,找实习找工作在即,本文颇有用处!原文出处:http://www.cnblogs.com/ywang172 ...

  8. 今天给大家分享一下js中常用的基础算法

    今天给大家分享一下js中常用的基础算法,废话不多说,直接上代码: 1.两个数字调换顺序 ,b= function fun(a,b){ b = b - a ;// a = 2 ; b = 2 a = a ...

  9. JavaScript常用算法

    一.排序算法 1.Array.sort(function)(JavaScript原生排序算法)参数:比较函数(可选)若无参数,则按照首字母的ASCII码排序,比较函数的作用为确定排序 function ...

随机推荐

  1. beyong Compare4解决30天的评估期结束

    刚开始是删掉注册表的CacheId(无效) 1.在搜索栏中输入 regedit ,打开注册表2.删除项目CacheId :HKEY_CURRENT_USER\Software\Scooter Soft ...

  2. C#怎么实现文件下载功能的四种方法

    using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Secu ...

  3. hdu 1874 畅通工程续 (floyd)

    畅通工程续Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  4. nyoj 243-交换输出 (swap)

    243-交换输出 内存限制:64MB 时间限制:3000ms 特判: No 通过数:16 提交数:39 难度:1 题目描述: 输入n(n<100)个数,找出其中最小的数,将它与最前面的数交换后输 ...

  5. set map symbol

    set 声明 let set = new Set();即创建了一个空的set 赋值 let set = new Set(['张三','李四','王五']); 特性 似于数组,但它的一大特性就是所有元素 ...

  6. PHP提高SESSION响应速度的方法有哪些

    1.设置多级目录存储SESSION 默认session的存储目录是1级目录,如果用户量比较大,session文件数量就比较大,我们可以设置目录数为2,使用2级目录可以提交查找和存取速度.不过这种方式对 ...

  7. python3 之 变量作用域详解

    作用域: 指命名空间可直接访问的python程序的文本区域,这里的 ‘可直接访问’ 意味着:对名称的引用(非限定),会尝试在命名空间中查找名称: L:local,局部作用域,即函数中定义的变量: E: ...

  8. java this,super简单理解

    *****this****** 表示对当前对象的引用. 作用:1.区分实例变量和局部变量(this.name----->实例变量name) 2.将当前对象当做参数传递给其它对象和方法.利用thi ...

  9. Oracle temp table

    Tow kinds of temp table data keep method. One is delete when commit Anothe one is preseve when commi ...

  10. 虚拟机中linux操作系统raid5(5块磁盘,3块做raid,2块做备份)配置流程及损坏磁盘的移除

    1.打开所要用的虚拟机,点击编辑虚拟机设置,点击硬盘,添加 2.一直点击下一步不做修改,直到最后完成 3.按照以上步骤添加5块磁盘 4.点击开启虚拟机,输入用户名root密码登录进去 5.进入虚拟机后 ...