排序算法分类

比较类排序

通过比较来决定元素间的相对次序,由于其时间复杂度不能突破 O(nlogn),因此也称为非线性时间比较类排序。

非比较类排序

不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

sort.png

sort_compare.png

重点看堆排序、快速排序、归并排序,面试通常会考 O(nlogn) 的排序。

排序动画

初级排序 O(n^2)

选择排序(Selection Sort)

每次选最小值,然后放到待排序数组的起始位置。

  1. function selectionSort (arr) {
  2. const len = arr.length;
  3. let minIdx, temp;
  4. for (let i = 0; i < len - 1; i++) {
  5. minIdx = i;
  6. for (let j = i + 1; j < len; j++) {
  7. if (arr[j] < arr[minIdx]) {
  8. minIdx = j;
  9. }
  10. }
  11. temp = arr[i];
  12. arr[i] = arr[minIdx];
  13. arr[minIdx] = temp;
  14. }
  15. return arr;
  16. }

插入排序(Insertion Sort)

从前到后逐步构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  1. function insertionSort (arr) {
  2. const len = arr.length;
  3. let preIdx, current;
  4. for (let i = 1; i < len; i++) {
  5. preIdx = i - 1;
  6. current = arr[i];
  7. while (preIdx >= 0 && arr[preIdx] > current) {
  8. arr[preIdx + 1] = arr[preIdx];
  9. preIdx--;
  10. }
  11. arr[preIdx + 1] = current;
  12. }
  13. return arr;
  14. }

冒泡排序(Bubble Sort)

嵌套循环,每次查看相邻的元素如果逆序,则交换。

  1. function bubbleSort (arr) {
  2. const len = arr.length;
  3. let temp, finish;
  4. for (let i = 0; i < len - 1; i++) {
  5. finish = true;
  6. for (let j = 0; j < len - 1 - i; j++) {
  7. if (arr[j] > arr[j + 1]) {
  8. temp = arr[j + 1];
  9. arr[j + 1] = arr[j];
  10. arr[j] = temp;
  11. finish = false;
  12. }
  13. }
  14. if (finish) break;
  15. }
  16. return arr;
  17. }

高级排序 O(N*LogN)

快速排序(Quick Sort)

数组取标杆 pivot,将小元素放到 pivot 左边,大元素放右侧,然后依次对左边和右边的子数组继续快排。以达到整个序列有序。

pivot 可以选在任意位置,左边、中间、右边。

  1. function partition (arr, start, end) {
  2. const pivot = end;
  3. let counter = start;
  4. for (let i = start; i < end; i++) {
  5. if (arr[i] < arr[pivot]) {
  6. if (counter == i) {
  7. counter++;
  8. } else {
  9. const temp = arr[counter];
  10. arr[counter] = arr[i];
  11. arr[i] = temp;
  12. counter++;
  13. }
  14. }
  15. }
  16. const temp = arr[pivot];
  17. arr[pivot] = arr[counter];
  18. arr[counter] = temp;
  19. return counter;
  20. }
  21. function quickSort (arr, begin, end) {
  22. if (end < begin) return;
  23. const pivot = partition(arr, begin, end);
  24. quickSort(arr, begin, pivot - 1);
  25. quickSort(arr, pivot + 1, end);
  26. }

归并排序(Merge Sort)

  • 把长度为 n 的输入序列分成两个长度为 n / 2 的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
  1. function merge (arr, left, mid, right) {
  2. const temp = new Array(right - left + 1);
  3. let i = left,
  4. j = mid + 1,
  5. k = 0;
  6. while (i <= mid && j <= right) {
  7. temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
  8. }
  9. while (i <= mid) temp[k++] = arr[i++];
  10. while (j <= right) temp[k++] = arr[j++];
  11. for (i = 0; i < temp.length; i++) {
  12. arr[left + i] = temp[i];
  13. }
  14. }
  15. function mergeSort (arr, left, right) {
  16. if (right <= left) return;
  17. const mid = (left + right) >> 1;
  18. mergeSort(arr, left, mid);
  19. mergeSort(arr, mid + 1, right);
  20. merge(arr, left, mid, right);
  21. }

堆排序(Heap Sort)

