各种排序算法

博客地址:https://ainyi.com/54

算法懂的不多,但是最基本的排序算法还是要理解的~~

十大经典算法排序对比

  • 名词解释: n: 数据规模 k:“桶”的个数 In-place: 占用常数内存,不占用额外内存 Out-place: 占用额外内存 稳定性:排序后2个相等键值的顺序和排序之前它们的顺序相同

冒泡排序(Bubble Sort)

冒泡排序应该是最早接触也是最简单的排序算法之一啦~

当数据正序的时候,最快

当数据反序的时候,最慢

冒泡排序动图演示

冒泡排序 JavaScript 代码实现

function bubbleSort(arr) {
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    for (var j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j+1]) {        // 相邻元素两两对比
        var temp = arr[j+1];        // 元素交换
        arr[j+1] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}

冒泡排序还有一种优化算法,就是立一个flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序

选择排序(Selection Sort)

无论什么数据进去都是 O(n²) 的时间复杂度,所以用到它的时候,数据规模越小越好

唯一的好处可能就是不占用额外的内存空间了吧

选择排序动图演示

选择排序 JavaScript 代码实现

function selectionSort(arr) {
  var len = arr.length;
  var minIndex, temp;
  for (var i = 0; i < len - 1; i++) {
    minIndex = i;
    for (var j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {     // 寻找最小的数
        minIndex = j;                 // 将最小数的索引保存
      }
    }
    temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
  return arr;
}

插入排序(Insertion Sort)

插入排序动图演示

插入排序 JavaScript 代码实现

function insertionSort(arr) {
  var len = arr.length;
  var preIndex, current;
  for (var i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while(preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex+1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex+1] = current;
  }
  return arr;
}

希尔排序(Shell Sort)

希尔排序是插入排序的一种更高效率的实现。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列

希尔排序 JavaScript 代码实现

function shellSort(arr) {
  var len = arr.length,
  var temp,
  var gap = 1;
  while(gap < len/3) {          //动态定义间隔序列
    gap =gap*3+1;
  }
  for (gap; gap > 0; gap = Math.floor(gap/3)) {
    for (var i = gap; i < len; i++) {
      temp = arr[i];
      for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
        arr[j+gap] = arr[j];
      }
      arr[j+gap] = temp;
    }
  }
  return arr;
}

归并排序(Merge Sort)

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  1. 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第2种方法)
  2. 自下而上的迭代

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn)的时间复杂度,代价是需要额外的内存空间

归并排序动图演示

归并排序JavaScript代码实现

这里采用自上而下的递归方法function mergeSort(arr) { var len = arr.length; if(len < 2) { return arr; } var middle = Math.floor(len / 2), var left = arr.slice(0, middle), var right = arr.slice(middle); return merge(mergeSort(left), mergeSort(right)); }function merge(left, right) { var result = []; while (left.length>0 && right.length>0) { if (left0 <= right0) { result.push(left.shift()); } else { result.push(right.shift()); } } while (left.length) { result.push(left.shift()); } while (right.length) { result.push(right.shift()); } return result; }

快速排序(Quick Sort)

又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高! 它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(nlogn) 的排序算法表现要更好

快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn) ,且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序

快速排序动图演示

function quickSort(arr, left, right) {
  var len = arr.length,
  var partitionIndex,
  var left = typeof left != 'number' ? 0 : left,
  var right = typeof right != 'number' ? len - 1 : right;

  if (left < right) {
    partitionIndex = partition(arr, left, right);
    quickSort(arr, left, partitionIndex-1);
    quickSort(arr, partitionIndex+1, right);
  }
  return arr;
}

function partition(arr, left ,right) {     // 分区操作
  var pivot = left,                      // 设定基准值(pivot)
  var index = pivot + 1;
  for (var i = index; i <= right; i++) {
    if (arr[i] < arr[pivot]) {
      swap(arr, i, index);
      index++;
    }        
  }
  swap(arr, pivot, index - 1);
  return index-1;
}

