什麼是排序?前端
JavaScript中的排序git
必會經典排序算法程序員
leetcode 排序 解法題目github
生活中也有不少排序,好比考試之後按總分進行降序排列:
第一名700分、第二名699分、第三名698分等等等等
值得注意的是,雖然分數是倒序,可是名次倒是正序1,2,3···算法
排序在生活中的例子實在太多了,就不一一贅述了。數據庫
在js中,Array.prototype上的sort()函數能夠很方便的達到咱們對升序和降序的要求。segmentfault
假設要給[2,4,3,1,5]進行排序:後端
const arr = [2,4,3,1,5] // 升序 arr.sort((a,b)=>a-b) // 降序 arr.sort((a,b)=>b-a)
對於複雜狀況的話,例如須要對對象數組根據對象中的某個key排序。數組
// items按照value排序 const items = [ { name: 'Edward', value: 21 }, { name: 'Sharpe', value: 37 }, { name: 'And', value: 45 }, { name: 'The', value: -12 }, { name: 'Magnetic', value: 13 }, { name: 'Zeros', value: 37 } ]; items.sort((a, b) => a.value - b.value);
// key 須要排序的key // 升序仍是降序 function sortBy(arr, key, type = 'asc'){ if(!arr || !key) return; let callback = (a, b) => a[key]- b[key] : if( type === 'desc'){ callback = (a, b) => b[key]- a[key] ; } arr.sort(callback); }
_.sortBy(collection, [iteratees=[_.identity]])瀏覽器
_.sortedIndex([30, 50], 40); // => 1
var objects = [{ 'x': 4 }, { 'x': 5 }]; _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); // => 0 // The `_.property` iteratee shorthand. _.sortedIndexBy(objects, { 'x': 4 }, 'x'); // => 0
_.sortedIndexOf([4, 5, 5, 5, 6], 5); // => 1
_.sortedUniq([1, 1, 2]); // => [1, 2]
_.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);// => [1.1, 2.3]
var users = [ { 'user': 'fred', 'age': 48 }, { 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }, { 'user': 'barney', 'age': 34 } ]; _.sortBy(users, [function(o) { return o.user; }]); // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] _.sortBy(users, ['user', 'age']); // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
InnerArraySort排序算法綜合運用到了快速排序和插入排序
function InnerArraySort(array, length, comparefn) { // In-place QuickSort algorithm. // For short (length <= 22) arrays, insertion sort is used for efficiency. var InsertionSort = function InsertionSort(a, from, to) { for (var i = from + 1; i < to; i++) { var element = a[i]; for (var j = i - 1; j >= from; j--) { var tmp = a[j]; var order = comparefn(tmp, element); if (order > 0) { a[j + 1] = tmp; } else { break; } } a[j + 1] = element; } }; var QuickSort = function QuickSort(a, from, to) { var third_index = 0; while (true) { // Insertion sort is faster for short arrays. if (to - from <= 10) { InsertionSort(a, from, to); return; } if (to - from > 1000) { third_index = GetThirdIndex(a, from, to); } else { third_index = from + ((to - from) >> 1); } // Find a pivot as the median of first, last and middle element. var v0 = a[from]; var v1 = a[to - 1]; var v2 = a[third_index]; var c01 = comparefn(v0, v1); if (c01 > 0) { // v1 < v0, so swap them. var tmp = v0; v0 = v1; v1 = tmp; } // v0 <= v1. var c02 = comparefn(v0, v2); if (c02 >= 0) { // v2 <= v0 <= v1. var tmp = v0; v0 = v2; v2 = v1; v1 = tmp; } else { // v0 <= v1 && v0 < v2 var c12 = comparefn(v1, v2); if (c12 > 0) { // v0 <= v2 < v1 var tmp = v1; v1 = v2; v2 = tmp; } } // v0 <= v1 <= v2 a[from] = v0; a[to - 1] = v2; var pivot = v1; var low_end = from + 1; // Upper bound of elements lower than pivot. var high_start = to - 1; // Lower bound of elements greater than pivot. a[third_index] = a[low_end]; a[low_end] = pivot; // From low_end to i are elements equal to pivot. // From i to high_start are elements that haven't been compared yet. partition: for (var i = low_end + 1; i < high_start; i++) { var element = a[i]; var order = comparefn(element, pivot); if (order < 0) { a[i] = a[low_end]; a[low_end] = element; low_end++; } else if (order > 0) { do { high_start--; if (high_start == i) break partition; var top_elem = a[high_start]; order = comparefn(top_elem, pivot); } while (order > 0); a[i] = a[high_start]; a[high_start] = element; if (order < 0) { element = a[i]; a[i] = a[low_end]; a[low_end] = element; low_end++; } } } if (to - high_start < low_end - from) { QuickSort(a, high_start, to); to = low_end; } else { QuickSort(a, from, low_end); from = high_start; } } }; if (length < 2) return array; QuickSort(array, 0, num_non_undefined); return array; }
經典排序算法有十(幾)種,因爲當前的能力有限,我將先介紹冒泡、選擇、插入、歸併和快排
這五種排序算法。
看了sort()函數的V8源碼之後,是否是束手無策以爲「哇 好難」,除了心存敬畏,其實明白算法是會通過不斷的優化的,sort()函數處理了根據JavaScript語言特性作了不少性能上的優化。
一般來講咱們去開心使用這樣性能又好使用又便捷的sort()函數便可,但其實有一些經典的排序算法,仍是很是值得去探索一下的。
即便數據結構課上學過,但其實時間一久,磚搬得過多,仍是容易忘記的,就算沒有忘記,工做幾年之後再回過頭來看算法,可能會對過去的算法作一個優化。
LeetCode的912題是一個很好的oj環境,適合對本身的排序算法作驗證,推薦給你們。
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
/** * 解法:冒泡排序 * 思路:外層每次循環都是不斷將最大值置於尾部,最小值像氣泡同樣向前冒出 * 性能:4704ms 39.4MB * 時間複雜度:O(n^2) */ var sortArray = function (nums) { for (let i = 0; i < nums.length; i++) { for (let j = 0; j < nums.length - 1 - i; j++) { if (nums[j] > nums[j + 1]) { const temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } return nums; }
/** * 解法:選擇排序 * 思路:已排序區間和未排序區間。在未排序區間中找到最小數,與未排序區間的第一項(已排序區間的下一項)交換,將已排序區間從[]構形成[...],最終完成排序。如果降序的話,則找最大的數。 * 性能:2104ms 41.5MB * 時間複雜度:O(n^2) */ var sortArray = function (nums) { for (let i = 0; i < nums.length; i++) { let min = nums[i]; let idx = i; for (let j = i + 1; j < nums.length; j++) { if (nums[j] < min) { min = nums[j]; idx = j; } if (j === nums.length - 1) { let temp = nums[i]; nums[i] = nums[idx]; nums[idx] = temp; } } } return nums; }
/** * 解法:插入排序 * 思路:已排序區間和未排序區間。取出未排序區間的第一項,在已排序區間上找到本身的位置,通常來講是找foo<x<bar,將x插入foo和bar之間,或者是x<bar插入頭部。 * 關鍵點:插入到指定位置後當即中止在已排序數組中查找 * 性能:2008ms 43.9MB * 時間複雜度:O(n^2) * */ var sortArray = function (nums) { const sorted = [nums[0]]; for (let i = 1; i < nums.length; i++) { // j = i - 1; 也行 for (let j = sorted.length - 1; j >= 0; j--) { if (nums[i] < sorted[j]) { if (j === 0) { sorted.splice(j, 0, nums[i]); } } else if (nums[i] >= sorted[j]) { sorted.splice(j + 1, 0, nums[i]); j = -1; // 這裏很關鍵,插入到指定位置後當即中止在已排序數組中查找 } } } return sorted; } /** * 優化版:插入排序(不借助輔助數組) * 思路:插入splice(j/j+1, 0), 刪除splice(i, 1)[0] * 須要注意的是: splice()返回的是一個數組,例如[1] * 性能:2372ms 42.5MB * 時間複雜度:O(n^2) */ var sortArray = function (nums) { for (let i = 1; i < nums.length; i++) { for (let j = i - 1; j >= 0; j--) { if (nums[i] < nums[j]) { if (j === 0) { nums.splice(j, 0, nums.splice(i, 1)[0]); } } else if (nums[i] >= nums[j]) { nums.splice(j + 1, 0, nums.splice(i, 1)[0]); j = -1; } } } return nums; }
/** * 解法:歸併排序 * 思路:將長度爲n的數組拆爲n/2長度的數組,分別對各自進行排序。再將n/2長度的數組使用歸併排序,直到最終的排序的數組長度爲2,最後將最終排序的數組依次向上合併 * 核心:二分和遞歸。相似二分排序,自頂向下二分拆解排序,自底向上合併排序結果。 * 注意:終止遞歸的條件爲if (length <= 1) { return nums; } * 性能:260ms 47.9MB * 時間複雜度: O(nlogn) */
var sortArray = function (nums) { const merge = (left, right) => { const result = []; while (left.length && right.length) { if (left[0] >= right[0]) { result.push(right.shift()); } else { result.push(left.shift()); } } while (left.length) { result.push(left.shift()); } while (right.length) { result.push(right.shift()); } return result; }; let length = nums.length; if (length <= 1) { return nums; } let middle = Math.floor(length / 2); let left = nums.splice(0, middle); let right = nums; return merge(sortArray(left), sortArray(right)); }
/**解法:快速排序 * 思路: * 1.選中一個分割點split * 2.定義左右雙指針,一次遍歷將分割值小的置於左側,比分割值大的置於右側 * 2.1 左右指針不相遇時 swap(left, right) * 2.2 左右指針相遇時,swap(start, left)而且返回left * 3.分治遞歸式爲左右兩側序列*** * 性能:128ms 40.8MB * 時間複雜度:O(nlogn) */ var sortArray = function (nums) { quickSort(nums, 0, nums.length - 1); return nums; // 定義一個***函數 function quickSort(arr, left, right) { if (left < right) { let splitIndex = findSplitIndex(nums, left, right); quickSort(nums, left, splitIndex - 1); quickSort(nums, splitIndex + 1, right); } } // 查找分割值索引 function findSplitIndex(arr, left, right) { const start = left; const splitValue = arr[start]; while (left !== right) { while (left < right && arr[right] > splitValue) { right--; } while (left < right && arr[left] <= splitValue) { left++; } if (left < right) { swap(arr, left, right); } } swap(arr, start, left); return left; } // 交換位置:左右交換、分割點與left交換 function swap(arr, i, j) { const temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } };
算法過程圖(來自程序員小灰的文章:漫畫:什麼是快速排序?(完整版))
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
var searchInsert = function(nums, target) { /** * 解法2:推入數組重排序法 96ms better than 6.35% */ nums.push(target); var resortedNums = nums.sort((x,y)=>x-y); return resortedNums.indexOf(target); };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
var merge = function(nums1, m, nums2, n) { /** * 特別須要注意的點:這道題會檢查nums1數組內存空間最後的存儲狀況 */ // splice截斷數組 nums1.splice(m); nums2.splice(n); // 未使用concat的緣由:concat返回一個新數組,而題目須要直接在nums1的空間進行存儲 nums2.forEach(num2 => { nums1.push(num2); }); // sort排序當前數組 var ascArr = nums1.sort((a, b) => a - b); return ascArr; };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
/** * @param {number} n - a positive integer * @return {number} */ var hammingWeight = function (n) { /**解法4:排序優化count * 性能:88ms 35.7MB */ let strArr = n.toString(2).split(""); strArr.sort((a, b) => parseInt(b) - parseInt(a)); let count = 0; for (let i = 0; i < strArr.length; i++) { if (strArr[i] === "1") count++; } return count; };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
/** * @param {number[]} nums * @return {number} */ var findUnsortedSubarray = function (nums) { /** * 解法 * - 克隆數組並排序 * - 找起始元素的索引值 * - startIdx 從頭至尾 找到第一個發生變化的元素索引 * - endIdx 從尾到頭 找到第一個發生變化的元素索引 */ // 使用[...nums]克隆一個新數組,是由於sort改變的是自身,不會返回一個新數組 var sortedNums = [...nums].sort((a, b) => a - b); var startIdx = 0; for (var i = 0; i < nums.length; i++) { if (nums[i] !== sortedNums[i]) { startIdx = i; break; } } var endIdx = 0; for (var j = nums.length - 1; j >= 0; j--) { if (nums[j] !== sortedNums[j]) { endIdx = j; break; } } var length = endIdx - startIdx > 0 ? endIdx - startIdx + 1 : 0; return length; };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
/** * @param {number[]} arr * @return {number[]} */ var arrayRankTransform = function(arr) { if (arr.length > Math.pow(10, 5)) return; /** * 生成惟一排序Map */ var uniqArr = Array.from(new Set(arr)); var sortArr = uniqArr.sort((a, b) => a - b); // 構造出一個二維數組做爲Map構造器入參 var twoDimArr = sortArr.map((num, idx) => [num, idx + 1]); var idxMap = new Map(twoDimArr); /** * Map中查找數字序號 */ var serialNums = []; for (var i = 0; i < arr.length; i++) { serialNums.push(idxMap.get(arr[i])); } return serialNums; };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
/** * @param {number[][]} intervals * @return {number[][]} */ var merge = function (intervals) { /** * 解法1:排序 + 棧 * 性能:88ms 36.3MB * 思路: * 推入區間 空棧 或者 arr[0]大於棧最後一個區間閉 * 覆蓋重疊 arr[0]小於棧最後一個區間閉 * */ intervals.sort((a, b) => a[0] - b[0]); let stack = []; for (let i = 0; i < intervals.length; i++) { let currrentInterval = intervals[i]; let stackLastItem = stack[stack.length - 1]; if (stack.length === 0 || currrentInterval[0] > stackLastItem[1]) { stack.push(currrentInterval); } else if (currrentInterval[0] <= stackLastItem[1]) { stackLastItem[1] = Math.max(stackLastItem[1], currrentInterval[1]); } } return stack; };
題目:https://leetcode-cn.com/probl...
題解:https://github.com/FrankKai/l...
var findKthLargest = function (nums, k) { /** * 解法1:倒序排序輸出 */ nums.sort((a, b) => b - a); return nums[k - 1]; };
期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:
- 微信公衆號: 生活在瀏覽器裏的咱們 / excellent_developers
- Github博客: 趁你還年輕233的我的博客
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
- Leetcode討論微信羣:Z2Fva2FpMjAxMDA4MDE=(加我微信拉你進羣)
努力成爲優秀前端工程師!