JavaScript 排序算法回顾

回顾选择排序,插入排序,冒泡排序,快速排序,以及如何计算时间复杂度

1.选择排序

思路:从未排序的序列中选出最小(大)的元素,放进已排好序的序列末尾。
时间复杂度:O(n^2)
算法稳定性:不稳定

// 定义一个函数用于交换
function swap (array, i, j) {
  let temp = array[i];
  array[i] = array[j];
  array[j] = temp;
}

function selectionSort (arr) {
  let minIndex;
  for (let i = 0; i < arr.length; i++) {
    minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {  // 对未排序的序列进行循环,找出最小元素。
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    swap(arr, i, minIndex);                     // 最小元素与放如排好序的序列末尾。
  }
  return arr;
}

let arr = [1,2,8,4,3,6,10];

selectionSort(arr)   // 1,2,3,4,6,8,10

选择排序所需要的元素比较次数为 (n-1) + (n-2) + ... + 1 = n*(n-1)/2 ,元素赋值次数界于 0 ~ 3(n-1) 之间,也就是原序列已排好序于原序列为反序两种极端情况。

2.插入排序

思路:从第二个元素往后遍历,从前面的序列中找到一个合适的位置进行插入。
时间复杂度:O(n^2)
算法稳定性:稳定

let arr = [5,3,2,6,7,10,1];  // 进行小到达排序

function InsertionSort(arr) {
  let len = arr.length;
  for (let i = 1; i < len; i++) {
    let curr = arr[i];                    // 要执行插入操作的元素
    let j = i;                            // 从i开始往回遍历
    while (j > 0 && arr[j-1] > curr) {   
// 不断跟curr元素进行比较,大于curr的往后退一位,最终给curr腾出一个插入的位置
      arr[j] = arr[j-1];
      j--;
    }
    arr[j] = curr                         // curr插入到合适的位置中
  }
  return arr;
}

console.log(InsertionSort(arr)); // 1,2,3,5,6,7,10

容易看出,当序列已排好序的时候,元素比较的次数最少,比较次数为 n - 1 次,每一个元素只需要和前一个元素比较即可,当序列是按反序排列,那么比较次数最多,比较次数为 n*(n-1)/2 。
元素赋值次数为等于比较次数加上 n - 1。

3.冒泡排序

思路:多次遍历序列,比较相邻元素,将最大(最小)元素像泡泡一样冒到后面已排好序的序列中。
时间复杂度:O(n^2)
算法稳定性:稳定

function advanceBubbleSort1(arr){
    let len = arr.length;
    let flag;          // 设置一个标记,如果某一轮没有交换,表示已经排好序了。不必再循环遍历。
    for(let i = 1, i <= len - 1; i++){
        flag = false;
        for(let j = 0; j < len - i; j++){
            if(arr[j] > arr[j + 1]){
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = true;
            }
        }
        if(flag === false){
            break;
        }
    }
    return arr;
}

4.快速排序

快速排序是一个非常流行并且高效的排序算法。
它之所以高效是因为它在原位上进行排序,不需要辅助的存储空间。
思路:以最左元素作为主元进行划分,最后再将主元放回正确位置,递归。
平均时间复杂度 Θ(nlogn), 最坏的情况 θ(n^2) 
算法稳定性:不稳定

在了解快速排序之前需要了解一个关键算法:划分算法

function partition(arr, left ,right) {   // 分区操作
  var pivot = left,                      // 设定基准值(pivot),即以最左元素为主元
      index = pivot + 1;
  for (var i = left + 1; 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;
}

我们可以看到,整个划分都在原数组上进行,不需要引进额外的辅助数组。
快速排序算法需要以划分算法为核心:

function quickSort(arr, left, right) {
  var len = arr.length,
      partitionIndex;

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

let arr = [1,6,3,8,5,0,7]

console.log(quickSort(arr, 0, 6))     // 0,1,3,5,6,7,8

5.如何估算时间复杂度

了解几个概念:

  • O 符号表示一个运行时间的上界。
  • Ω 符号表示一个运行时间的下界。
  • θ 符号表示一个精准描述。

可以这样帮助理解,O 类似于 <= ,Ω 类似于 >=, θ 类似于 = ,但只能说是类似于。

5.1 计算迭代次数

let i = 0;
for (let i = 0; i < n; i++) {
  i ++;
}

可以看到迭代次数为n,所以时间复杂度为 θ(n)

5.2 计算基本运算的频度

什么是基本运算呢?

  • 在分析搜索和排序算法时,如果比较是元运算(不能再细化的运算),可以选择它为基本运算
  • 矩阵乘法算法中,可以选择数量乘法运算
  • 遍历链表时,可以选择设置或更新指针的运算
  • 再图的遍历中可以选择访问结点的动作和被访问结点的计算

如上一份代码

let i = 0;
for (let i = 0; i < n; i++) {
  i ++;
}

选择自加运算,同理得时间复杂度 θ(n) 本文由学什么技术好网亲情奉献

猜你喜欢

转载自blog.csdn.net/aide315/article/details/79612032