排序算法分类
比较类排序
通过比较来决定元素间的相对次序,由于其时间复杂度不能突破 O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序
不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。


重点看堆排序、快速排序、归并排序,面试通常会考 O(nlogn) 的排序。
排序动画
- https://www.cnblogs.com/onepixel/p/7674659.html
- https://www.bilibili.com/video/av25136272
- https://www.bilibili.com/video/av63851336
初级排序 O(n^2)
选择排序(Selection Sort)
每次选最小值,然后放到待排序数组的起始位置。
function selectionSort (arr) {const len = arr.length;let minIdx, temp;for (let i = 0; i < len - 1; i++) {minIdx = i;for (let j = i + 1; j < len; j++) {if (arr[j] < arr[minIdx]) {minIdx = j;}}temp = arr[i];arr[i] = arr[minIdx];arr[minIdx] = temp;}return arr;}
插入排序(Insertion Sort)
从前到后逐步构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
function insertionSort (arr) {const len = arr.length;let preIdx, current;for (let i = 1; i < len; i++) {preIdx = i - 1;current = arr[i];while (preIdx >= 0 && arr[preIdx] > current) {arr[preIdx + 1] = arr[preIdx];preIdx--;}arr[preIdx + 1] = current;}return arr;}
冒泡排序(Bubble Sort)
嵌套循环,每次查看相邻的元素如果逆序,则交换。
function bubbleSort (arr) {const len = arr.length;let temp, finish;for (let i = 0; i < len - 1; i++) {finish = true;for (let j = 0; j < len - 1 - i; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j + 1];arr[j + 1] = arr[j];arr[j] = temp;finish = false;}}if (finish) break;}return arr;}
高级排序 O(N*LogN)
快速排序(Quick Sort)
数组取标杆 pivot,将小元素放到 pivot 左边,大元素放右侧,然后依次对左边和右边的子数组继续快排。以达到整个序列有序。
pivot 可以选在任意位置,左边、中间、右边。
function partition (arr, start, end) {const pivot = end;let counter = start;for (let i = start; i < end; i++) {if (arr[i] < arr[pivot]) {if (counter == i) {counter++;} else {const temp = arr[counter];arr[counter] = arr[i];arr[i] = temp;counter++;}}}const temp = arr[pivot];arr[pivot] = arr[counter];arr[counter] = temp;return counter;}function quickSort (arr, begin, end) {if (end < begin) return;const pivot = partition(arr, begin, end);quickSort(arr, begin, pivot - 1);quickSort(arr, pivot + 1, end);}
归并排序(Merge Sort)
- 把长度为 n 的输入序列分成两个长度为 n / 2 的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
function merge (arr, left, mid, right) {const temp = new Array(right - left + 1);let i = left,j = mid + 1,k = 0;while (i <= mid && j <= right) {temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];}while (i <= mid) temp[k++] = arr[i++];while (j <= right) temp[k++] = arr[j++];for (i = 0; i < temp.length; i++) {arr[left + i] = temp[i];}}function mergeSort (arr, left, right) {if (right <= left) return;const mid = (left + right) >> 1;mergeSort(arr, left, mid);mergeSort(arr, mid + 1, right);merge(arr, left, mid, right);}
堆排序(Heap Sort)
堆插入 O(logN),取最大值/最小值 O(1)。
- 数组元素依次建立小顶堆
- 依次取堆顶元素,并删除
class Heap {constructor (arr) {this.len = arr.length;for (let i = Math.floor(this.len / 2); i >= 0; i--) {this.heapify(arr, i);}}heapify (arr, i) {let left = 2 * i + 1,right = 2 * i + 2,largest = i;if (left < this.len && arr[left] > arr[largest]) {largest = left;}if (right < this.len && arr[right] > arr[largest]) {largest = right;}if (largest != i) {this.swap(arr, i, largest);this.heapify(arr, largest);}}swap (arr, i, j) {const temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}function heapSort (arr) {const heap = new Heap(arr);for (let i = arr.length - 1; i > 0; i--) {heap.swap(arr, 0, i);heap.len--;heap.heapify(arr, 0);}}
总结
归并和快排具有相似性,但步骤顺序相反。
归并排序:先排序左右子数组,然后合并两个有序子数组;
快速排序:先调配出左右子数组,然后对于左右子数组进行排序;
特殊排序 O(n)
计数排序(Counting Sort)
计数排序要求输入的数组必须是有确定范围的整数。
将输入的数据值转化为键存储在额外开辟的数组空间中,然后依次把计数大于 1 的填充回原数组。
function countingSort (arr, maxVal) {const bucket = new Array(maxVal + 1);let sortedIndex = 0,arrLen = arr.length,bucketLen = bucket.length;for (let i = 0; i < arrLen; i++) {if (!bucket[arr[i]]) {bucket[arr[i]] = 0;}bucket[arr[i]]++;}for (let j = 0; j < bucketLen; j++) {while (bucket[j] > 0) {arr[sortedIndex++] = j;bucket[j]--;}}}
桶排序(Bucket Sort)
桶排序的工作原理:假设输入数据均匀分布,将数据分到有限的桶里,每个桶再分别排序(可能使用别的排序算法或者以递归方式继续使用桶排序)。
function insertionSort (arr) {const len = arr.length;let preIdx, current;for (let i = 1; i < len; i++) {preIdx = i - 1;current = arr[i];while (preIdx >= 0 && arr[preIdx] > current) {arr[preIdx + 1] = arr[preIdx];preIdx--;}arr[preIdx + 1] = current;}return arr;}function bucketSort (arr, bucketSize) {if (arr.length === 0) return [];let i,minVal = arr[0],maxVal = arr[0];for (i = 1; i < arr.length; i++) {if (arr[i] < minVal) {minVal = arr[i];} else if (arr[i] > maxVal) {maxVal = arr[i];}}// 初始化桶bucketSize = bucketSize || 5;const bucketCount = Math.floor((maxVal - minVal) / bucketSize) + 1,buckets = new Array(bucketCount);for (i = 0; i < buckets.length; i++) {buckets[i] = [];}// 利用映射函数分配数据for (i = 0; i < arr.length; i++) {buckets[Math.floor((arr[i] - minVal) / bucketSize)].push(arr[i]);}arr.length = 0;for (i = 0; i < buckets.length; i++) {insertionSort(buckets[i]); // 桶排序,使用插入排序for (let j = 0; j < buckets[i].length; j++) {arr.push(buckets[i][j]);}}}
基数排序(Radix Sort)
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。
function radixSort (arr, maxDigit) {const counter = [];let mod = 10,dev = 1;for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {for (let j = 0; j < arr.length; j++) {const bucket = parseInt((arr[j] % mod) / dev);if (counter[bucket] == null) counter[bucket] = [];counter[bucket].push(arr[j]);}let pos = 0;for (let j = 0; j < counter.length; j++) {let val = null;if (counter[j]) {while ((val = counter[j].shift()) != null) {arr[pos++] = val;}}}}}
相关题目
// 数组的相对排序// 计数排序/*** @param {number[]} arr1* @param {number[]} arr2* @return {number[]}*/var relativeSortArray = function (arr1, arr2) {const counts = new Array(1001).fill(0);for (const n of arr1) {counts[n]++;}const ans = [];for (const n of arr2) {while (counts[n] > 0) {ans.push(n);counts[n]--;}}for (let i = 0; i < counts.length; i++) {while (counts[i] > 0) {ans.push(i);counts[i]--;}}return ans;};
// 有效的字母异位词,可以使用快排和计数排序/*** @param {string} s* @param {string} t* @return {boolean}*/var isAnagram = function(s, t) {if (s.length != t.length) return false;const map = new Map();for (const i of s) {map.set(i, s);}for (const i of t) {if (!map.has(i)) return false;map.delete(i);}return true;};
// 合并区间/*** @param {number[][]} intervals* @return {number[][]}*/var merge = function(intervals) {intervals.sort((a, b) => a[0] - b[0]);const ans = [];for (let i = 0; i < intervals.length; i++) {const start = intervals[i][0];let cur = intervals[i][1];while(i < intervals.length-1 && cur >= intervals[i+1][0]) {cur = Math.max(intervals[i+1][1], cur);i++;}ans.push([start, cur]);}return ans;};
// 翻转对var reversePairs = function(nums) {if (nums.length === 0) return 0;return reversePairsRecursive(nums, 0, nums.length - 1);};const reversePairsRecursive = (nums, left, right) => {if (left === right) return 0;const mid = Math.floor((left + right) / 2),n1 = reversePairsRecursive(nums, left, mid),n2 = reversePairsRecursive(nums, mid + 1, right);let ret = n1 + n2,i = left,j = mid + 1;while (i <= mid) {while (j <= right && nums[i] > 2 * nums[j]) j++;ret += j - mid - 1;i++;}const sorted = new Array(right - left + 1);let p1 = left,p2 = mid + 1,p = 0;while (p1 <= mid || p2 <= right) {if (p1 > mid) {sorted[p++] = nums[p2++];} else if (p2 > right) {sorted[p++] = nums[p1++];} else {if (nums[p1] < nums[p2]) {sorted[p++] = nums[p1++];} else {sorted[p++] = nums[p2++];}}}for (let k = 0; k < sorted.length; k++) {nums[left + k] = sorted[k];}return ret;}
