摘要:不過說到排序,最容易想到的就是冒泡排序,選擇排序,插入排序了。一是影響太小,而是我們?nèi)说男蕟栴},一分鐘能從頭寫個(gè)冒泡選擇插入的排序方法,而換成是歸并排序呢原文發(fā)表在我的博客排序,不只是冒泡,歡迎訪問
非常非常推薦大家去讀一本gitBook上的書 - 十大經(jīng)典排序算法 : https://sort.hust.cc/ , 本文的動(dòng)圖和演示代碼均是這里面的。
做編程,排序是個(gè)必然的需求。前端也不例外,雖然不多,但是你肯定會(huì)遇到。
不過說到排序,最容易想到的就是冒泡排序,選擇排序,插入排序了。
冒泡排序依次比較相鄰的兩個(gè)元素,如果后一個(gè)小于前一個(gè),則交換,這樣從頭到尾一次,就將最大的放到了末尾。
從頭到尾再來一次,由于每進(jìn)行一輪,最后的都已經(jīng)是最大的了,因此后一輪需要比較次數(shù)可以比上一次少一個(gè)。雖然你還是可以讓他從頭到尾來比較,但是后面的比較是沒有意義的無(wú)用功,為了效率,你應(yīng)該對(duì)代碼進(jìn)行優(yōu)化。
圖片演示如下:
代碼實(shí)現(xiàn):
function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len - 1; i++) { for (var j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j+1]) { // 相鄰元素兩兩對(duì)比 var temp = arr[j+1]; // 元素交換 arr[j+1] = arr[j]; arr[j] = temp; } } } return arr; }選擇排序
選擇排序我覺得是最簡(jiǎn)單的了,大一學(xué)VB的時(shí)候,就只記住了這個(gè)排序方法,原理非常簡(jiǎn)單:每次都找一個(gè)最大或者最小的排在開始即可。
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再?gòu)氖S辔磁判蛟刂欣^續(xù)尋找最小(大)元素,然后放到已排序序列的末尾。
重復(fù)第二步,直到所有元素均排序完畢。
動(dòng)圖演示:
代碼演示:
function selectionSort(arr) { var len = arr.length; var minIndex, temp; for (var i = 0; i < len - 1; i++) { minIndex = i; for (var j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { // 尋找最小的數(shù) minIndex = j; // 將最小數(shù)的索引保存 } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; }插入排序
插入排序也比較簡(jiǎn)單。就像打撲克一樣,依次將拿到的元素插入到正確的位置即可。
將第一待排序序列第一個(gè)元素看做一個(gè)有序序列,把第二個(gè)元素到最后一個(gè)元素當(dāng)成是未排序序列。
從頭到尾依次掃描未排序序列,將掃描到的每個(gè)元素插入有序序列的適當(dāng)位置。(如果待插入的元素與有序序列中的某個(gè)元素相等,則將待插入元素插入到相等元素的后面。)
動(dòng)圖演示:
代碼示例:
function insertionSort(arr) { var len = arr.length; var preIndex, current; for (var i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while(preIndex >= 0 && arr[preIndex] > current) { arr[preIndex+1] = arr[preIndex]; preIndex--; } arr[preIndex+1] = current; } return arr; }簡(jiǎn)單的代價(jià)是低效
上面三種都是非常簡(jiǎn)單的排序方法,簡(jiǎn)單的同時(shí)呢,效率也會(huì)比較低,還是拿這本書里的對(duì)比圖來說明:
時(shí)間復(fù)雜度都高達(dá)O(n^2),而它們后面的一些排序算法時(shí)間復(fù)雜度基本都只有O(n log n)。
我的強(qiáng)迫癥又犯了,我想要高效率一點(diǎn)的排序方法。
歸并排序簡(jiǎn)單把這本書的內(nèi)容過了一遍,當(dāng)時(shí)就理解了這個(gè)歸并排序,因此這里就談一下這個(gè)歸并排序吧。
基本原理是分治法,就是分開并且遞歸來排序。
步驟如下:
申請(qǐng)空間,使其大小為兩個(gè)已經(jīng)排序序列之和,該空間用來存放合并后的序列;
設(shè)定兩個(gè)指針,最初位置分別為兩個(gè)已經(jīng)排序序列的起始位置;
比較兩個(gè)指針?biāo)赶虻脑兀x擇相對(duì)小的元素放入到合并空間,并移動(dòng)指針到下一位置;
重復(fù)步驟 3 直到某一指針達(dá)到序列尾;
將另一序列剩下的所有元素直接復(fù)制到合并序列尾。
動(dòng)圖演示:
代碼示例:
function mergeSort(arr) { // 采用自上而下的遞歸方法 var len = arr.length; if(len < 2) { return arr; } var middle = Math.floor(len / 2), left = arr.slice(0, middle), right = arr.slice(middle); return merge(mergeSort(left), mergeSort(right)); } function merge(left, right) { var result = []; while (left.length && right.length) { if (left[0] <= right[0]) { result.push(left.shift()); } else { result.push(right.shift()); } } while (left.length) result.push(left.shift()); while (right.length) result.push(right.shift()); return result; }
既然是個(gè)愛折騰的人,折騰了總得看看效果吧。
效率測(cè)試由于我學(xué)這個(gè)來進(jìn)行排序不是對(duì)簡(jiǎn)單數(shù)組,數(shù)組內(nèi)都是對(duì)象,要對(duì)對(duì)象的某個(gè)屬性進(jìn)行排序,還要考慮升降序。
因此我的代碼實(shí)現(xiàn)如下:
/** * [歸并排序] * @param {[Array]} arr [要排序的數(shù)組] * @param {[String]} prop [排序字段,用于數(shù)組成員是對(duì)象時(shí),按照其某個(gè)屬性進(jìn)行排序,簡(jiǎn)單數(shù)組直接排序忽略此參數(shù)] * @param {[String]} order [排序方式 省略或asc為升序 否則降序] * @return {[Array]} [排序后數(shù)組,新數(shù)組,并非在原數(shù)組上的修改] */ var mergeSort = (function() { // 合并 var _merge = function(left, right, prop) { var result = []; // 對(duì)數(shù)組內(nèi)成員的某個(gè)屬性排序 if (prop) { while (left.length && right.length) { if (left[0][prop] <= right[0][prop]) { result.push(left.shift()); } else { result.push(right.shift()); } } } else { // 數(shù)組成員直接排序 while (left.length && right.length) { if (left[0] <= right[0]) { result.push(left.shift()); } else { result.push(right.shift()); } } } while (left.length) result.push(left.shift()); while (right.length) result.push(right.shift()); return result; }; var _mergeSort = function(arr, prop) { // 采用自上而下的遞歸方法 var len = arr.length; if (len < 2) { return arr; } var middle = Math.floor(len / 2), left = arr.slice(0, middle), right = arr.slice(middle); return _merge(_mergeSort(left, prop), _mergeSort(right, prop), prop); }; return function(arr, prop, order) { var result = _mergeSort(arr, prop); if (!order || order.toLowerCase() === "asc") { // 升序 return result; } else { // 降序 var _ = []; result.forEach(function(item) { _.unshift(item); }); return _; } }; })();
需要對(duì)哪個(gè)屬性進(jìn)行排序是不確定,可以隨意指定,因此寫成了參數(shù)。有由于不想讓這些東西在每次循環(huán)都進(jìn)行判斷,因此代碼有點(diǎn)冗余。
關(guān)于降序的問題,也沒有加入?yún)?shù)中,而是簡(jiǎn)單的升序后再逆序輸出。原因是不想讓每次循環(huán)遞歸里都去判斷條件,所以簡(jiǎn)單處理了。
下面就是見證效率的時(shí)候了,一段數(shù)據(jù)模擬:
var getData = function() { return Mock.mock({ "list|1000": [{ name: "@cname", age: "@integer(0,500)" }] }).list; };
上面使用Mock進(jìn)行了模擬數(shù)據(jù),關(guān)于Mock : http://mockjs.com/
實(shí)際測(cè)試來啦:
// 效率測(cè)試 var arr = getData(); console.time("歸并排序"); mergeSort(arr, "age"); console.timeEnd("歸并排序"); console.time("冒泡排序"); for (var i = 0, l = arr.length; i < l - 1; ++i) { var temp; for (var j = 0; j < l - i - 1; ++j) { if (arr[j].age > arr[j + 1].age) { temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } } console.timeEnd("冒泡排序");
進(jìn)行了五次,效果如下:
// 歸并排序: 6.592ms // 冒泡排序: 25.959ms // 歸并排序: 1.334ms // 冒泡排序: 20.078ms // 歸并排序: 1.085ms // 冒泡排序: 16.420ms // 歸并排序: 1.200ms // 冒泡排序: 16.574ms // 歸并排序: 2.593ms // 冒泡排序: 12.653ms
最低4倍,最高近16倍的效率之差還是比較滿意的。
雖然1000條數(shù)據(jù)讓前端排序的可能性不大,但是幾十上百條的情況還是有的。另外由于node,JavaScript也能運(yùn)行的服務(wù)端了,這個(gè)效率的提升也還是有用武之地的。
一點(diǎn)疑問歸并排序里面使用了遞歸,在《數(shù)據(jù)結(jié)構(gòu)與算法 JavaScript 描述》中,作者給出了自下而上的迭代方法。但是對(duì)于遞歸法,作者卻認(rèn)為:
However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中這種方式不太可行,因?yàn)檫@個(gè)算法的遞歸深度對(duì)它來講太深了。
gitbook上這本書的作者對(duì)此有疑問,我也有疑問。
歸并中雖然用了遞歸,但是他是放在return后的呀。關(guān)于在renturn后的遞歸是有尾遞歸優(yōu)化的呀。
關(guān)于尾遞歸優(yōu)化是指:本來外層函數(shù)內(nèi)部再調(diào)用一個(gè)函數(shù)的話,由于外層函數(shù)需要等待內(nèi)層函數(shù)返回后才能返回結(jié)果,進(jìn)入內(nèi)層函數(shù)后,外層函數(shù)的信息,內(nèi)存中是必須記住的,也就是調(diào)用堆棧。而內(nèi)部函數(shù)放在return關(guān)鍵字后,就表示外層函數(shù)到此也就結(jié)束了,進(jìn)入內(nèi)層函數(shù)后,沒有必要再記住外層函數(shù)內(nèi)的所有信息。
上面是我的理解的描述,不知道算不算準(zhǔn)確。chrome下已經(jīng)可以開啟尾遞歸優(yōu)化的功能了,我覺得這個(gè)遞歸是不該影響他在JavaScript下的使用的。
最后有興趣的話,推薦讀讀這本書,進(jìn)行排序的時(shí)候,可以考慮一些更高效的方法。
不過需要注意的是,這些高效率的排序方法,一般都需要相對(duì)較多的額外內(nèi)存空間,需要權(quán)衡一下。
另外,非常小規(guī)模的數(shù)據(jù)就沒有必要了。一是影響太小,而是我們?nèi)说男蕟栴},一分鐘能從頭寫個(gè)冒泡、選擇、插入的排序方法,而換成是歸并排序呢?
原文發(fā)表在我的博客JavaScript排序,不只是冒泡,歡迎訪問!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/82161.html
摘要:說明上次寫了實(shí)現(xiàn)冒泡排序,只是簡(jiǎn)單的說了冒泡排序算法是什么,怎么實(shí)現(xiàn),這次來實(shí)現(xiàn)將冒泡排序的過程展現(xiàn)出來。總結(jié)上面的兩個(gè)版本的思路基本一樣,用一句話概括就是,記錄冒泡排序所有的改變,將這些改變一步一步的顯示出來。 說明 上次寫了 JavaScript實(shí)現(xiàn)冒泡排序 ,只是簡(jiǎn)單的說了冒泡排序算法是什么,怎么實(shí)現(xiàn),這次來實(shí)現(xiàn)將冒泡排序的過程展現(xiàn)出來。 解釋 先來個(gè)簡(jiǎn)單的版本,看效果圖 sh...
摘要:推薦一下,,這里還有個(gè)可視化的排序博客,各大排序算法的實(shí)現(xiàn)都栩栩如生。堆排序堆排序是指利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序算法。共勉參考維基百科排序搜索聊一聊排序算法秒殺種排序算法版排序圖解排序算法實(shí)現(xiàn)歡迎來我的博客交流 最近看到了很多公司都在準(zhǔn)備明年的實(shí)習(xí)校招,雖然離三月份還有一段時(shí)間,感覺已經(jīng)可以準(zhǔn)備了。在網(wǎng)上看了一些排序算法和數(shù)組去重操作,感覺都寫的很好,心血來潮,也來寫一寫。 s...
摘要:筆者寫的數(shù)據(jù)結(jié)構(gòu)與算法之美系列用的語(yǔ)言是,旨在入門數(shù)據(jù)結(jié)構(gòu)與算法和方便以后復(fù)習(xí)。這應(yīng)該是目前較為簡(jiǎn)單的十大經(jīng)典排序算法的文章講解了吧。比如原本在的前面,而,排序之后,在的后面十大經(jīng)典排序算法冒泡排序思想冒泡排序只會(huì)操作相鄰的兩個(gè)數(shù)據(jù)。 showImg(https://segmentfault.com/img/bVbvHet); 1. 前言 算法為王。想學(xué)好前端,先練好內(nèi)功,內(nèi)功不行,就...
摘要:冒泡排序原理冒泡排序的過程就是將數(shù)組中相鄰的兩個(gè)元素進(jìn)行比較,如果前面的元素比后面的元素要大交換位置,否則位置不變舉個(gè)栗子有數(shù)組第一輪循環(huán)和比較,小于兩者位置不變,接下來和比較,大于,兩者交換位置,接著和比較,兩者交換位置,繼續(xù)和比較兩者交 1.冒泡排序 原理:冒泡排序的過程就是將數(shù)組中相鄰的兩個(gè)元素進(jìn)行比較,如果前面的元素比后面的元素要大交換位置,否則位置不變;舉個(gè)栗子:有數(shù)組 ar...
摘要:之所以把冒泡排序選擇排序插入排序放在一起比較,是因?yàn)樗鼈兊钠骄鶗r(shí)間復(fù)雜度都為。其中,冒泡排序就是原地排序算法。所以冒泡排序是穩(wěn)定的排序算法。選擇排序思路選擇排序算法的實(shí)現(xiàn)思路有點(diǎn)類似插入排序,也分已排序區(qū)間和未排序區(qū)間。 showImg(https://segmentfault.com/img/bVbuvnj?w=900&h=250); 1. 前言 算法為王。 想學(xué)好前端,先練好內(nèi)功,...
閱讀 810·2021-10-14 09:43
閱讀 2133·2021-09-30 09:48
閱讀 3457·2021-09-08 09:45
閱讀 1104·2021-09-02 15:41
閱讀 1900·2021-08-26 14:15
閱讀 787·2021-08-03 14:04
閱讀 2987·2019-08-30 15:56
閱讀 3083·2019-08-30 15:52