第一部分:数据结构中常用的排序算法

  数据结构中的排序算法一般包括冒泡排序、选择排序、插入排序、归并排序和 快速排序, 当然还有很多其他的排序方式,这里主要介绍这五种排序方式。

  排序是数据结构中的主要内容, 并不限于语言而主要在于思想,这里用js实现。

  

一、冒泡排序

  由小到大。

  名称由来: 循环时两两比较,每次循环都会将无序数组中的最大值放在后头。

  冒泡排序是我在学习C++时最先学习的一种排序方式,因为它理解简单,所以往往是入门之首选。

  规则: 既然是冒泡,那么越靠近前面(下面--最开始)的泡越小,越靠近后面(上面--结束)泡越大,故最大的泡在最上面。

  1. <script>
  2. /*
  3. * 冒泡排序
  4. * 遍历一次就把最大的放在最后面
  5. * 排序完毕
  6. */
  7. function bubble(arr) {
  8. var len = arr.length,
  9. i,
  10. j,
  11. temp;
  12. for ( i = ; i < len; i++) {
  13. for ( j = ; j < len - i - ; j++) {
  14. if (arr[j] > arr[j + ]) {
  15. temp = arr[j];
  16. arr[j] = arr[j + ];
  17. arr[j + ] = temp;
  18. }
  19. }
  20. }
  21. console.log(arr);
  22. }
  23. bubble([, , , , ]); // [12, 17, 18, 28, 29]
  24. bubble([, , , , ]); // [8, 17, 29, 35, 58]
  25. // 注意事项: 数组的长度需要多次使用,并在使用过程中不会改变,所以提前缓存起来。并注意单变量原则的使用。另外,在写代码的过程中,尽量将数字带入思考,而不是死记硬背,这样效果会好很多。
  26. </script>

  可以看到,冒泡排序中包含有嵌套的两个循环,这导致了二次方的复杂度。即冒泡排序的复杂度为 O(n2)。

二、 选择排序

   由小到大。

   名称由来: 将无序数组中的最小值放在最前面。

   选择排序的思路和冒泡排序的思路是差不多的, 冒泡排序是将最大的数放在最后面,以后的每一次不再掺合最后放置的数。

   而选择排序的思路是每次将 “无序数组” 中的第一个数字和后面的每一个做出比较,将最小的放在最前面形成有序数组, 然后后续循环比较的时候就不再比较最前面的有序数组, 而只是将无序数组的最小值放在最前面。

  1. <script>
  2. /*
  3. * 简单选择排序
  4. * 首先假设“第一个”数最小,如果遇到后面还有更小的,就放在前面即可
  5. * 排序完毕
  6. */
  7. function simpleSelectionSort(arr) {
  8. var len = arr.length,
  9. i,
  10. j,
  11. temp,
  12. min;
  13. for (i = ; i < len; i++) {
  14. min = i;
  15. for (j = i + ; j < len; j++ ) {
  16. if (arr[j] < arr[min]) {
  17. min = j;
  18. }
  19. }
  20. if (min !== i) {
  21. temp = arr[min];
  22. arr[min] = arr[i];
  23. arr[i] = temp;
  24. }
  25. }
  26. console.log(arr);
  27. }
  28. simpleSelectionSort([, , , , , ]); // [8, 12, 17, 18, 28, 29]
  29. simpleSelectionSort([, , , , , ]); // [65, 66, 92, 93, 95, 97]
  30. // 注意事项:很好理解,最好不要使用<=类似的符号,因为需要用<=的,我们一般都可以使用<并改变数字大小来实现。
  31.  
  32. </script>

  选择排序同样也是一个复杂度为 O(n 2) 的算法,和冒泡排序一样, 它包含有嵌套的两个循环, 这就导致了二次方的复杂度。

