【算法JS】详解用快速选择算法找出无序数组的中位数

97 阅读3分钟

使用快速选择算法找出无序数组里的中位数
该方法不需要暴力整体排序,而是找出某个数按大小排列时的下标 先介绍大致步骤,以arr=[4,2,6,9,11]为例
arr.length为5,那我们要找到按大小排列时下标为

Math.floor(5/2)=2

的那个数即为中位数

Let's begin:
s1:随机选择一个数,比如4,把它交换到末尾,此时

arr=[11,2,9,6,4] 

s2:找出4在数组中按大小排列时的下标i

//过程是遍历数组,最终下标<i的都<4,假设遍历过程的下标为j,
j=0,i=0,arr[j]=11,而11>4,数组不变

j++

j=1,i=0,arr[j]=22<4,此时把arr[j]和arr[i]交换,i++
变更后 arr=[2,11,9,6,4],i=1

j++

j=2,i=1,arr[j]=6,6>4,数组不变,i不变,
arr=[2,11,9,6,4],i=1

j++

j=3,i=1,arr[j]=9,9>4,数组不变,i不变
arr=[2,11,9,6,4],i=1

j++

j=4,此时遍历到最后一个,而一开始我们就把4放到了最后一个,所以不用比较,结束遍历

最终arr[i]和4交换,
变更后 arr=[2,4,9,6,11],i=1
说明4在下标为1的位置上,而我们要找的是下标为3的那个数

s3:此时4之后的数都>4,而下标2>1,说明要继续在9,6,11里随机选一个数,执行于s2类似的步骤,将<这个数的排到它之前,将>这个数的排到它之后,找出它按顺序排列的下标i,并判断i===2?

//这次我们选择6,6要换到最后面
此时,arr=[2,4,9,11,6],i为4的顺序下标1加一,即i=2,遍历范围为[9,11,6],故j从2开始

j=2,arr[j]=9,9>6,i不变,i=2

j++

j=3,arr[j]=11,11>6,i不变,i=2

j++

j=4,此时遍历到最后一个,而一开始我们就把6放到了最后一个,所以不用比较,结束遍历

最终arr[i]与6交换
变更后arr=[2,4,6,9,11],i=2,找的就是它!
中位数为6

上代码,把上面三个步骤搞懂后,代码就很好懂了

function findMedian(arr) {
  let length = arr.length;
  let k = Math.floor(length / 2);

  if (length % 2 === 0) {
    // 如果数组长度是偶数,返回中间两个元素的平均值
    let x = quickSelect(arr.slice(), 0, length - 1, k - 1);
    let y = quickSelect(arr.slice(), 0, length - 1, k);
    return (x + y) / 2;
  } else {
    // 如果数组长度是奇数,返回中间元素
    return quickSelect(arr.slice(), 0, length - 1, k);
  }
}

function quickSelect(arr, low, high, k) {
  if (low <= high) {
    //随机选一个下标
    let pivotIndex = Math.floor(Math.random() * (high - low + 1) + low);
    //拿到这个下标的值
    let pivot = arr[pivotIndex];
    //把它放到数组最后面
    swap(arr, pivotIndex, high);
    let i = low; //i最终为pivot的顺序排列时的下标
    for (let j = low; j < high; j++) {
      if (arr[j] < pivot) {
        swap(arr, i, j);
        i++;
      }
    }
    //把pivot从最后面换到它顺序排列时该呆的位置
    //此时pivot前面的数都<它,后面的数都>它
    swap(arr, i, high);
    //判断pivot是不是我们要找的数
    if (i === k) {
      return pivot;
    } else if (i < k) {
      //说明要从比pivot大的数里面找
      return quickSelect(arr, i + 1, high, k);
    } else {
      //说明要从比pivot小的数里面找
      return quickSelect(arr, low, i - 1, k);
    }
  }
}

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

let myArray = [4, 2, 7, 1, 9, 5, 99, 6];
//顺序排列为[1,2,4,5,6,7,9,99],中位数为5.5
let median = findMedian(myArray);
console.log("中位数是:" + median); //5.5