快速排序的几种实现方式

原理

快速排序算法是基于分治思想的,在递归过程中,每次从待排序区间选取一个元素(这个值的位置随意,可以是两端,也可以是中间,也有为了处理基准值而专门写一个方法的)作为基准记录,遍历整个区间,将所有比基准记录小的元素移动到基准记录的左边,而所有比基准记录大的元素移动到基准记录的右边。之后分别对基准记录的左边和右边两个区间进行快速排序,直至将整个区间排序完成。

以下讨论不同的几种实现方式,都以从小到大排序为准。

基准值最左/最右+双指针

覆蓋值(标准算法)

这种方法将基准值先保存下来,用两个指针分别从左边和右边遍历待排序区间,注意,此处根据基准值在左边或右边的情况不同:
如果选取最左边的值为基准值,则需要用比它小的值来放在基准值原来的位置,这个值一定是从右侧开始遍历取到的,因为左边指针遇到比基准值大的停下来,右边指针遇到比基准值小的停下来,所以首先走右边指针,将它指向的值覆蓋基准值的位置,然后走左边,遇到比基准值大的数值,覆蓋右边指针指的位置,这样依此覆蓋和往中间遍历,直到左右指针相遇,这时把基准值放到这个下标位置,完成一轮排序,左边的都比它小,右边的都比它大,具体实现如下:

function quikSort1(arr, left, right) {
    if (left > right) {return;}
    var pivot = arr[left];
    var l = left, r = right;

    while (l < r) {
        while (r > l && arr[r] >= pivot) { r--; }
        arr[l] = arr[r];

       while (l < r && arr[l] <= pivot) { l++; }
        arr[r] = arr[l];
    }
    arr[r] = pivot;
    if (left < r) { quikSort1(arr, left, r-1); }
    if (right > l) { quikSort1(arr, l+1, right); }
}

同理,如果是取右边值为基准值,则先走左边指针:

function quikSort1(arr, left, right) {
    if (left > right) {return;}
    var pivot = arr[right];
    var l = left, r = right;

    while (l < r) {
        while (l < r && arr[l] <= pivot) { l++; }
        arr[r] = arr[l];

        while (r > l && arr[r] >= pivot) { r--; }
        arr[l] = arr[r];
    }
    arr[r] = pivot;
    if (left < r) { quikSort1(arr, left, r-1); }
    if (right > l) { quikSort1(arr, l+1, right); }
}

这里有一个细节,在arr[r] = arr[j]之类的覆蓋结束后,可以把被覆蓋的位置加一或减一即r--,因为这个位置被覆蓋了,可以直接跳过从下一位开始比较。

交换值(两头交换法)

在这种实现方式中,可以使用不断覆蓋值,最后把基准值放进来的方法,也有先保存l和r的下标位置,然后直接交换这两个位置数值的方法,在这种情况下,基准值不会被覆蓋,也不需要在一轮比较结束后插入,但指针下标在交换后需要各自增长一位。实现如下:

function quickSort2(arr, left, right) {
    var pivot = arr[left], i = left, j = right; // 以最左边的数为基准,i是左zhizhen,往右走,j是右指针,往左走
    do{
        while (i <= j && arr[i] < pivot) {
            i++;
        }// 从左边寻找比基准数大的数
        while (i <= j && arr[j] > pivot) {
            j--;
        }// 从右边寻找比基准数小的数
        if (i <= j) {
            // 找完了,交换
            var temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
            i ++;
            j --;
            // 指针继续挪动,然后比较交换,直到指针相遇
        }
    }while(i <= j);
    // 此时已经分成左右两部分,左边比基准小,右边比基准大
    if (left < j) {
        //对左边部分排序
        quickSort2(arr, left, j);
    }
    if (i < right) {
        //对右边部分排序
        quickSort2(arr, i, right);
    }
}

基准值最左/最右+单指针遍历(两头交换法的另一种实现)

这种实现方式的思路是,以最左或最右为基准值,假设将待排序区间分为大于小于基准值的两个区间S1和S2,遍历整个区间把数值划分到这两个区域中。
首先设定一个下标m,指向S1的起始点,也就是基准值的位置,该下标表示S1区间的结束位置(一开始就是0,然后随着区间变大而增加),然后使用一个下标指针k从m的位置开始依次遍历,遇到比基准值小的,就把m增加一位,然后交换k与m的值,这样逐渐把比基准值小的值都挪到靠近基准值的位置,比基准值大的则不处理,一趟结束后,m左边的都比基准值小,右边都比基准值大,这时交换基准值和m下标上的值,让基准值处于S1和S2的分界处,然后递归排序左边到到m,和m到右边两个区域。具体实现:

function quickSort4(arr, left, right) {
  var pivot = arr[left]; // p 为基准值
  var m = left; // 用S1 and S2 表示基准值两边两个空的区域,m表示排好序区域最右侧的位置下标
  for (var k = left+1; k <= right; k++) { // 从第二位开始遍历后面的值
    if (arr[k] < pivot) { // 比基准值小
      m++; // S1区域增加一个元素
      // 交换该值和存储指数值
      var temp = arr[k];
      arr[k] = arr[m];
      arr[m] = temp;
    } // 对大于基准值的不做处理
  }
  // 交换m位置的值和基准值(m此时在S1和S2交界处)
  var temp = arr[left];
  arr[left] = arr[m];
  arr[m] = temp;
  if(left< right) {
    quickSort4(arr, left, m-1);
    quickSort4(arr, m+1, right); 
  }
}

也可以拆分成两个函数:

function partition(arr, left, right) {
  var pivot = arr[left]; // 基准值
  var m = left;
  for (var k = left+1; k <= right; k++) {
    if (arr[k] < pivot) { // 比基准值小
      m++; // 存储指数+1
      var temp = arr[k];
      arr[k] = arr[m];
      arr[m] = temp;
    } // notice that we do nothing in case 1: arr[k] >= p
  }
  var temp = arr[left];
  arr[left] = arr[m];
  arr[m] = temp;
  return m; // 返回存储指数位置,用于快速排序
}
function quickSort3(arr, left, right) {
  if (left < right) {
    var pivotIdx = partition(arr, left, right); // O(N)
    // a[left..right] ~> a[left..pivotIdx–1], pivot, a[pivotIdx+1..right]
    quickSort3(arr, left, pivotIdx-1); // recursively sort left subarray
    // a[pivotIdx] = pivot is already sorted after partition
    quickSort3(arr, pivotIdx+1, right); // then sort right subarray
  }
}

该实现方式来自VISUALGO

简单粗暴的使用数组

来自快速排序(Quicksort)的Javascript实现
这种方式是选取一个基准值,然后新建两个数组,把大于和小于基准值的分别放在两个数组中,递归排序两个数组,然后合并。

var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var 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));
};
点赞