三、插入排序

  由小到大

  名称由来,前面的数组总是确定的, 当前的数字根据大小关系来选择需要插入的位置 --- 插入排序。

  插入排序的思想: 假设第一个数字已经是排序好的,我们循环的时候从第二个数字开始; 先把这个数字用temp存起来, 和前面的做出比较,如果说前面的数字大, 就把前面的数字放在当前位置,那缓存的数字就该放在前面数字的位置吗? 不是的, 还要继续比较缓存的数字和前面的前面的数字的大小关系,如果前面的前面的数字比这个缓存数字小,好,缓存数字就放在前面的数字的位置, 否则,就接着循环,直到满足条件。

  1. <script>
  2. /*
  3. * 插入排序
  4. * 就像整理扑克牌一样,前面的是有序的,每次拿到一张牌,我们就比较他们之前的大小关系,然后插入即可
  5. * i从1开始,是因为我们认为第一个数字是排列好的,而每循环一次,就认为前面已经排列好了一次。
  6. * 排序完毕
  7. */
  8. function insertSort(arr) {
  9. var len = arr.length,
  10. i,
  11. j,
  12. temp;
  13. for (i = ; i < len; i++) {
  14. temp = arr[i];
  15. j = i;
  16. while ((temp < arr[j - ])&&(j > )) {
  17. arr[j] = arr[j - ];
  18. j--;
  19. }
  20. arr[j] = temp;
  21. }
  22. console.log(arr);
  23. }
  24.  
  25. insertSort([, , , , , ]); //[8, 12, 17, 18, 28, 29]
  26. // 注意:一定要提前把arr[i]缓存起来,否则就会被覆盖掉;
  27. </script>

  排序小型的数组时, 此算法比选择排序和冒泡排序的性能要好。

  

四、 快速排序

  1. <script>
  2. /*
  3. * 快速排序
  4. * 即取得中间的值,然后把小于中间的放在左边,大于中间的放在右边,递归执行函数即可
  5. */
  6. Array.prototype.quickSort = function () {
  7. // 获取长度
  8. var len = this.length;
  9. // 如何长度不大于1, 直接返回即可
  10. if (len <= ) {
  11. return this.slice();
  12. }
  13. // 定义left、right、mid数组
  14. // 注意:mid必须要是数组,因为我们希望用到数组的concat方法。 这样才能完成递归调用
  15. var left = [],
  16. right = [],
  17. mid = [this[]];
  18. // 这里从i=1开始即可,因为i=0已经让我们看做中间值了,就算是用了i=0,也是做得无意义的比较
  19. for (var i = ; i < len; i++) {
  20. if (this[i] < mid[]) {
  21. left.push(this[i]);
  22. }
  23. if (this[i] > mid[]) {
  24. right.push(this[i]);
  25. }
  26. }
  27. // 精髓,递归调用。关键在于当length <= 1时,就返回了,即递归调用一定要有一个出口。
  28. return left.quickSort().concat(mid.concat(right.quickSort()));
  29. };
  30. var arr = [, , , ];
  31. console.log(arr.quickSort()); // [5, 12, 45, 69]
  32. </script>

五、归并排序

  1. <script>
  2. /*
  3. * 归并排序
  4. * 采用的是分治法的思想,即首先将整个数组分成单个的即长度为1的数组,然后最后进行有序的合并。
  5. */
  6. Array.prototype.mergeSort = function () {
  7. var merge = function (left, right) {
  8. var final = [];
  9. while (left.length && right.length) {
  10. final.push(left[] < right[] ? left.shift() : right.shift());
  11. }
  12. return final.concat(left.concat(right));
  13. }
  14. var len = this.length;
  15. if (len < ) {
  16. return this;
  17. }
  18. var mid = len/;
  19. return merge(this.slice(, parseInt(mid)).mergeSort(), this.slice(parseInt(mid)).mergeSort());
  20. }
  21. var arr = [, , , , , , , ];
  22. console.log(arr.mergeSort()); //[8, 12, 15, 18, 28, 29, 45, 56]
  23. </script>

六、希尔排序

  1. <script>
  2. /*
  3. * 希尔排序
  4. * 思想: 把数组按照一定的gap抓取即可,抓取得到的使用插入排序
  5. * 排序完毕
  6. */
  7. Array.prototype.shellSort = function () {
  8. var gap, i, j;
  9. var temp;
  10. for (gap = this.length >> ; gap > ; gap >>= ) {
  11. for (i = gap; i < this.length; i++) {
  12. temp = this[i];
  13. for (j = i - gap; j >= && this[j] > temp; j -= gap) {
  14. this[j + gap] = this[j];
  15. }
  16. this[j + gap] = temp;
  17. }
  18. }
  19. console.log(this);
  20. };
  21. [, , , ].shellSort();
  22. </script>