堆插入 O(logN),取最大值/最小值 O(1)。

  • 数组元素依次建立小顶堆
  • 依次取堆顶元素,并删除
  1. class Heap {
  2. constructor (arr) {
  3. this.len = arr.length;
  4. for (let i = Math.floor(this.len / 2); i >= 0; i--) {
  5. this.heapify(arr, i);
  6. }
  7. }
  8. heapify (arr, i) {
  9. let left = 2 * i + 1,
  10. right = 2 * i + 2,
  11. largest = i;
  12. if (left < this.len && arr[left] > arr[largest]) {
  13. largest = left;
  14. }
  15. if (right < this.len && arr[right] > arr[largest]) {
  16. largest = right;
  17. }
  18. if (largest != i) {
  19. this.swap(arr, i, largest);
  20. this.heapify(arr, largest);
  21. }
  22. }
  23. swap (arr, i, j) {
  24. const temp = arr[i];
  25. arr[i] = arr[j];
  26. arr[j] = temp;
  27. }
  28. }
  29. function heapSort (arr) {
  30. const heap = new Heap(arr);
  31. for (let i = arr.length - 1; i > 0; i--) {
  32. heap.swap(arr, 0, i);
  33. heap.len--;
  34. heap.heapify(arr, 0);
  35. }
  36. }

总结

归并和快排具有相似性,但步骤顺序相反。

归并排序:先排序左右子数组,然后合并两个有序子数组;
快速排序:先调配出左右子数组,然后对于左右子数组进行排序;

特殊排序 O(n)

计数排序(Counting Sort)

计数排序要求输入的数组必须是有确定范围的整数。
将输入的数据值转化为键存储在额外开辟的数组空间中,然后依次把计数大于 1 的填充回原数组。

  1. function countingSort (arr, maxVal) {
  2. const bucket = new Array(maxVal + 1);
  3. let sortedIndex = 0,
  4. arrLen = arr.length,
  5. bucketLen = bucket.length;
  6. for (let i = 0; i < arrLen; i++) {
  7. if (!bucket[arr[i]]) {
  8. bucket[arr[i]] = 0;
  9. }
  10. bucket[arr[i]]++;
  11. }
  12. for (let j = 0; j < bucketLen; j++) {
  13. while (bucket[j] > 0) {
  14. arr[sortedIndex++] = j;
  15. bucket[j]--;
  16. }
  17. }
  18. }

桶排序(Bucket Sort)

桶排序的工作原理:假设输入数据均匀分布,将数据分到有限的桶里,每个桶再分别排序(可能使用别的排序算法或者以递归方式继续使用桶排序)。

  1. function insertionSort (arr) {
  2. const len = arr.length;
  3. let preIdx, current;
  4. for (let i = 1; i < len; i++) {
  5. preIdx = i - 1;
  6. current = arr[i];
  7. while (preIdx >= 0 && arr[preIdx] > current) {
  8. arr[preIdx + 1] = arr[preIdx];
  9. preIdx--;
  10. }
  11. arr[preIdx + 1] = current;
  12. }
  13. return arr;
  14. }
  15. function bucketSort (arr, bucketSize) {
  16. if (arr.length === 0) return [];
  17. let i,
  18. minVal = arr[0],
  19. maxVal = arr[0];
  20. for (i = 1; i < arr.length; i++) {
  21. if (arr[i] < minVal) {
  22. minVal = arr[i];
  23. } else if (arr[i] > maxVal) {
  24. maxVal = arr[i];
  25. }
  26. }
  27. // 初始化桶
  28. bucketSize = bucketSize || 5;
  29. const bucketCount = Math.floor((maxVal - minVal) / bucketSize) + 1,
  30. buckets = new Array(bucketCount);
  31. for (i = 0; i < buckets.length; i++) {
  32. buckets[i] = [];
  33. }
  34. // 利用映射函数分配数据
  35. for (i = 0; i < arr.length; i++) {
  36. buckets[Math.floor((arr[i] - minVal) / bucketSize)].push(arr[i]);
  37. }
  38. arr.length = 0;
  39. for (i = 0; i < buckets.length; i++) {
  40. insertionSort(buckets[i]); // 桶排序,使用插入排序
  41. for (let j = 0; j < buckets[i].length; j++) {
  42. arr.push(buckets[i][j]);
  43. }
  44. }
  45. }

