Merge Sort -- 复杂问题拆分成简单问题的典范

23 阅读2分钟

将一个乱序数组重新排序,直接思考是困难的,可以转换这个问题,变成对两个已经排好序的数组进行合并的问题,这样,用上双指针用上就解决了。

这么多个排序算法,多年过去,仍然觉得merge sort是个神奇算法,把复杂问题拆分成大家都懂的简单问题,然后组装简单问题的答案,就能解决复杂问题。

image.png 参考# Learn Merge Sort in 13 minutes

简单问题:

  1. 单个元素排序:不用排序
  2. 两个元素排序:对比大小 然后 swap

复杂问题:

  1. N个元素排序:拆分,变成2个元素的数组,或者1个元素的数组,排序;排序好多个小数组后,再利用双指针方法合并。
const arr = [3,7,8,4,1,5,2,6];

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

mergeSort(arr, 0, arr.length - 1);

function mergeSort(arr, start, end) { //
  console.log(`mergeSort ${start}, ${end}`)
  if (end === start) {
    // One element is just sorted
  } else if (end - start === 1) {
    // Sorting two element is easy.
    if (arr[start] > arr[end]) {
      swap(arr, start, end);
    }
  } else {
    const mid = start + Math.floor((end - start) / 2);
    mergeSort(arr, start, mid);
    mergeSort(arr, mid + 1, end);
    merge(arr, start, mid, end);
  }
}

function merge(arr, start, mid, end) {
  const arr2 = [];
  // two pointer merge.
  let p1 = start, p2 = mid + 1;
  for(;;) {
    if (p1 === mid + 1) {
      break;
    }

    if (p2 === end + 1) {
      break;
    }

    if (arr[p1] < arr[p2]) {
      arr2.push(arr[p1]);
      p1++;
    } else {
      arr2.push(arr[p2]);
      p2++;
    }
  }

  if (p1 === mid + 1) {
    while(p2 !== end + 1) {
      arr2.push(arr[p2]);
      p2++;
    }
  }

  if (p2 === end + 1) {
    while(p1 !== mid + 1) {
      arr2.push(arr[p1]);
      p1++;
    }
  }
  for (let i=start; i<=end; i++) {
    arr[i] = arr2[i-start];
  }
  console.log('after merge', arr)
}

对比下别人写的代码 www.geeksforgeeks.org/merge-sort/ 发现可以更精简,拆分成2个元素的情况,也可以利用merge去排序,而不是对比+swap。不过这个思考就更加深入了,一般人一开始可能并想不出来。属于polish过的代码。

另外也发现大家的思考习惯不一样。 对于递归,我一般喜欢把有直接答案的条件,放在最前面,然后return。而geeksforgeeks,反过来,把需要继续递归的放在if里面。 对于遍历,我喜欢使用

{
// init variables...
for(;;) {
  // break conditions...
  // contents
  // change variables for next loop
}

这样有些复杂的情况,也可以统一按照这个套路写的。 用while或者do while,感觉就是不直接。

另外其实,反过来想,其实也可以不用递归的,先将数组话分成多个2元数组,然后一步一步合并,,就可以了。