总结 6种排序算法

299 阅读4分钟

排序算法是一种能将一串资料依照特定排序方式进行排列的算法

排序算法的输出必须遵守的原则

输出结果为递增序列(递增是针对所需的排序顺序而言)
输出结果是原输入的一种排列、或是重组

关于算法的相关概念

稳定排序   如果a原本在b的前面,且a=b,排序之后a仍然在b的前面,则为稳定排序
非稳定排序 如果a原本在b的前面,且a=b,排序之后a可能不在b的前面,则为非稳定排序
原地排序   指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的排序
非原地排序 需要利用额外的数组来辅助排序
时间复杂度 一个算法执行所消耗的时间
空间复杂度 运行完一个算法所需的内存大小

快速排序(quickSort)

快速排序是分治的算法,核心是分区,每次选择一个元素为基准,并且将整个数组以基准分为两部分
比基准小的放基准前面,比基准大的放基准后面;基准的位置就是正确的,因为前面的都比它小,后面的都比它大
然后使用递归的把小于基准和大于基准的分区进行排序
时间复杂度:平均O(n log n) 最坏O(n^2)	空间复杂度O(log n)

代码实现
let quickSort = arr =>{
  if(arr.length <= 1){
    return arr;
  }
  let pivotIndex = Math.floor(arr.length/2);  // 声明基准,数组长度除2
  let pivot = arr.splice(provtIndex, 1)[0];   // 把基准单独取出
  let left = [];
  let right = [];
  for(let i=0; i<arr.length; i++){            // 分区,和基准进行对比
    if(arr.i < pivot){
      left.push(arr[i])
    }else{
      right.push(arr.i)
    }
  }                                           // 使用递归,把分区进行排序,然后连接成排好序的新数组
  return quickSort(left).concat([pivot, quickSort(right)])
}

计数排序(countSort)

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中
实现思路,使用一个哈希表做记录,发现数字N,就记做N:1,如果再次发现N就加1
最后把哈希表的key全部打出来,假设N:M,那么N就打印M次
时间复杂度:平均O(n+m)	空间复杂度O(n+m)

代码实现
let countSort = arr =>{
  let hashTable = {}, max = 0, result = [];     // 声明哈希,最大值,存放排好序的数组
  for(let i=0; i<arr.length; i++){              // 遍历数组
    if(!(arr[i] in hashTable)){                 // 如果数组的第i项不在哈希里,哈希的第i项=1
      hashTable[arr[i]] = 1                     // 指arr[1]出现的次数为1次
    }else{
      hashTable[arr[i]] += 1                    // 如果存在,就把arr[i]的次数加1
    }
    if(arr[i] > max){                           // 记录最大数,收集max
      max = arr[i]
    }
  }
  for(let j=0; j<=max; j++){                   // 遍历哈希表,j是数组的数据
    if(j in hashTable){                        // 如果j在哈希里,就放到result,返回result
      for(let i = 0; i<hashTable[j]; i++){
        result.push(j)
      }
    }
  }
  return result
}

冒泡排序(bubbleSort)

冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它顺序错误就把它们交换过来
走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成
实现思路:
步骤1:比较相邻的元素,如果第一个比第二个大,就交换它们两个
步骤2:对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
步骤3:针对所有的元素重复以上的步骤,除了最后一个
重复步骤1~3,直到排序完成
时间复杂度:O(n^2)	空间复杂度O(1)

代码实现
function bubbleSort(arr){                       // 使用2层循环执行排序
  for(let i = 0; i < arr.length - 1; i++){      // 内循环每执行一次,外层的i-1,表示前面的数据完成排序
    for(let j = 0; j < arr.length - i - 1; j++){// 内层循环每次将最小数据移动到数组最左边
      if(arr[j] > arr[j + 1]){     // 对比相邻的2个元素,如果前面的元素比后面的大,就交换位置
        swap(arr, j, j+1);
      }
    }
  }
  return arr;
}
function swap(arr, i, j){           // 换位,每次对比完相邻的的元素,满足条件进行交换位置
  let temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

选择排序(selectionSort)

选择排序的原理,以升序排序为例,就是从数组的开头开始,用第一条数据和其他数据进行比较
取其中最小的数据与第一个位置的数据交换,再用第二条数据对后面的数据进行比较......如此反复
时间复杂度:O(n^2)	空间复杂度O(1)

代码实现
function selectionSort(arr){                // 使用2层循环执行排序
  for(let i = 0; i < arr.length - 1; i++){  // 遍历数组,先记录最小值为i
    let index = i;                          // index记录当前循环最小值的位置
    for(let j = i+1; j < arr.length; j++){  // 内层循环从i后面1位开始寻找最小的数据
      if(arr[index] > arr[j]){              // 最小数比当前数小,把当前数记录为最小数
        index = j;
      }
    }
    swap(arr, i, index);                    // 交换位置
  }
  return arr;
}
function swap(arr, i, j){                  // 换位
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

归并排序(mergeSort)

归并排序的原理是将数组不断的对半拆分,直到拆分为单个元素的数组
然后将单个元素的数组进行组合,就排好序了,因为单个元素默认是排好序的
归并排序的核心是组合数组,组合时进行排序
时间复杂度:O(nlog^2n)   空间复杂度O(1)

代码实现
let mergeSort = arr =>{
  let k = arr.length                               // 得到数组的长度
  if(k===1){return arr}                            // 长度为1,返回数组
  let left = arr.slice(0, Math.floor(k/2))         // 拆分成左右两个数组,从数组的中间位置
  let right = arr.slice(Math.floor(k/2))
  return merge(mergeSort(left), mergeSort(right))  // 对拆分的数组继续拆分直到拆分成单个数组
}
let merge = (a, b) => {                            // 使用递归,合并单个元素的数组
  if(a.length === 0) return b                      // a数组为空时返回数组b
  if(b.length === 0) return a                      // b数组为空时返回数组a
  return a[0] > b[0] ?                             // 对比a,b数组第一位的大小
     [b[0]].concat(merge(a, b.slice(1))) :         // 如果b[0]小,就用b[0]连接a和b的其它部分的递归
     [a[0]].concat(merge(a.slice(1), b))           // 如果a[0]小,就用a[0]连接a的其它部分和b的递归
}

插入排序(insertSort)

插入排序的思路:将元素插入到已排序好的数组中
具体步骤
将第一个元素标记为已排序,遍历每个没有排序过的元素,提取元素
记录最后排过序的元素指数,将新提取的元素和已经排序过的元素进行对比
如果排过序的元素大于新提取的元素,将排序过的元素向右移一格;否则,插入提取的元素
时间复杂度:O(n^2)	空间复杂度O(1)

代码实现
function insertSort(arr) {
    var value,                            // 当前未排序要进行比较的值
        i,                                // 表示未排序部分的当前元素
        j;                                // 表示已排序部分的当前元素
    for (i=0; i < arr.length; i++) {      // 遍历未排序部分,提取的元素
        value = arr[i];                   // 当前未排序位置的元素的值
        for (j=i-1; j > -1 && arr[j] > value; j--) { // 当已排序部分的当前元素大于提取的元素
            arr[j+1] = arr[j];            // 将当前元素向后移一位,再将前一位与提取的元素比较
        }
        arr[j+1] = value;                 // 插入提取的元素            
    }
    return arr;
}