基数排序(Radix Sort)

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。

  1. function radixSort (arr, maxDigit) {
  2. const counter = [];
  3. let mod = 10,
  4. dev = 1;
  5. for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
  6. for (let j = 0; j < arr.length; j++) {
  7. const bucket = parseInt((arr[j] % mod) / dev);
  8. if (counter[bucket] == null) counter[bucket] = [];
  9. counter[bucket].push(arr[j]);
  10. }
  11. let pos = 0;
  12. for (let j = 0; j < counter.length; j++) {
  13. let val = null;
  14. if (counter[j]) {
  15. while ((val = counter[j].shift()) != null) {
  16. arr[pos++] = val;
  17. }
  18. }
  19. }
  20. }
  21. }

相关题目

数组的相对排序

有效的字母异位词

合并区间

翻转对

  1. // 数组的相对排序
  2. // 计数排序
  3. /**
  4. * @param {number[]} arr1
  5. * @param {number[]} arr2
  6. * @return {number[]}
  7. */
  8. var relativeSortArray = function (arr1, arr2) {
  9. const counts = new Array(1001).fill(0);
  10. for (const n of arr1) {
  11. counts[n]++;
  12. }
  13. const ans = [];
  14. for (const n of arr2) {
  15. while (counts[n] > 0) {
  16. ans.push(n);
  17. counts[n]--;
  18. }
  19. }
  20. for (let i = 0; i < counts.length; i++) {
  21. while (counts[i] > 0) {
  22. ans.push(i);
  23. counts[i]--;
  24. }
  25. }
  26. return ans;
  27. };
  1. // 有效的字母异位词,可以使用快排和计数排序
  2. /**
  3. * @param {string} s
  4. * @param {string} t
  5. * @return {boolean}
  6. */
  7. var isAnagram = function(s, t) {
  8. if (s.length != t.length) return false;
  9. const map = new Map();
  10. for (const i of s) {
  11. map.set(i, s);
  12. }
  13. for (const i of t) {
  14. if (!map.has(i)) return false;
  15. map.delete(i);
  16. }
  17. return true;
  18. };
  1. // 合并区间
  2. /**
  3. * @param {number[][]} intervals
  4. * @return {number[][]}
  5. */
  6. var merge = function(intervals) {
  7. intervals.sort((a, b) => a[0] - b[0]);
  8. const ans = [];
  9. for (let i = 0; i < intervals.length; i++) {
  10. const start = intervals[i][0];
  11. let cur = intervals[i][1];
  12. while(i < intervals.length-1 && cur >= intervals[i+1][0]) {
  13. cur = Math.max(intervals[i+1][1], cur);
  14. i++;
  15. }
  16. ans.push([start, cur]);
  17. }
  18. return ans;
  19. };
  1. // 翻转对
  2. var reversePairs = function(nums) {
  3. if (nums.length === 0) return 0;
  4. return reversePairsRecursive(nums, 0, nums.length - 1);
  5. };
  6. const reversePairsRecursive = (nums, left, right) => {
  7. if (left === right) return 0;
  8. const mid = Math.floor((left + right) / 2),
  9. n1 = reversePairsRecursive(nums, left, mid),
  10. n2 = reversePairsRecursive(nums, mid + 1, right);
  11. let ret = n1 + n2,
  12. i = left,
  13. j = mid + 1;
  14. while (i <= mid) {
  15. while (j <= right && nums[i] > 2 * nums[j]) j++;
  16. ret += j - mid - 1;
  17. i++;
  18. }
  19. const sorted = new Array(right - left + 1);
  20. let p1 = left,
  21. p2 = mid + 1,
  22. p = 0;
  23. while (p1 <= mid || p2 <= right) {
  24. if (p1 > mid) {
  25. sorted[p++] = nums[p2++];
  26. } else if (p2 > right) {
  27. sorted[p++] = nums[p1++];
  28. } else {
  29. if (nums[p1] < nums[p2]) {
  30. sorted[p++] = nums[p1++];
  31. } else {
  32. sorted[p++] = nums[p2++];
  33. }
  34. }
  35. }
  36. for (let k = 0; k < sorted.length; k++) {
  37. nums[left + k] = sorted[k];
  38. }
  39. return ret;
  40. }