七、堆排序

  1. <script>
  2. /*
  3. * 堆排序: 即每次建立一个堆,然后把大的往后放,每次都要使用递归
  4. */
  5. function heapSort(arr) {
  6.  
  7. // 交换函数
  8. function swap(i, j) {
  9. var tmp = arr[i];
  10. arr[i] = arr[j];
  11. arr[j] = tmp;
  12. }
  13.  
  14. // start 即认为是根节点, end认为是最后一个index然后加1
  15. function heapify(start, end) {
  16. var dad = start;
  17. // son为左子树的序号
  18. var son = dad* + ;
  19.  
  20. if (son >= end) {
  21. return;
  22. }
  23. // 选择两者中的大者,将大者和dad比较,然后如果更大就替换
  24. if (son + < end && arr[son] < arr[son + ]) {
  25. son++;
  26. }
  27. if (arr[son] > arr[dad]) {
  28. swap(son, dad);
  29. heapify(son, end);
  30. }
  31. }
  32. var len = arr.length;
  33. // 从最后一个父节点开始,进行一次堆排序,因为如何不是父节点,就是没有意义的。
  34. for (var i = Math.floor(len/) - ; i >= ; i--) {
  35. heapify(i, len);
  36. }
  37.  
  38. // 整个的排序与交换位置
  39. for (var i = len - ; i >= ; i--) {
  40. swap(, i);
  41. heapify(, i);
  42. }
  43. console.log(arr);
  44. }
  45. heapSort([, , , , ]); //[15, 25, 89, 482, 555]
  46. </script>

第二部分:时间复杂度&&空间复杂度

  

一、复杂度总结

1、时间复杂度  

  平均情况下,快速排序、希尔排序、归并排序和堆排序的时间复杂度都是O(nlog2n),其他的都是O(n2)。一个特殊的是基数排序,其时间复杂度为 O(d(n + rd))

   最坏情况下快速排序的时间复杂度为O(n2), 而其他的都和平均情况下是一样的

2、空间复杂度

   空间复杂度中记住几个比较特殊的就好了, 快速排序为 O(log2n), 归并排序为O(n),基数排序为O(rd),其他的都是O(1)

  

3、其他

   直接插容易变成O(n), 起泡起的好变成O(n),所谓“容易插”或者“起的好”都是指初始序列已经有序

二、算法稳定性总结

  一句话记忆: 考研复习苦啊,心情不稳定快些选好友来聊天吧。

  这里, “快“指的是快速排序,“些”指的是希尔排序“选”指的是简单选择排序”指的是堆排序,这四种方式都是不稳定的,其他都是稳定的。 

三、 其他细节

  1. 经过一趟排序,能够保证一个元素可以到达最终位置,这样的排序时交换类的那两种(起泡、快速)和选择类的排序(简单选择和堆)。
  2. 排序方法的元素比较次数和原始序列无关 --- 简单选择排序折半插入排序
  3. 排序方法的排序趟数和原始序列有关 --- 交换类的排序

