数据结构与算法基础(四)- 排序

274 阅读4分钟

这次主要讲几个经典的排序:

  • 冒泡排序
  • 插入排序
  • 选择排序
  • 归并排序
  • 快速排序
  • 计数排序
  • 桶排序

按时间复杂度度分类

  • O(n^2): 冒泡、插入、选择
  • O(nlogn): 快排、归并
  • O(n): 桶、计数、基数

一般情况下我们会从以下几个方面来衡量:

  • 最好情况、最坏情况、平均情况时间复杂度
  • 时间复杂度的系数、常数、低阶
  • 比较次数和交换次数

排序算法的内存消耗

内存消耗是通过空间复杂度来衡量

排序算法的稳定性

就是说序列中存在值相等的元素,经过排序之后,相等元素之前原有的先后顺序不变。

冒泡排序

 function bubbleSort(arr:Array) {
    const array = [...arr];2
    const length = array.length;
    if(!array.length){
        return;
    }
    for(let i = 0; i {
            let t = -1;
            if(array[index] > array[index+1]){
                t = array[index];
                array[index] = array[index+1];
                array[index+1] = t;
            }
        });
    }
    return array;
}
 

是原地排序法

是稳定排序法

时间复杂度O(n^2)

插入排序

说一下这个核心思想吧,就是把数据分成两个部分,一个是已排序,一个是未排序。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想就是取未排序的区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。真到未排序区间中元素为空,算法结束。

function insertSort(arr:Array) {
    const array = [...arr];
    const length = array.length;
    if(!array.length){
        return;
    }
    for(let i = 1; i= 0; j--){
            if(array[j] > t){
                array[j+1] = array[j];
            }else{
                break;
            }
        }
        array[j+1] = t;
    }
    return array;
}

是原地排序法

是稳定排序法

时间复杂度O(n^2)

选择排序

选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

这个代码跟插入排序类似,不写了 但他不是稳定算法

归并排序

归并排序就是把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排序好的两部分合并在一起,这样整个数组就都有序了。

function mergeSortItem(left: Array, right: Array) {
    let [l, r] = [0, 0];
      let result = [];
      while (l < left.length && r < right.length) {
        if (left[l] < right[r]) {
          result.push(left[l]);
          l++;
        } else {
          result.push(right[r]);
          r++;
        }
      }
      result = result.concat(left.slice(l, left.length));
      result = result.concat(right.slice(r, right.length));
    return result;
  
}
function mergeSort(arr: Array):Array {
    const array = [...arr];
    const length = array.length;
    if (length === 1) {
        return array;
    }
    const num = Math.floor(length / 2);
    const arrayLeft = array.slice(0, num);
    const arrayRight = array.slice(num, length)
    return mergeSortItem(mergeSort(arrayLeft), mergeSort(arrayRight));
}

快速排序

就是在一组数据中随机找个数作为中间点,小于中间点的放在左边,大于中间点的放在右边。

function quickSort(arr: Array):Array {
    if(arr.length < 2){
        return arr;
    }
    const index = Math.floor(arr.length/2);
    const element = arr[index];
    arr.splice(index, 1);
    const left:any[] = [];
    const right:any[] = [];
    arr.map((item) => {
        if(item < element){
            left.push(item);
        }else{
            right.push(item);
        }
    });
    return [...quickSort(left), element, ...quickSort(right)];
}  
桶排序、计数排序、基数排序。因为这些排序算法的时间复杂度是线性的,所以我们把这类排序算法叫作线性排序。之所以能做到线性的时间复杂度,主要原因是,这三个算法是非基于比较的排序算法,都不涉及元素之间的比较操作。

桶排序

核心思想就是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行非序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

计数排序

计数排序其实就是桶排序的一种特殊情况。当要排序的n个数据,所处的范围并不大的时候,比如最大值是K,我们就可以把数据划分成k个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

计数排序只能用在数据范围不大的场景中,如果数据范围K比要排序的数据n大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果在排序的数据是其他类型的,要羌其在不改变相对大小的情况下,转化为非负整数。