javascript-排序算法(冒泡,选择,插入,归并)

472 阅读5分钟

javascript 排序算法实现

冒泡排序

  • 冒泡排序的实现思路就是将第i个元素和i+1上对应的元素进行比较,如果第一个arr[i] 大于arr[i+1]就交换他们的位置
  • 冒泡排序会使用双层循环,第一层循环用于界定比较的范围,每比较完一轮,最后一个元素一定是arr中的最大值,所以在下一轮比较中就可以不考虑第n位,比较的范围就变成了0到n-1,第三次的范围就变成了0到n-2,以此类推经过n次比较第0位的数就只arr中的最小值,第二层循环用于比较排序
  • 关于算法时间复杂度,冒泡排序的算法时间复杂度是O(n^2) ~ 实现:
// 交换方法
function swap(arr, i, j) {
  let temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

function bubbleSort(arr){
  if(arr === null || arr.length < 2) return;
  for(let end = arr.length - 1; end > 0; end--){
    for(let i = 0; i < end; i++){
      if(arr[i] > arr[i+1]) {
        swap(arr, i, i+1)
      }
    }
  }
}

选择排序

  • 选择排序的实现思路就是每次都去找到一个最小值放在当前位置上,例如,在0n-1范围内找到一个最小值放在位置0上, 接下来在1n-1范围内找到一个最小值放在1位置上,在 2n-1范围内找到最小值放在2位置上,至此02位置上的值已经排好序了
  • 选择排序也需要使用双层循环,第一层同样是控制排序的范围,因为我们已排好序的部分就不用考虑了,第二层巡皇就负责找到最小值的索引下标,实现选择排序的过程中我们需要定义一个标记最下值位置的变量。
  • 关于选择排序的算法时间复杂度是O(n^2)

! 选择排序和冒泡排序算法时间复杂度都是O(n^2), 但是选择排序是一种不稳定的排序算法,比如给定一个序列6 9 6 0 8, 在第一趟比较时我们会找到0与第一个6交换,在接下来的比较过程中,第一个6和第二个6的原序就被打乱了, 选择排序的最好和最坏算法时间复杂度都是O(n^2)

实现~

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

function selectSort(arr) {
  if(arr === null || arr.length < 2) return;
  for(let i = 0; i < arr.length - 1; i++){
    let minIndex = i;
    for(let j = 0; j < arr.length; j++) {
    	minIndex = arr[j] < arr[minIndex] ? j : minIndex;
    }
    swap(arr, i, minIndex)
  }
}

插入排序

  • 将 i + 1位上的数与i位上的数进行比较,如果arr[i+1] 小于i位上的数就交换
  • 同理插入排序也是双层循环的,外层循环控制排序范围,内层循环负责比较交换
  • 插入排序的算法时间复杂度平均复杂度是O(n^2),最坏时间复杂度也就是需要比较 1 + 2 + 3 + ... + (N - 1)次等差数列求和N^2 / 2,此时的时间复杂度是O(n^2),最好时间复杂度就。是给定的数组是有序的,排每插入一个元素,只需要考查前一个元素,此时的时间复杂度是O(n)

! 插入排序与选择排序和冒泡排序算法复杂度虽然都是O(n^2),但是插入排序与数据状况是有关系的,数据有序的情况下,算法时间复杂度就低。但是冒泡排序与数据状况是没有关系的,

~实现

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

function insertSort(arr){
  if(arr === null || arr.length < 2) return;
  for(let i = 1; i < arr.length; i++){
    for(let j = i - 1; j > 0 && arr[j] > arr[j+1]; j--) {
      swap(arr, j, j+1);
    }
  }
}

归并排序

归并排序算法实现的思路是:

  • 找到给定目标数组arr的中间位置索引,将数据样本分割为两个部分进行排序,记中间位置索引为mid
  • 将分割好的数组分别进行排序,确保左右两边的数组都是有序的,这样就可以使用外排的方式为数组排序
  • 模拟C语言中的'指针'对左右两边的数组进行外排,
  • 准备一个辅助数组,用于存放左右两侧数组外排的结果
  • 判断左右两个数组外派过程中的边界条件,某一个数组先完成一轮比较,就将另外一个数组的数据拷贝到辅助数组中即可
  • 最后将辅助数组中的数据拷贝到目标数组arr中,归并排序将就完成了

如果你觉得难以理解,可以举个例子:

// 目标数组
let arr = [5,3,7,4,9,1] 

// 找到中间索引位置
let mid = (0 + 5) / 2 = 2;

// 所以将数组分割为两个部分[5,3,7]和[4,9,1],然后将这两个数组排序
let arrLeft = [3,5,7]
let arrRight = [1,4,9]

// 准备一个辅助数组, 数组长度为arr.length
let helpArr = new Array(6);

// 令p1指向左侧数组的起始位置,p2指向右侧数组的起始位置,然后开始比较p1和p2索引所对应元素的值

/**
一下是归并排序的过程:
* 刚开始p1指向3,p2指向1, 此时arr[p1] > arr[p2] 将arr[p2]存到辅助数组中[1];
* 然后p1指向不变, p2++, p2指向4, 此时arr[p1] < arr[p2] 将arr[p1]存到辅助数组中,此时辅助数组为[1,3];
* 接下来p2指向不变, p1++, p1指向5, p2指向4, arr[p1] > arr[p2] ,将arr[p2]存到辅助数组中[1, 3, 4];
* 接下来p1指向不变,p2++, p1指向5, p2指向9, arr[p1] < arr[p2], 将arr[p1]存到辅助数组中[1,3,4,5];
*  接下来p1不变, p2++, 此时p2越界了,直接将未越界的左侧数组拷贝到helpArr中[1,3,4,5,7]
*/

~现在先用代码完整的实现一下归并排序,然后再进行归并排序的复杂度分析

function mergeSort(arr) {
  if(arr === nnull || arr.length < 2) return;

  mergeProcess(arr, 0, arr.length-1);
}

/**
* @param {*number} L 排序起始位置
* @param {*number} R 排序终止位置
*/
function mergeProcess(arr, L, R) {
  // 停止递归的条件是 L = R
  if(L === R) return;

  // 中间位置索引
  let mid = (L + R) / 2;

  // 左侧数组排序
  mergeProcess(arr, L, mid);
  // 右侧数组排序
  mergeProcess(arr, mid + 1, R);

  // 排序过程的具体实现
  merge(arr, L, mid, R);
}

function merge(arr, L, mid, R) {
  // 创建一个长度为R-L + 1的辅助数组
  let helpArr = new Array(R - L + 1);
  let i = 0;
  // 左侧数组起始位置
  let p1 = L;
  // 右侧数组起始位置
  let p2 = mid + 1;

  while(p1 <= mid && p2 <= R) {
  	helpArr[i++] = arr[p1++] < arr[p2++] ? arr[p1++] : arr[p2++];
  }

  // 将没有越界的数组中剩余的数据拷贝到辅助数组中
  while(p1 <= mid) {
  	// 右侧越界
  	helpArr[i++] = arr[p1++];
  }

  while(p2 <= R) {
  	// 左侧越界
  	helpArr[i++] = arr[p2++]
  }
  
  // 将辅助数组中的数据拷贝到arr中
  for(let j = 0; j < helpArr.length; j++) {
  	arr[L+j] = helpArr[j] 
  }
}
归并排序算法复杂度分析
  1. 归并排序将总的数据样本量N划分我两部分,左侧和右侧各N/2, 加上最后将辅助数组拷贝到原数组的复杂度O(N), 总的算法复杂度为 T(N) = 2T(N/2) + O(N);
  2. 归并排序算法使用了递归短发,所以尝试使用递归算法的算法复杂度分析方法来进行分析,由于递归时两个子过程的划分是一致的,所以可以用master公式来分析 T(N) = aT(b*(N/2)) + O(N^d);递归算法中a=2, b = 1, d = 1, log(b, a) = d 所以归并排序的复杂度为 T(N) = N*logN;

! 归并排序的时间复杂度O(N*lonN)要由于复杂度为O(N^2)的算法