数据结构中常用的排序算法 && 时间复杂度 && 空间复杂度的更多相关文章

  1. C#中常用的排序算法的时间复杂度和空间复杂度

    常用的排序算法的时间复杂度和空间复杂度   常用的排序算法的时间复杂度和空间复杂度 排序法 最差时间分析 平均时间复杂度 稳定度 空间复杂度 冒泡排序 O(n2) O(n2) 稳定 O(1) 快速排序 ...

  2. JavaScript实现常用的排序算法

    ▓▓▓▓▓▓ 大致介绍 由于最近要考试复习,所以学习js的时间少了 -_-||,考试完还会继续的努力学习,这次用原生的JavaScript实现以前学习的常用的排序算法,有冒泡排序.快速排序.直接插入排 ...

  3. 常用Java排序算法

    常用Java排序算法 冒泡排序 .选择排序.快速排序 package com.javaee.corejava; public class DataSort { public DataSort() { ...

  4. java SE 常用的排序算法

    java程序员会用到的经典排序算法实现 常用的排序算法(以下代码包含的)有以下五类: A.插入排序(直接插入排序.希尔排序) B.交换排序(冒泡排序.快速排序) C.选择排序(直接选择排序.堆排序) ...

  5. 常用的排序算法介绍和在JAVA的实现(二)

    一.写随笔的原因:本文接上次的常用的排序算法介绍和在JAVA的实现(一) 二.具体的内容: 3.交换排序 交换排序:通过交换元素之间的位置来实现排序. 交换排序又可细分为:冒泡排序,快速排序 (1)冒 ...

  6. 十大排序算法时间复杂度 All In One

    十大排序算法时间复杂度 All In One 排序算法时间复杂度 排序算法对比 Big O O(n) O(n*log(n)) O(n^2) 冒泡排序 选择排序 插入排序 快速排序 归并排序 基数排序 ...

  7. 常用的排序算法介绍和在JAVA的实现(一)

    一.写随笔的原因:排序比较常用,借此文介绍下排序常用的算法及实现,借此来MARK一下,方便以后的复习.(本人总是忘得比较快) 二.具体的内容: 1.插入排序 插入排序:在前面已经排好序的序列中找到合适 ...

  8. C语言几种常用的排序算法

    /* ============================================================================= 相关知识介绍(所有定义只为帮助读者理解 ...

  9. php常用的排序算法与二分法查找

    一 : 归并排序 将两个的有序数列合并成一个有序数列,我们称之为"归并".归并排序(Merge Sort)就是利用归并思想对数列进行排序.根据具体的实现,归并排序包括"从 ...

随机推荐

  1. (匹配 Hopcroft-Karp算法)Rain on your Parade -- Hdu --2389

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=2389 不能用匈牙利,会TEL的,用Hopcroft-Karp Hopcroft-Karp课件 以前是寻找 ...

  2. Robotframework-Appium 之常用API(一)

    上一遍隨筆(https://www.cnblogs.com/cnkemi/p/9639809.html)用Python + Robotframework + Appium對Android app小試牛 ...

  3. 团体程序设计天梯赛L2-001 紧急救援 2017-03-22 17:25 93人阅读 评论(0) 收藏

    L2-001. 紧急救援 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 作为一个城市的应急救援队伍的负责人,你有一张特殊的全国 ...

  4. javaWeb项目中到底什么是单例,多例

    你用杯子喝可乐,喝完了不刷,继续去倒果汁喝,就是单例.你用杯子喝可乐,直接扔了杯子,换个杯子去倒果汁喝,就是多例. 数据库连接池就是单例模式,有且仅有一个连接池管理者,管理多个连接池对象. 1. 什么 ...

  5. [Yii2]yiisoft/yii2 2.0.2 requires bower-asset/jquery 2.1.*@stable | 1.11.*@stable -> no matching package found

    composer require "dektrium/yii2-user:0.9.*@dev" 一直安装失败,提示:Your requirements could not be r ...

  6. 咏南 DATASNAP LINUX中间件

    咏南 DATASNAP LINUX中间件,一套源码,同时支持WINDOWS和LINUX操作系统. 基于DELPHI 10.2 TOKYO开发 使用FIREDAC数据库引擎,支持MYSQL,MSSQL, ...

  7. Elasticsearch 安装的时候,Unsupported major.minor version 51.0问题的解决

    Elasticsearch安装的时候报错 bootstrap/Elasticsearch : Unsupported major.minor version 51.0 网上一般说的方法是,升级或者降级 ...

  8. JQuery --- 第五期 (JQuery节点操作)

    学习笔记 1.JQuery添加节点相关方法 <!DOCTYPE html> <html lang="en"> <head> <meta c ...

  9. [Git00] Pro Git 一二章读书笔记

    记得知乎以前有个问题说:如果用一天的时间学习一门技能,选什么好?里面有个说学会Git是个很不错选择,今天就抽时间感受下Git的魅力吧.   Pro Git (Scott Chacon) 读书笔记:   ...

  10. 学习React前端框架,报错 'render' is not defined no-undef

    报错 'render' is not defined no-undef 原因没有 写 import { render } from 'react-dom'