摘要:也稱縮小增量排序,是直接插入排序算法的一種更高效的改進(jìn)版本。希爾排序是非穩(wěn)定排序算法。該方法因於年提出而得名。
前言
因?yàn)楸容^隨心所欲,所以我不按難度分享算法,所以你們會(huì)看到有時(shí)候順序有變化,因?yàn)槲野l(fā)表的時(shí)候會(huì)按照難度修改下位置,盡量讓你們看的時(shí)候能從簡(jiǎn)單開始,
以后每次更新都加個(gè)更新時(shí)間好了,讓你們知道我進(jìn)度.新增計(jì)時(shí)函數(shù)直觀對(duì)比效率
并且因?yàn)橘Y料比較雜,很多都是我個(gè)人理解說法,如果有發(fā)現(xiàn)問題歡迎批評(píng),造福更多人,誤人誤己就不好了
思路是一樣的,寫法不是唯一,所以不用死記代碼的
折騰了這麼久時(shí)間總算把十個(gè)算法都總結(jié)完了,但是還不熟練,一些思想還沒吃透,覺得有些寫法應(yīng)該還能更好,以后肯定還會(huì)找時(shí)間優(yōu)化一下的,但是可能不是最近,我有點(diǎn)迫不及待得想去總結(jié)下其他方面的知識(shí)點(diǎn),例如請(qǐng)求方面的東西,好多短板需要學(xué)習(xí)
PS:
2018/12/18 全新排版,代碼優(yōu)化
PS:經(jīng)博友點(diǎn)明之后,為了提高嚴(yán)謹(jǐn)性,我把測(cè)試方法都新增類型判斷和過濾,生成函數(shù)新增能否重復(fù)取值的參數(shù).
首先介紹一些生成測(cè)試?yán)拥姆椒ńo大家,方便效率不用手動(dòng)添加。
為了提高測(cè)試直觀性,我們采用固定亂序數(shù)組使用.
//是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); } /** * @param {*} num 生成長(zhǎng)度 * @param {*} isRepetition 能否重復(fù)取值 默認(rèn)不能 * @param {*} min 最小范圍 默認(rèn)0 * @param {*} max 最大范圍 默認(rèn)1000 * @returns */ function randomAry(num, isRepetition, min, max) { var ary = [], i = 0, min = min || 0, max = max || 1000; if (!isRepetition && num > max - min) { throw ("當(dāng)前取值范圍超過最小值最大值之間的范圍并且不能重復(fù)取值!"); } for (; i < num; i++) { const random = Math.ceil(Math.random() * (max - min) + min); if (!isRepetition && ary.indexOf(random) > -1) { --i; continue; } ary.push(random); } console.log("排序前: ", ary) return ary; }總結(jié),演算法效率
(友情鏈接,什麼叫l(wèi)og,應(yīng)該不少人跟我一樣忘了什麼叫l(wèi)og吧!(~ ̄▽ ̄)~)
排序方法 | 平均情況 | 最好情況 | 最壞情況 | 空間復(fù)雜度 | 排序方式 | 穩(wěn)定性 |
---|---|---|---|---|---|---|
選擇排序 | O(n2) | O(n2) | O(n2) | O(1) | In-place | 不穩(wěn)定 |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | In-place | 穩(wěn)定 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | In-place | 穩(wěn)定 |
快速排序 | O(n log n) | O(n log n) | O(n2) | O(log n) | In-place | 不穩(wěn)定 |
歸并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | Out-place | 穩(wěn)定 |
希爾排序 | O(n log n) | O(n log2 n) | O(n log2 n) | O(1) | In-place | 不穩(wěn)定 |
計(jì)數(shù)排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | Out-place | 穩(wěn)定 |
桶排序 | O(n + k) | O(n + k) | O(n2) | O(n + k) | Out-place | 穩(wěn)定 |
基數(shù)排序 | O(n * k) | O(n * k) | O(n * k) | O(n + k) | Out-place | 穩(wěn)定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | In-place | 不穩(wěn)定 |
術(shù)語 | 描述 |
---|---|
n | 數(shù)據(jù)規(guī)模 |
k | 桶的個(gè)數(shù) |
In-place | 佔(zhàn)用常數(shù)內(nèi)存,不佔(zhàn)用額外內(nèi)存 |
Out-place | 佔(zhàn)用額外內(nèi)存 |
穩(wěn)定性 | 存在相同的元素的話,排序前后他們的相對(duì)位置會(huì)不會(huì)發(fā)生變化 |
第一個(gè)循環(huán),從零開始,假設(shè)它是最小值( i )
第二個(gè)循環(huán),從最小值+1( j )位置開始,后續(xù)所有數(shù)據(jù)逐個(gè)跟最小值比較,發(fā)現(xiàn)有更小值就替換掉
第二層循環(huán)遍歷完后如果最小值已經(jīng)改變了,就跟原最小值(也就是第一層循環(huán)的 i )互換位置
繼續(xù)重復(fù)步驟直到完成
要實(shí)現(xiàn)上述規(guī)則需要用雙層for循環(huán)
優(yōu)點(diǎn):① 最最最簡(jiǎn)單粗暴,不佔(zhàn)用額外的內(nèi)存空間
缺點(diǎn):① 最最最噁心,計(jì)算過程消耗性能巨大,數(shù)組越長(zhǎng)遍歷次數(shù)越多,并且效率極度不穩(wěn)定,不管數(shù)據(jù)亂序程度,它都必定會(huì)遍歷所有數(shù)據(jù)
//選擇排序 function selectIn(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var len = ary.length, i = 0, j, min; //最小值 //互換位置 var swap = function(a, b) { ary[a] = [ary[b], (ary[b] = ary[a])][0]; }; //拆分 var select = (function() { for (; i < len; i++) { //假設(shè)當(dāng)前是最小值 min = i; for (j = i + 1; j < len; j++) { //找到最小值 if (ary[min] > ary[j]) min = j; } //符合條件互換位置 if (i != min) swap(i, min); } return ary; })(); //返回新數(shù)組 return select; } useTime("選擇排序算法", selectIn); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }
運(yùn)行結(jié)果:
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 選擇排序算法耗時(shí): 12.634ms插入排序算法 思路:
插入算法把要排序的數(shù)組分成兩部分:第一部分包含了這個(gè)數(shù)組的所有元素,但將最后一個(gè)元素除外(讓數(shù)組多一個(gè)空間才有插入的位置),而第二部分就只包含這一個(gè)元素(即待插入元素)。在第一部分排序完成后,再將這個(gè)最后元素插入到已排好序的第一部分中。
第一個(gè)循環(huán),從 1 開始,保存當(dāng)前值作參考數(shù)
第二個(gè)循環(huán),從 j=i-1 位置開始,滿足 j>=0 并且 j 位置值大於參考值時(shí)數(shù)值位置后移
不滿足條件中斷循環(huán),參考值賦值當(dāng)前 j+1 位置
要實(shí)現(xiàn)上述規(guī)則需要用到for和while循環(huán)
優(yōu)點(diǎn):① 適用於少量數(shù)據(jù)的排序,最好情況就是,序列已經(jīng)是升序排列了,在這種情況下,需要進(jìn)行的比較操作需(n-1)次即可
缺點(diǎn):① 計(jì)算過程消耗性能巨大,越往后遍歷次數(shù)越多,跟數(shù)據(jù)亂序程度有關(guān),最壞情況就是,序列是降序排列,那麼此時(shí)需要進(jìn)行的比較共有 n(n-1)/2 次。插入排序的賦值操作是比較操作的次數(shù)加上 (n-1)次
//插入排序 function insertIn(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; //雙層遍歷,i要從1開始 var len = ary.length, i = 1, j, tmp, //臨時(shí)變量 copy = ary.slice(0); // 設(shè)置數(shù)組副本 for (; i < len; i++) { tmp = copy[i]; j = i - 1; //找到相應(yīng)位置并插入 while (j >= 0 && copy[j] > tmp) { //這裡i是固定的,j是遞減的,所以用j+1 copy[j + 1] = copy[j]; j--; } //賦值中斷位置,有種情況是順序沒發(fā)生變化相當(dāng)於重新賦值自身,所以是穩(wěn)定算法 copy[j + 1] = tmp; } return copy; } useTime("插入排序", insertIn); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }
運(yùn)行結(jié)果:
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 插入排序耗時(shí): 10.002ms冒泡排序演算法 思路:
遍歷對(duì)比相鄰兩個(gè)數(shù)據(jù),小的排在前面,如果前面的數(shù)據(jù)比后面的大就交換位置,這個(gè)算法的名字由來是因?yàn)樵酱蟮脑貢?huì)經(jīng)由交換慢慢“浮”到數(shù)列的頂端.
外循環(huán),從數(shù)組長(zhǎng)度開始遞減
內(nèi)循環(huán),從 i=0 開始不超過數(shù)組長(zhǎng)度開始遞增,比較 i 和 i+1 位置數(shù)字排序
重復(fù)一二步驟
優(yōu)點(diǎn):① 最好情況就是,序列已經(jīng)是升序排列了,在這種情況下,需要進(jìn)行的比較操作需(n-1)次即可。
缺點(diǎn):① 是比較次數(shù)多,效率較低,每次只能移動(dòng)相鄰兩個(gè)數(shù)據(jù)。最壞情況就是,序列是降序排列,那麼此時(shí)需要進(jìn)行的比較共有 n(n-1)/2 次。
//冒泡排序 function bubbleSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var len = ary.length, i; //互換位置 var swap = function(a, b) { ary[a] = [ary[b], (ary[b] = ary[a])][0]; }; while (len > 0) { for (i = 0; i < len - 1; i++) { if (ary[i] > ary[i + 1]) swap(i, i + 1); } len--; } return ary; } useTime("冒泡排序算法", bubbleSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }
運(yùn)行結(jié)果:
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 冒泡排序算法耗時(shí): 12.200ms快速排序算法 思路:
通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序,整個(gè)排序過程可以遞歸進(jìn)行,以此達(dá)到整個(gè)數(shù)據(jù)變成有序序列。
聲明兩個(gè)新數(shù)組,原數(shù)組里選擇一個(gè)中間元素作為參考數(shù)
其他元素里比參考數(shù)小的和比參考數(shù)大的分別放進(jìn)兩個(gè)新數(shù)組裡并且重新組合成一個(gè)數(shù)組
然后不斷重復(fù)一二步驟直到完成為止
要實(shí)現(xiàn)上述規(guī)則需要用到 for 循環(huán)和遞歸.
優(yōu)點(diǎn):① 是冒泡排序的改進(jìn)版,使用得最廣泛,速度也較快。
缺點(diǎn):① 在初始序列有序或者基本有序時(shí),其時(shí)間復(fù)雜度降為 O(n*n) ,數(shù)據(jù)越多遞歸層級(jí)越深占用堆??臻g越大。
//快速排序 function quickSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var middleIndex = Math.ceil(ary.length / 2), //尋找中間位置 middle = ary.splice(middleIndex, 1), //提取中間值 left = [], right = [], i = 0, len = ary.length; //上面有個(gè)刪除步驟,不能放前面 for (; i < len; i++) { (ary[i] < middle ? left : right).push(ary[i]); } //遞歸執(zhí)行 return quickSort(left).concat(middle, quickSort(right)); } useTime("快速排序算法", quickSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }
運(yùn)行結(jié)果:
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 快速排序算法耗時(shí): 13.195ms
有個(gè)需要注意的地方是過濾數(shù)組部分必須放在聲明變量,再具體點(diǎn)就是提取參考數(shù)之前,放在后面如果數(shù)組長(zhǎng)度為 2,刪除參考數(shù)之后會(huì)因?yàn)榉蠗l件被直接返回剩餘的數(shù)組,可以看看實(shí)例.(如果一次看不到效果,加加減減空格再查看效果就有變化了)
//快速排序 function quickSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var middleIndex = Math.ceil(ary.length / 2), middle = ary.splice(middleIndex, 1), left = [], right = [], i = 0, len = ary.length; //過濾部分?jǐn)?shù)組 if (len <= 1) return ary; for (; i < len; i++) { (ary[i] < middle ? left : right).push(ary[i]); } return quickSort(left).concat(middle, quickSort(right)); } useTime("快速排序算法", quickSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }歸并排序算法 思路:
第一步拆分,數(shù)據(jù)不斷從中間拆分,直到無法繼續(xù)為止(從上至下)
[12,56,23,2] => [12,56] + [23,2] => [12] + [56] + [23] + [2]
第二步比較合并,每?jī)山M數(shù)據(jù)排序合并,直到合并到只剩一組數(shù)據(jù)(從下至上)
[12] + [56] + [23] + [2] => [12,56] + [2,23] => [2,12,23,56]
要實(shí)現(xiàn)上述規(guī)則需要用到while循環(huán)和嵌套遞歸。
優(yōu)點(diǎn):① 速度僅次於快速排序,例如如果排序項(xiàng)的數(shù)目是 1000,用歸併排序需要 3 秒的時(shí)間,那麼用插入排序則需要大概 16.7 分鐘(差距隨排序項(xiàng)增大而增加)。
缺點(diǎn):① 比較佔(zhàn)用內(nèi)存,復(fù)雜度稍微高一點(diǎn)點(diǎn)
//歸并排序 function excreteAndMerge(ary) { //類型檢查 if (!isArray(ary)) return false; //拆分 var excrete = function(_ary) { //不加中斷會(huì)一直運(yùn)行直到堆棧溢出,關(guān)鍵!! if (_ary.length <= 1) return _ary; //拆分兩個(gè)數(shù)組 var middleIndex = Math.ceil(_ary.length / 2), left = _ary.slice(0, middleIndex), right = _ary.slice(middleIndex); //合并數(shù)組 return merge(excrete(left), excrete(right)); }; //排序併合并 var merge = function(left, right) { var _ary = []; //這一步相當(dāng)關(guān)鍵!!! while (left.length > 0 && right.length > 0) { //分別比較數(shù)組首位并把較小的數(shù)值推入新數(shù)組 _ary.push((left[0] < right[0] ? left : right).shift()); } //將比較完后剩下的數(shù)組項(xiàng)組合起來 return _ary.concat(left, right); }; return excrete(ary); } useTime("歸并排序算法", excreteAndMerge); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }
運(yùn)行結(jié)果:
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 歸并排序算法耗時(shí): 13.403ms希爾排序算法
基礎(chǔ):希爾排序是基於插入排序的以下兩點(diǎn)性質(zhì)而提出改進(jìn)方法的:
插入排序在對(duì)幾乎已經(jīng)排好序的數(shù)據(jù)操作時(shí),效率高,即可以達(dá)到線性排序的效率。
但插入排序一般來說是低效的,因?yàn)椴迦肱判蛎看沃荒軐?shù)據(jù)移動(dòng)一位。
希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進(jìn)版本。希爾排序是非穩(wěn)定排序算法。該方法因 DL.Shell 於 1959 年提出而得名。
第一步拆分,固定或動(dòng)態(tài)定義一個(gè)間隔序列,最關(guān)鍵是不管序列多大,最終必須要逐個(gè)遍歷一遍收尾,(我在這步跪了一段時(shí)間才發(fā)現(xiàn));
一般的初次取序列的一半為增量,以后每次減半,直到增量為 1。
例如長(zhǎng)度 100, 跨度是 2, 初始間隔序列 = 長(zhǎng)度/跨度, 遞減量=自身/跨度即 50,25,22.....1
第二步按間隔序列排序直到逐個(gè)排序?yàn)橹?/p>
要實(shí)現(xiàn)上述規(guī)則需要用到多個(gè)循環(huán)和插入算法.
優(yōu)點(diǎn):本質(zhì)上講,希爾排序算法是直接插入排序算法的一種改進(jìn),減少了其復(fù)製的次數(shù),速度要快很多,原因是
① 當(dāng)文件初態(tài)基本有序時(shí)直接插入排序所需的比較和移動(dòng)次數(shù)均較少。
② 當(dāng) n 值較小時(shí),n 和 n2 的差別也較小,即直接插入排序的最好時(shí)間復(fù)雜度 O(n)和最壞時(shí)間復(fù)雜度 O(n2)差別不大。
③ 在希爾排序開始時(shí)增量較大,分組較多,每組的記錄數(shù)目少,故各組內(nèi)直接插入較快,后來增量逐漸縮小,分組數(shù)逐漸減少,而各組的記錄數(shù)目逐漸增多,但由於已經(jīng)按增量距離排過序,使文件較接近於有序狀態(tài),所以新的一趟排序過程也較快。
對(duì)規(guī)模非常大的數(shù)據(jù)排序不是最優(yōu)選擇
示例圖如下,假設(shè)原數(shù)組[663, 949, 916, 393, 470, 26, 649, 256, 901, 705, 519, 803, 334, 861, 83, 70, 230, 588, 9, 346],跨度為 3.
① 第一次循環(huán)增量:20/3≈7,即比較位置相差 7
第二次循環(huán)增量:7/3≈3,即比較位置相差 3
第三次循環(huán)增量:3/3=1,最終逐個(gè)遍歷一遍收尾
可以好好看看思維導(dǎo)圖,因?yàn)閱渭兯伎己萌菀酌院?
先來看看百度百科的寫法
var arr = [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312], len = arr.length; for ( var fraction = Math.floor(len / 2); fraction > 0; fraction = Math.floor(fraction / 2) ) { console.log("fraction : " + fraction); for (var i = fraction; i < len; i++) { for ( var j = i - fraction; j >= 0 && arr[j] > arr[fraction + j]; j -= fraction ) { var temp = arr[j]; arr[j] = arr[fraction + j]; arr[fraction + j] = temp; } } } console.log(arr);運(yùn)行結(jié)果
fraction : 7 fraction : 3 fraction : 1 [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ]
乍看之下好像挺好理解而且又簡(jiǎn)單沒毛病,但有個(gè)小問題,實(shí)際上這是一個(gè)只能固定增量的代碼,如果你把間隔序列換成其他,也就是 Math.floor(len / 4)這樣子,有可能排序就完蛋了.如下:
var arr = [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312], len = arr.length; for ( var fraction = Math.floor(len / 4); fraction > 0; fraction = Math.floor(fraction / 4) ) { console.log("fraction : " + fraction); for (var i = fraction; i < len; i++) { for ( var j = i - fraction; j >= 0 && arr[j] > arr[fraction + j]; j -= fraction ) { var temp = arr[j]; arr[j] = arr[fraction + j]; arr[fraction + j] = temp; } } } console.log(arr);運(yùn)行結(jié)果
fraction : 3 [ 246, 380, 312, 317, 446, 722, 403, 561, 751, 614, 663, 917, 616, 907, 970 ]
幸好我好奇心還行動(dòng)手實(shí)踐一下,不然就栽在這裡以為懂了.關(guān)鍵就是上面說的不管序列多大,最終必須要逐個(gè)遍歷一遍收尾
我繼續(xù)挖掘下網(wǎng)上的代碼,另一種基本代碼都是這麼寫的
var arr = [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312], len = arr.length, temp, fraction = 1; while (fraction < len / 5) { //動(dòng)態(tài)定義間隔序列 fraction = fraction * 5 + 1; } for (fraction; fraction > 0; fraction = Math.floor(fraction / 5)) { console.log("fraction: " + fraction); for (var i = fraction; i < len; i++) { temp = arr[i]; for (var j = i - fraction; j >= 0 && arr[j] > temp; j -= fraction) { arr[j + fraction] = arr[j]; } arr[j + fraction] = temp; } } console.log(arr);運(yùn)行結(jié)果
fraction: 6 fraction: 1 [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ]
中間動(dòng)態(tài)定義間隔序列的代碼就是解決上面說的問題
但我還不滿意,可擴(kuò)展度還不夠高,下面是我按自己理解寫的一個(gè)希爾算法,只用三個(gè)循環(huán)加個(gè)中斷條件.自測(cè)沒發(fā)現(xiàn)什麼問題,如果有錯(cuò)誤的地方麻煩指出來吧.
//希爾排序 function shellSort(ary, limit) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var len = ary.length, limit = limit || 3, //跨度 gap = Math.ceil(len / limit), //動(dòng)態(tài)間隔序列 i, j; //互換位置 var swap = function (a, b) { ary[a] = [ary[b], (ary[b] = ary[a])][0]; }; //間隔序列循環(huán)遞減 for (; gap > 0; gap = Math.ceil(gap / limit)) { for (i = gap; i < len; i++) { //間隔排序 for (j = i - gap; j >= 0 && ary[j] > ary[j + gap]; j -= gap) { swap(j, j + gap); } } //另一個(gè)關(guān)鍵地方,不加中斷條件引起死循環(huán)導(dǎo)致內(nèi)存溢出 if (gap == 1) break; } return ary; } useTime("希爾排序算法", shellSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }運(yùn)行結(jié)果
排序前: [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ] 排序后: [ 246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970 ] 希爾排序算法耗時(shí): 8.988ms計(jì)數(shù)排序演算法
基礎(chǔ):計(jì)數(shù)排序是一個(gè)非基於比較的排序算法,核心在於將輸入的數(shù)據(jù)值轉(zhuǎn)化為鍵存儲(chǔ)在額外開闢的數(shù)組空間中。作為一種線性時(shí)間復(fù)雜度的排序,計(jì)數(shù)排序要求輸入的數(shù)據(jù)必須是有確定范圍的整數(shù)。計(jì)數(shù)排序?qū)斎氲臄?shù)據(jù)有附加的限制條件:
輸入的線性表的元素屬於有限偏序集 S;
設(shè)輸入的線性表的長(zhǎng)度為 n,|S|=k(表示集合 S 中元素的總數(shù)目為 k),則 k=O(n)。
在這兩個(gè)條件下,計(jì)數(shù)排序的復(fù)雜性為 O(n)。
統(tǒng)計(jì)數(shù)組統(tǒng)計(jì)每個(gè)值出現(xiàn)次數(shù)和找出其中最大最小值
統(tǒng)計(jì)數(shù)組從 min 位置開始對(duì)所有計(jì)數(shù)開始累加(當(dāng)前項(xiàng)和前一項(xiàng)相加)直到最大值位置為止
根據(jù)統(tǒng)計(jì)數(shù)組的值大小可以知道排列順序,然后反向填充目標(biāo)數(shù)組
優(yōu)點(diǎn):① 在對(duì)一定范圍內(nèi)的整數(shù)排序時(shí),它的復(fù)雜度為 Ο(n+k)(其中 k 是整數(shù)的范圍),快於任何比較排序算法。
缺點(diǎn):① 犧牲空間換取時(shí)間,當(dāng) O(k)>O(nlog(n))的時(shí)候其效率反而不如基於比較的排序(基於比較的排序的時(shí)間復(fù)雜度在理論上的下限是 O(nlog(n)), 如歸併排序,堆排序)
詳細(xì)分析如下,假設(shè)原數(shù)組[27, 24, 23, 27, 21]
找到最小值21,最大值27
然后統(tǒng)計(jì)數(shù)組統(tǒng)計(jì)每個(gè)值出現(xiàn)次數(shù)后 Count [21: 1, 23: 1, 24: 1, 27: 2]
從最小值到最大值之間每個(gè)位置都填滿后 Count [21: 1, 22: 1, 23: 2, 24: 3, 25: 3, 26: 3, 27: 5]
反向填充目標(biāo)數(shù)組: [21, 23, 24, 27, 27]
② 不適用數(shù)值相差范圍太大的排序,例如[1, 2, 1000],因?yàn)榻y(tǒng)計(jì)數(shù)組會(huì)從0開始累加直到1000, 即中間太多冗余填充數(shù)據(jù)了.
(這裡只會(huì)打印出值,不會(huì)打印相對(duì)應(yīng)位置,但這是整個(gè)算法的關(guān)鍵,所以建議拷貝到本地去運(yùn)行.)
因?yàn)樗惴ǖ奶厥庑晕覀儞Q一組相差范圍比較小的亂序數(shù)組做測(cè)試.
//計(jì)數(shù)排序 function countingSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var len = ary.length, Result = [], //排序數(shù)組 Count = [], //統(tǒng)計(jì)數(shù)組 min = (max = ary[0]), i = 0, j, k; //在Count里統(tǒng)計(jì)數(shù)據(jù),并且找出最大值最小值 for (; i < len; i++) { //切記不能用一個(gè)變量保存Count[ary[i]]??!,詳情http://www.qdfuns.com/notes/40831/dd2b82537d74065b8a53b75e2eb85715.html Count[ary[i]]++ || (Count[ary[i]] = 1); //可能有重復(fù)數(shù)值 min > ary[i] && (min = ary[i]); max < ary[i] && (max = ary[i]); } console.log("第一個(gè)循環(huán)后的Count: ", Count); //從最小值到最大值之間每個(gè)位置都填滿(這步相當(dāng)關(guān)鍵) for (j = min; j < max; j++) { //讓當(dāng)前項(xiàng)數(shù)值比前一項(xiàng)數(shù)值大,后面將根據(jù)這個(gè)數(shù)值確定順序位置 Count[j + 1] = (Count[j + 1] || 0) + (Count[j] || 0); } console.log("第二個(gè)循環(huán)后的Count: ", Count); //算法精華,反向填充數(shù)組!! for (k = len - 1; k >= 0; k--) { console.log("位置: ", Count[ary[k]] - 1, "值: ", ary[k]) //Result[位置] = ary值,-1是因?yàn)樾聰?shù)組該從0位置開始排序 Result[Count[ary[k]] - 1] = ary[k]; //減少Count數(shù)組中保存的計(jì)數(shù),注釋掉的話當(dāng)數(shù)組存在重復(fù)數(shù)值時(shí)會(huì)跳過后續(xù)重復(fù)數(shù)值排序 Count[ary[k]]--; } return Result; } useTime("計(jì)數(shù)排序算法", countingSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [18, 19, 5, 15, 20, 2, 17, 13, 8, 6, 7, 16, 3, 10, 4]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }運(yùn)行結(jié)果
排序前: (15) [18, 19, 5, 15, 20, 2, 17, 13, 8, 6, 7, 16, 3, 10, 4] 第一個(gè)循環(huán)后的Count: (21) [undefined, undefined, 1, 1, 1, 1, 1, 1, 1, undefined, 1, undefined, undefined, 1, undefined, 1, 1, 1, 1, 1, 1] 第二個(gè)循環(huán)后的Count: (21) [undefined, undefined, 1, 2, 3, 4, 5, 6, 7, 7, 8, 8, 8, 9, 9, 10, 11, 12, 13, 14, 15] 位置: 2 值: 4 位置: 7 值: 10 位置: 1 值: 3 位置: 10 值: 16 位置: 5 值: 7 位置: 4 值: 6 位置: 6 值: 8 位置: 8 值: 13 位置: 11 值: 17 位置: 0 值: 2 位置: 14 值: 20 位置: 9 值: 15 位置: 3 值: 5 位置: 13 值: 19 位置: 12 值: 18 排序后: (15) [2, 3, 4, 5, 6, 7, 8, 10, 13, 15, 16, 17, 18, 19, 20] 計(jì)數(shù)排序算法耗時(shí): 14.284999999999997ms桶排序演算法
基礎(chǔ):桶排序是計(jì)數(shù)排序的升級(jí)版。它利用了函數(shù)的映射關(guān)係,高效與否的關(guān)鍵就在於這個(gè)映射函數(shù)的確定。將數(shù)組分到有限數(shù)量的桶子里。每個(gè)桶子再個(gè)別排序(有可能再使用別的排序算法或是以遞歸方式繼續(xù)使用桶排序進(jìn)行排序)。
在額外空間充足的情況下,合理規(guī)劃桶的數(shù)量
使用的映射函數(shù)能夠?qū)⑤斎氲?N 個(gè)數(shù)據(jù)合理的分配到相應(yīng)桶中
思路:找出最大值最小值
求出每一個(gè)桶的數(shù)值范圍
將數(shù)值裝入相應(yīng)桶中并排序
開始合并數(shù)組
優(yōu)點(diǎn):① 時(shí)間復(fù)雜度是 O(M+N),據(jù)說是最快最簡(jiǎn)單的排序
缺點(diǎn):① 如果輸入數(shù)據(jù)非常龐大,而桶的數(shù)量也非常多,則空間代價(jià)是昂貴的,或者數(shù)據(jù)長(zhǎng)度相差過大如[1,255,366,120,156],效率反而變低
示例圖如下,假設(shè)初始數(shù)組[17, 42, 45, 44, 30, 21, 22, 1, 38, 33, 36, 39, 47, 5, 50, 31, 34, 27, 29, 11],桶為 bucketCount(5)個(gè).
第一步: 找到最小值 1, 最大值 50
第二步: 每一個(gè)桶的數(shù)值范圍 Math.floor((max - min) / bucketCount) + 1 = 10
第三步: 將數(shù)值裝入相應(yīng)桶中并排序
第四步: 合并數(shù)組["1", "5", "11", "17", "21", "22", "27", "29", "30", "31", "33", "34", "36", "38", "39", "42", "44", "45", "47", "50"]
//桶排序 function bucketSort(ary, bucketCount) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; //初始化變量參數(shù) bucketCount = bucketCount || 5; var len = ary.length, buckets = [], //桶數(shù)組 max = min = ary[0], //默認(rèn)取出首位 space, //數(shù)值范圍 i; //找出最大值最小值 for (i = 1; i < len; i++) { min > ary[i] && (min = ary[i]); max < ary[i] && (max = ary[i]); } //求出每一個(gè)桶的數(shù)值范圍(向下取值并且加一),范圍取值(min -> >=max) space = Math.floor((max - min) / bucketCount) + 1; //將數(shù)值裝入桶中 for (i = 0; i < len; i++) { var item = ary[i], //值 index = Math.floor((item - min) / space), //難點(diǎn)一,找到相應(yīng)的桶序列 bucket = buckets[index]; //桶序列數(shù)組 //判斷是否有對(duì)應(yīng)桶 if (bucket) { //因?yàn)閿?shù)組從小到大排列 var bucketLen = bucket.length - 1; //難點(diǎn)二,逆向比較大小,符合條件數(shù)值后移一位,k減一 while (bucketLen >= 0 && bucket[bucketLen] > item) { bucket[bucketLen + 1] = bucket[bucketLen]; bucketLen--; } //對(duì)應(yīng)位置插入數(shù)組 bucket[bucketLen + 1] = item; } else { //新增數(shù)值入桶(這裡不能引用變量bucket,詳情http://www.qdfuns.com/notes/40831/dd2b82537d74065b8a53b75e2eb85715.html) buckets[index] = [item]; } } //開始合并數(shù)組 // 方法一,返回?cái)?shù)字格式數(shù)組,但是步驟性能都較差 /* var n = 0, result = []; while(n < bucketCount) { //中間可能有沒有符合范圍的斷層數(shù)組 if(buckets[n]) result = result.concat(buckets[n]); n++; } return result */ //方法二,返回字符串格式數(shù)組,簡(jiǎn)單方便(平均時(shí)間快幾毫秒級(jí)別) return buckets.join(",").split(","); } useTime("桶排序算法", bucketSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }運(yùn)行結(jié)果
排序前: (15) [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312] 排序后: (15) [246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970] 桶排序算法耗時(shí): 2.3799999999999955ms基數(shù)排序演算法
基礎(chǔ):將所有待比較數(shù)值(正整數(shù))統(tǒng)一為同樣的數(shù)位長(zhǎng)度,數(shù)位較短的數(shù)前面補(bǔ)零。然后,從最低位開始,依次進(jìn)行一次排序。這樣從最低位排序一直到最高位排序完成以后, 數(shù)列就變成一個(gè)有序序列。
思路:找出最大數(shù)值的單位長(zhǎng)度(我網(wǎng)上看到的別人寫法都是沒有這一步,而是已知單位長(zhǎng)度然后傳參進(jìn)去,我改為自動(dòng)判斷了)
根據(jù)排序方式 LSD/MSD,從低位數(shù)或者高位數(shù)為基層開始分配,
直到所有位數(shù)都分配完之后合并數(shù)組
LSD(Least significant digital):由鍵值的最右邊開始,適用於位數(shù)小的數(shù)列,由低位數(shù)為基底開始進(jìn)行分配,但在分配之后并不馬上合并回一個(gè)數(shù)組中,而是在每個(gè)“桶子”中建立“子桶”,將每個(gè)桶子中的數(shù)值按照上一數(shù)位的值分配到“子桶”中。在進(jìn)行完最高位數(shù)的分配后再合并回單一的數(shù)組中。
MSD(Most significant digital):由鍵值的最左邊開始,適用於位數(shù)大的數(shù)列,由高位數(shù)為基底開始進(jìn)行分配,但在分配之后并不馬上合并回一個(gè)數(shù)組中,而是在每個(gè)“桶子”中建立“子桶”,將每個(gè)桶子中的數(shù)值按照下一數(shù)位的值分配到“子桶”中。在進(jìn)行完最低位數(shù)的分配后再合并回單一的數(shù)組中。
① 穩(wěn)定排序;時(shí)間復(fù)雜度可以突破 O(n)
缺點(diǎn):① 只適用於有基數(shù)的情況,比如非數(shù)字字符串排序就沒法做了,適用范圍太窄,內(nèi)存佔(zhàn)用太大。
示例圖如下,假設(shè)初始數(shù)組[537, 674, 474, 21, 60, 692, 728, 911, 322, 949]
按個(gè)位數(shù)排序
得出數(shù)組[60, 21, 911, 692, 322, 674, 474, 537, 728, 949]
按十位數(shù)排序
得出數(shù)組[911, 21, 322, 728, 537, 949, 60, 674, 474, 692]
按百位數(shù)排序
得出數(shù)組[21, 60, 322, 474, 537, 674, 692, 728, 911, 949]
//基數(shù)排序 function radixSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; var maxLen = (Math.max.apply(null, ary) + "").length, //找出其中的最大值單位長(zhǎng)度 len = ary.length, // 數(shù)組長(zhǎng)度 counter = [], //基數(shù)數(shù)組 mod = 10, //餘數(shù)單位 dev = 1, //除數(shù)單位 i = 0, j, index, //序列值 val; //遍歷數(shù)值單位位數(shù) for (; i < maxLen; i++, dev *= 10, mod *= 10) { //遍歷數(shù)組長(zhǎng)度 for (j = 0; j < len; j++) { //難點(diǎn)一,得出數(shù)值指定單位的數(shù)字,從個(gè)位數(shù)開始比較,缺省數(shù)值補(bǔ)零,如(561 => 1, 6, 5, 0) index = parseInt((ary[j] % mod) / dev); //將數(shù)值放到對(duì)應(yīng)的指定單位序列桶 !counter[index] && (counter[index] = []); // //將數(shù)值放到對(duì)應(yīng)的指定單位序列桶 counter[index].push(ary[j]); } //統(tǒng)計(jì)數(shù)組長(zhǎng)度,以最大長(zhǎng)度為準(zhǔn),即使中間有空數(shù)組. var counterLen = counter.length, // 統(tǒng)計(jì)數(shù)組長(zhǎng)度 pos = 0; //定位 //遍歷桶數(shù)組,修改原數(shù)組順序 for (j = 0; j < counterLen; j++) { //過濾空數(shù)組 if (counter[j]) { while (val = counter[j].shift()) { //用pos而不直接用j是因?yàn)閏ounter中間可能有斷層 //修改原數(shù)組而不使用新數(shù)組是因?yàn)樵撗h(huán)多次執(zhí)行,并不是一次完成排序 ary[pos++] = val; } } } } return ary; } useTime("基數(shù)排序算法", radixSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }運(yùn)行結(jié)果
排序前: (15) [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312] 排序后: (15) [907, 380, 722, 403, 614, 616, 317, 907, 970, 614, 446, 917, 403, 663, 312] 基數(shù)排序算法耗時(shí): 12.169999999999987ms十,堆排序演算法 基礎(chǔ):
因?yàn)?js 模擬二叉樹比較麻煩,所以堆排序的優(yōu)勢(shì)用 js 語言無法體現(xiàn), 相對(duì)而言 C 語言的鏈表在實(shí)現(xiàn)上更能表現(xiàn)堆排序,堆排序或許更適合指針類的計(jì)算機(jī)語言。(二叉樹算法)
二叉樹的每個(gè)結(jié)點(diǎn)至多只有二棵子樹
二叉樹的第 i 層至多有 2^(i ? 1)個(gè)結(jié)點(diǎn)
深度為 k 的二叉樹至多有 2^k ? 1 個(gè)結(jié)點(diǎn)
堆排序的時(shí)間,主要由建立初始堆和反覆重建堆這兩部分的時(shí)間開銷構(gòu)成
思路:堆排序就是把最大堆堆頂?shù)淖畲髷?shù)取出,將剩餘的堆繼續(xù)調(diào)整為最大堆,再次將堆頂?shù)淖畲髷?shù)取出,這個(gè)過程持續(xù)到剩餘數(shù)只有一個(gè)時(shí)結(jié)束。在堆中定義以下幾種操作:
最大堆調(diào)整(Max-Heapify):將堆的末端子節(jié)點(diǎn)作調(diào)整,使得子節(jié)點(diǎn)永遠(yuǎn)小於父節(jié)點(diǎn)
創(chuàng)建最大堆(Build-Max-Heap):將堆所有數(shù)據(jù)重新排序,使其成為最大堆
堆排序(Heap-Sort):移除位在第一個(gè)數(shù)據(jù)的根節(jié)點(diǎn),并做最大堆調(diào)整的遞歸運(yùn)算
優(yōu)點(diǎn):① 對(duì)於較大的序列,將表現(xiàn)出優(yōu)越的性能
缺點(diǎn):① 由於建初始堆所需的比較次數(shù)較多,所以堆排序不適宜於記錄數(shù)較少的文件。堆排序是穩(wěn)定時(shí)間的,堆排序的排序時(shí)間與數(shù)據(jù)無關(guān)意味著同樣大小的數(shù)據(jù),可能已經(jīng)排好序的,堆排序仍然需要花上同樣的時(shí)間去排序,內(nèi)存佔(zhàn)用是快排的兩倍
示例圖如下,假設(shè)初始數(shù)組[31, 16, 73, 42, 51, 28, 71, 80, 61, 39, 60, 10, 85, 51, 21],二叉樹首次遍歷排序從最后的父結(jié)點(diǎn)開始
首次遍歷完成之后,交換首尾位置,然后尾數(shù)不參與下次排序,然后從堆頭開始遍歷排序,直到所有排序完成結(jié)束
可以好好看看思維導(dǎo)圖,因?yàn)閱渭兯伎己萌菀酌院?
//堆算法 function maxHeapSort(ary) { //類型檢查 if (!isArray(ary)) return false; if (ary.length <= 1) return ary; //互換位置 var swap = function (a, b) { ary[a] = [ary[b], (ary[b] = ary[a])][0]; }; //尋找父結(jié)點(diǎn) function buildMaxHeap(len) { var i = Math.floor(len / 2) - 1; //難點(diǎn)一,最后一個(gè)父尾結(jié)點(diǎn) //遍歷父結(jié)點(diǎn),將堆所有數(shù)據(jù)逐層排序,找出最大結(jié)點(diǎn) for (; i >= 0; i--) { maxHeapify(i, len); } } //將堆的末端子節(jié)點(diǎn)作調(diào)整,使得子節(jié)點(diǎn)永遠(yuǎn)小於父節(jié)點(diǎn) function maxHeapify(_index, _len) { var left, right, max = _index; //可以寫成遞歸,可讀性強(qiáng)一些,但是我覺得遍歷效率會(huì)比較好一些. while (true) { //分別為左子樹結(jié)點(diǎn),右子樹結(jié)點(diǎn),父結(jié)點(diǎn)的位置 left = 2 * _index + 1; right = 2 * _index + 2; max = _index; //分別比較,父結(jié)點(diǎn)必須比子樹結(jié)點(diǎn)都大,不然換位置 if (left < _len && ary[left] > ary[max]) max = left; if (right < _len && ary[right] > ary[max]) max = right; if (max != _index) { //順序不對(duì),排序 swap(_index, max); //替換節(jié)點(diǎn)位置繼續(xù)執(zhí)行 _index = max; } else { break; } } } //移除位在第一個(gè)數(shù)據(jù)的根節(jié)點(diǎn),并做最大堆調(diào)整的遞歸運(yùn)算 function _sort() { //首次排序之后得出第一個(gè)最大的數(shù)值也就是首結(jié)點(diǎn), var len = ary.length, i = len - 1; //尾部位置 //初始運(yùn)行代碼,第一輪遍歷排序,先找出最大節(jié)點(diǎn)數(shù)提升到堆頭 buildMaxHeap(len); for (; i > 0; i--) { //首尾數(shù)據(jù)互換,原本的尾數(shù)被提到首位進(jìn)行排序(排序的尾數(shù),不是完整數(shù)組的尾數(shù)) swap(0, i); //忽略尾部正常順序數(shù)據(jù),繼續(xù)排序 maxHeapify(0, i); } return ary; } return _sort() } useTime("堆算法算法", maxHeapSort); //是否數(shù)組 function isArray(obj) { var bol = Object.prototype.toString.call(obj) === "[object Array]"; !bol && alert("當(dāng)前入?yún)⒎菙?shù)組類型!"); return bol; } //計(jì)時(shí)小玩意 function useTime(name, fn) { var ary = [ 616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312 ]; console.time(name + "耗時(shí)"); console.log("排序前: ", ary); console.log("排序后: ", fn(ary)); console.timeEnd(name + "耗時(shí)"); }運(yùn)行結(jié)果
排序前: (15) [616, 380, 751, 317, 561, 722, 246, 907, 970, 614, 446, 917, 403, 663, 312] 排序后: (15) [246, 312, 317, 380, 403, 446, 561, 614, 616, 663, 722, 751, 907, 917, 970] 堆算法算法耗時(shí): 3.914999999999992ms
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106439.html
摘要:我們得從原因理解起。這個(gè)編碼位置是唯一的。為了確保其組織性,把這個(gè)範(fàn)圍的編碼區(qū)分成個(gè)區(qū)段,各自由個(gè)編碼組成。由進(jìn)制格式的個(gè)位元組成代表一個(gè)編碼位置。跳脫序列可以被用來表示編碼位置從到。 為了理解 ES6 到底對(duì)於 Unicode 萬國碼有哪些新的支援。我們得從原因理解起。 Javascript 在處理 Unicode 時(shí)很有多問題 關(guān)於 Javascript 處理 Unicode 的方...
摘要:本文愿從彈幕設(shè)計(jì)這個(gè)場(chǎng)景來描述算法在前端中的應(yīng)用,我們先來看下實(shí)現(xiàn)效果圖彈幕效果開場(chǎng)之前我們先來描述彈幕開發(fā)的難度,再集中精力描述算法設(shè)計(jì)的思路。軌道軌道這個(gè)角色很重要,它可以解決彈幕位置計(jì)算速度控制碰撞檢測(cè)問題。 大家都說前端寫頁面較多,幾乎用不到算法。本文愿從彈幕設(shè)計(jì)這個(gè)場(chǎng)景來描述算法在前端中的應(yīng)用,我們先來看下實(shí)現(xiàn)效果: showImg(https://segmentfault....
摘要:每個(gè)列表中的數(shù)據(jù)項(xiàng)稱為元素。棧被稱為一種后入先出,的數(shù)據(jù)結(jié)構(gòu)。散列使用的數(shù)據(jù)結(jié)構(gòu)叫做散列表。不包含任何成員的集合稱為空集,全集則是包含一切可能成員的集合。因此二叉搜索樹需要平衡,即左右子樹高度要相近。 樓樓非計(jì)算機(jī)專業(yè),但是對(duì)計(jì)算機(jī)也還算喜歡。個(gè)人理解若有偏差,歡迎各位批評(píng)指正! 對(duì)于數(shù)據(jù)結(jié)構(gòu)和算法一直是我的薄弱環(huán)節(jié),相信大多數(shù)前端工程師可能多少會(huì)有些這方面的弱點(diǎn),加上數(shù)據(jù)結(jié)構(gòu)和算法本...
閱讀 682·2021-11-25 09:43
閱讀 1688·2021-11-18 10:02
閱讀 1067·2021-10-15 09:39
閱讀 1918·2021-10-12 10:18
閱讀 2151·2021-09-22 15:43
閱讀 795·2021-09-22 15:10
閱讀 2110·2019-08-30 15:53
閱讀 1011·2019-08-30 13:00