function swap(arr, i, j) {
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

堆排序(Heap Sort)

堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列

堆排序动图演示

堆排序 JavaScript 代码实现

var len;    // 因为声明的多个函数都需要数据长度,所以把 len 设置成为全局变量
function buildMaxHeap(arr) {   // 建立大顶堆
  len = arr.length;
  for (var i = Math.floor(len/2); i >= 0; i--) {
    heapify(arr, i);
  }
}

function heapify(arr, i) {     // 堆调整
  var left = 2 * i + 1,
  var right = 2 * i + 2,
  var largest = i;

  if (left < len && arr[left] > arr[largest]) {
    largest = left;
  }

  if (right < len && arr[right] > arr[largest]) {
    largest = right;
  }

  if (largest != i) {
    swap(arr, i, largest);
    heapify(arr, largest);
  }
}

function swap(arr, i, j) {
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

function heapSort(arr) {
  buildMaxHeap(arr);

  for (var i = arr.length-1; i > 0; i--) {
     swap(arr, 0, i);
     len--;
     heapify(arr, 0);
  }
  return arr;
}

计数排序(Counting Sort)

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中

作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

计数排序动图演示

计数排序 JavaScript 代码实现

function countingSort(arr, maxValue) {
  var bucket = new Array(maxValue+1),
  var sortedIndex = 0;
  var arrLen = arr.length,
  var bucketLen = maxValue + 1;

  for (var i = 0; i < arrLen; i++) {
    if (!bucket[arr[i]]) {
      bucket[arr[i]] = 0;
    }
    bucket[arr[i]]++;
  }

  for (var j = 0; j < bucketLen; j++) {
    while(bucket[j] > 0) {
      arr[sortedIndex++] = j;
      bucket[j]--;
    }
  }

  return arr;
}

桶排序(Bucket Sort)

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定

为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的N个数据均匀的分配到K个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要

  • 什么时候最快(Best Cases): 当输入的数据可以均匀的分配到每一个桶中
  • 什么时候最慢(Worst Cases): 当输入的数据被分配到了同一个桶中

桶排序 JavaScript 代码实现

function bucketSort(arr, bucketSize) {
  if (arr.length === 0) {
    return arr;
  }

  var i;
  var minValue = arr[0];
  var maxValue = arr[0];
  for (i = 1; i < arr.length; i++) {
    if (arr[i] < minValue) {
      minValue = arr[i];                // 输入数据的最小值
    } else if (arr[i] > maxValue) {
      maxValue = arr[i];                // 输入数据的最大值
    }
  }

  // 桶的初始化
  var DEFAULT_BUCKET_SIZE = 5;            // 设置桶的默认数量为5
  bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
  var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;   
  var buckets = new Array(bucketCount);
  for (i = 0; i < buckets.length; i++) {
    buckets[i] = [];
  }

  // 利用映射函数将数据分配到各个桶中
  for (i = 0; i < arr.length; i++) {
    buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
  }

  arr.length = 0;
  for (i = 0; i < buckets.length; i++) {
    insertionSort(buckets[i]);                      // 对每个桶进行排序,这里使用了插入排序
    for (var j = 0; j < buckets[i].length; j++) {
      arr.push(buckets[i][j]);                      
    }
  }

  return arr;
}

基数排序(Radix Sort)

基数排序有两种方法:

  1. MSD 从高位开始进行排序
  2. LSD 从低位开始进行排序

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

基数排序:根据键值的每位数字来分配桶

计数排序:每个桶只存储单一键值

桶排序:每个桶存储一定范围的数值

LSD 基数排序动图演示

基数排序 JavaScript 代码实现

// LSD Radix Sort
var counter = [];
function radixSort(arr, maxDigit) {
  var mod = 10;
  var dev = 1;
  for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
    for(var j = 0; j < arr.length; j++) {
      var bucket = parseInt((arr[j] % mod) / dev);
      if(counter[bucket]==null) {
        counter[bucket] = [];   
      }
      counter[bucket].push(arr[j]);
    }

    var pos = 0;
    for(var j = 0; j < counter.length; j++) {
      var value = null;
      if(counter[j]!=null) {
        while ((value = counter[j].shift()) != null) {
          arr[pos++] = value;
        }
      }
    }
  }
  return arr;
}

博客地址:https://ainyi.com/54

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

《Kotlin 程序设计》第六章 Kotlin 函数式编程(FP)第六章 Kotlin 函数式编程(FP)1. 函数式编程概述2. Kotlin函数式编程参考资料

从本质上来说, 程序就是一系列有序执行的指令集合。 如何将指令集合组织成可靠可用可信赖的软件(美妙的逻辑之塔), 这是个问题。

14460
来自专栏专注研发

poj-2909-哥德巴赫猜想

For any even number n greater than or equal to 4, there exists at least one pair...

9310
来自专栏HappenLee的技术杂谈

C++雾中风景8:Lambda表达式

Lambda表达式是函数式编程的重要的语法结构。 Lambda 表达式(lambda expression)说起来很简单,就是一个匿名函数,即没有函数名的函数...

11120
来自专栏zaking's

js算法初窥02(排序算法02-归并、快速以及堆排序)

20230
来自专栏chenjx85的技术专栏

leetcode-551-Student Attendance Record I(判断是否出现连续几个相同字符)

15360
来自专栏大数据杂谈

从 Zero 到 Hero ,一文掌握 Python

21990
来自专栏牛客网

校招面试手撕算法汇总

所有题目都是从面经中提取而来,持续更新。 本人也是菜鸟一枚,帖子也会相应的发布自己对于题目的解法和看法,但是可能想得不够,也希望大家能够一起讨论,一起进步。 1...

445110
来自专栏blackheart的专栏

[程序设计语言]-[核心概念]-03:控制流

0.概述 前面介绍了语言的演进以及一些基础概念后,从本篇开始进入了语言的核心问题中。这一篇讨论的是语言计算模型(大致可以用控制流来表述),大致如下7种: 顺序执...

218100
来自专栏算法修养

温故KMP算法

最近由于某些原因,又回顾了一次KMP算法。上一次回顾KMP算法还是在刷题的时候遇到的: http://blog.csdn.net/dacc123/article...

39680
来自专栏chenjx85的技术专栏

leetcode-162-寻找峰值

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

24830

扫码关注云+社区

领取腾讯云代金券