关于前端中常用的排序算法-图文讲解

本文详细介绍了几种常见的排序算法,包括直接插入排序、简单选择排序、快速排序及归并排序的实现原理与步骤。通过具体示例代码展示了每种算法的工作流程。

如果出现错误,请在评论中指出,我也好自己纠正自己的错误

author: thomaszhou

排序

先定义一些变量和方法

    function ArrayList() {
	  let arr = [];  
	  this.insert = function(item) { // 插入
	    arr.push(item);
		};

	  this.toString = function() { // 字符串形式显示数值
        return arr.join();
	  };
复制代码

很多排序算法用到两个值的交换功能,所以我抽象出一个函数:

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

复制代码

直接插入排序

这个方法是排序中最简答的方法

  • 思想:依次将待排序序列中的每一个值插入到一个已经排好序的序列中,直到全部记录都排好序
  • 需要解决的问题:
    • 如何构造初始的有序队列?
    • 如何查找待插入记录的插入位置?
  • 思路:如图中第四趟排序结果,从第三趟的结果开始,我们要插入值6,然后我们只跟当前要插入的值(值为6)的前一个值比较(如果当前是arr[i],那前一个就是arr[i-1])来比较,( 因为看到的用[ ]包裹的是排好序的序列,最右边是其中最大的值 ),我们将当前要排序的值存到变量current当中,如果current值比arr[i-1]小,那就继续向前移动,继续比较current和arr[i-2],依次类推,并且每次移动,都将值向后移(20移到之前6的位置,15移到之前20的位置,代码是arr[index + 1] = arr[index])。当找到current > arr[i-n]的时候,将current赋值给arr[i-n+1].
// 设置arr = [12, 15, 9, 20, 6, 31, 24]
    this.InsertSort = function() {
      let len = arr.length,
		  current,
		  index;
      for (let i = 1; i < len; i++) {
        current = arr[i]; // current存储的是当前要插入的值
        for ( index = i - 1; arr[index] > current; index--) {
        // index是有序序列的最后一个值,即current的前一个值
          arr[index + 1] = arr[index];
		}
		arr[index + 1] = current;
	  }
    };
复制代码
let arraylist = new ArrayList();
arraylist.InsertSort();
console.log(`InsertSort数组是:${arraylist.toString()}`); // InsertSort数组是:6,9,12,15,20,24,31
复制代码

简单选择排序

主要思想:每趟排序在当前待排序序列中选出最小值,和下标为i的值交换(i 初始为0,每躺排序结束后 i++)

从算法逻辑上看, 选择排序是一种简单且直观的排序算法. 它也是两层循环. 内层循环每执行一遍, 将选出本次待排序的元素中最小(或最大)的一个, 存放在数组位置i. 外层循环就是设置循环结束条件(i < n)。

选择排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序. 比如数组[2,2,1,3], 正向排序时, 第一个数字2将与数字1交换, 那么两个数字2之间的顺序将和原来的顺序不一致, 虽然它们的值相同, 但它们相对的顺序却发生了变化. 我们将这种现象称作 不稳定性 .

this.SlectSort = function() {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
	let index = i; 
    for (let j = i; j < len; j++) {
	  if (arr[j] < arr[index]) {  // 找最小值/或者最大值(< 是升序  > 是降序)
		index = j; 
	  }
	}
    //	exchange
    index !== i && this.swap(i,index,arr);
  }
};
复制代码

快速排序

快排被人们认为是最快的,所以呢,面试题中也会经常考。它将数组拆分为两个子数组(也就是一次划分操作), 其中一个子数组的所有元素都比另一个子数组的元素小, 然后对这两个子数组再重复进行上述操作(递归下去,不断划分), 直到数组不可拆分, 排序完成.

如图是快排的一次h划分的过程;

伪代码表示一次划分:
  • 将i和j分别指向待划分区间的最左侧和最右侧记录的位置
  • 重复下述过程,直到i = j
    • 右侧扫描,直到下标 j 的值小于下标 i 的值
    • 将r[j]与r[i]交换,并执行i++
    • 左侧扫描,直到下标 i 的值大于下标j 的值
    • 将r[i]与r[j]交换,并执行j--
  • 退出循环,说明i和j指向了轴值记录的所在位置,返回该位置
//调用函数:arraylist.Partition(1, 7);但是数组下标是0-6

    this.Partition = function(first, end) { // 如果提取出来会传递一个arr数组,本例是直接调用全局的arr数组
	    let i = first - 1, // arr下标是(first-1, end-1)
		    j = end - 1;
	    while (i < j) {
	      while (i < j && arr[i] <= arr[j]) { j--; }
		  if (i < j) {
		    this.swap(i, j, arr); // 交换
    	    i++;
		  }
		  while(i < j && arr[i] <= arr[j]) { i++; }
          if (i < j) {
            this.swap(i, j, arr);
            j--;
          }
		}
		return i+1;
	};
复制代码

如图,一次划分后,我们可以得到[19, 13, 6]和[31, 49 ,28],然后再对这两个子区间进行划分,依次类推,此处需要递归

this.QuickSort = function(first, end) {// 如果提取出来会传递一个arr数组,本例是直接调用全局的arr数组
	if (first < end) {
	  let pivot = this.Partition(first, end);
	  this.QuickSort(first,pivot - 1); // 前半部分
	  this.QuickSort(pivot + 1, end); // 后半部分
	}
};
复制代码

验证

console.log(arraylist.Partition(1, 7));
console.log(`第一个划分的结果${arraylist.toString()}`); // 第一个划分的结果19,13,6,23,31,49,28
arraylist.QuickSort(1, 7);
console.log(`最终结果是:${arraylist.toString()}`); // 最终结果是:6,13,19,23,28,31,49
复制代码

归并排序

首先将具有n个待排序的记录序列变成n个长度为1的有序序列,然后再两两合并,边合并边排序。

归并排序严格按照从左往右(或从右往左)的顺序去合并子数组, 它并不会改变相同元素之间的相对顺序, 因此它也是一种稳定的排序算法.

  • 合并过程(建议结合图片和合并的代码理解!):我们看倒数第二行的右侧,[1,7]和[4,5]进行合并,创建一个数组result,先比较两个数组的首位置数值,1比4小,将1加入result,然后 7大于4,将4加入result,7大于5,将5加入result,然后原本[4,5]数组长度为空,跳出循环
拆分过程:
function mergeSort(arr) {  //输入一个长度为n的数组,输出长度为1的n个数组
    var length = arr.length;
    if(length < 2) {
      return arr;
    }
    var m = (length >> 1),
        left = arr.slice(0, m),
        right = arr.slice(m); //拆分为两个子数组
    return merge(mergeSort(left), mergeSort(right));//子数组继续递归拆分,然后再合并
};
复制代码

注: x>>1 是位运算中的右移运算, 等同于x除以2再取整, 即 x>>1 == Math.floor(x/2) 合并过程:

function merge(left, right){ // 合并数组

    let result = [];
    while (left.length && right.length) {
    // 注意:判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
      let item = left[0] <= right[0] ? left.shift() : right.shift();
    }
    //      只要left.length和right.length其中一个不满足,就会跳出while循环
    // 假设left为空,跳出循环,那么right还剩一个值,反之同理
    return result.concat(left.length ? left : right); // 将最终的return返回
};
复制代码

由上, 长度为n的数组, 最终会调用mergeSort函数2n-1次.

这个算法会出现错误,栈溢出

Uncaught RangeError: Maximum call stack size exceeded
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值