js实现快速排序的两种方法

471 阅读2分钟

方法一:单边循环法

第一步:选定基准元素pivot(为了方便我就选第一个作为基准元素);

第二步:设置一个mark指针,代表小于基准元素的区域边界;

第三步:从基准元素的下一个位置开始遍历数组;

🌟第四步:如果遍历到大于pivot的元素,就继续往后遍历,如果遍历到小于pivot的元素?

1⃣️步:mark1,代表小于pivot的元素增加1;

第2⃣️步:把遍历到的那个元素换到mark这个位置来;

第五步:mark最后所在的位置就是边界位置,把pivot和mark交换过来,返回这个位置就行;

第六步:利用分治策略,对mark两边递归排序;

/**
 * 单边循环法的快速排序算法
 * @param {需要排序的数组} arr
 * @param {开始位置} startIndex
 * @param {结束位置} endIndex
 * @returns void 原地排序 修改原数组
 */
function quickSort(arr, startIndex, endIndex) {
  if (startIndex >= endIndex) {
    // 递归结束条件
    return;
  }
  // 得到基准元素位置
  let pivotIndex = partition(arr, startIndex, endIndex);
  // 根据基准元素对左右两边进行递归排序
  quickSort(arr, startIndex, pivotIndex - 1);
  quickSort(arr, pivotIndex + 1, endIndex);
}

function partition(arr, startIndex, endIndex) {
  // 取第一个作为基准元素
  let pivot = arr[startIndex]
  // mark左边是小于基准元素的值,mark右边是大于基准元素的值
  let mark = startIndex;
  // 从基准元素下一个位置开始遍历
  for (let i = startIndex + 1; i <= endIndex; i++) {
    // 遍历到小于基准元素,mark加一,然后mark和这个小的元素交换位置
    if (arr[i] < pivot) {
      mark++;
      //这里使用的是数组的解构赋值
      [arr[i], arr[mark]] = [arr[mark], arr[i]]
    }
    // 遍历结束后,arr[startIndex + 1] ~ arr[mark]的值是小于pivot的,arr[mark+1]~arr[endIndex]是大于pivot的
    // 所以pivot的位置就确定了,下一步直接换位置就行
  }
  //数组结构赋值
  [arr[startIndex], arr[mark]] = [arr[mark], arr[startIndex]]
  return mark;
}

export default quickSort;

方法二:双边循环法

第一步:选取基准元素pivot(还是为了方便选第一个元素就行);

第二步:定义left和right两个指针,分别指向两端;

🌟第三步:从right开始向左移动,拿每一个元素与pivot比较,如果〈=pivot(表示这个值小于pivot,应该在左边),指针停止。开始移动left,left开始向右移动,如果〉=pivot(表示这个值应该在右边),然后交换这两个值。然后一直循环下去,直到left=right(两个指针在中间某个位置相撞);

第四步:交换pivot和重合位置的值,此时左边全是小于pivot的,右边全是大于pivot的;

第五步:接下来递归排序pivot的左右两边;

/**
 *
 * @param {*} arr
 * @param {*} startIndex
 * @param {*} endIndex
 * @returns void 原地排序 直接修改原数组
 */
function quickSort(arr, startIndex, endIndex) {
  // 递归结束条件
  if (startIndex >= endIndex) {
    return;
  }
  // 得到基准元素的位置
  let pivotIndex = partition(arr, startIndex, endIndex);
  quickSort(arr, startIndex, pivotIndex - 1);
  quickSort(arr, pivotIndex + 1, endIndex);
}
/**
 *
 * @param {待排序数组} arr
 * @param {开始位置} startIndex
 * @param {结束位置} endIndex
 * @returns 基准元素的下标
 */
function partition(arr, startIndex, endIndex) {

  // 取第一个元素作为基准元素
  let pivot = arr[startIndex];
  let left = startIndex;
  let right = endIndex;

  // 左指针=右指针时代表已经全都比较完,此时的位置就是pivot应该在的位置
  while (left !== right) {
    // 右指针每次向左移动一位,直到遇到小于基准元素的值才停下来
    while (left < right && arr[right] > pivot) {
      right--;
    }
    // 右指针停下来后,左指针一样的每次向右移动一位,遇到大于基准元素的值停下来
    while (left < right && arr[left] < pivot) {
      left++;
    }
    // 如果是因为left=right才停下来,表示已经比较完,此时不需要交换位置,直接退出即可
    if (left < right) {
      // 利用数组的解构赋值,交换左右指针的值
      [arr[left], arr[right]] = [arr[right], arr[left]];
    }
  }
  // left和right重合了,此时将pivot和重合点交换即可。
  [pivot, arr[left]] = [arr[left], pivot];
  // 此时重合点就是pivot的位置,我用的left,使用right是一样的
  return left;
}

export default quickSort;

其实还有第三种方法,不使用递归,使用栈的方式,但我不会。。。