標籤:ice 計算 分而治之 comment data 數組元素 com param uil
? 此生之路,我將走過;走過這一次,便再也無法重來。所有力所能及的善行,所有充盈於心的善意,我將毫不吝惜,即刻傾予。我將再不拖延,再不淡漠,只因此生之路,再也無法重來。
對電腦中儲存的資料執行的兩種最常見操作是排序和索引。下述闡述的排序方式,暫且都是用數組進行測試(從小到大)。
var dataAry = [5, 4, 3, 7, 1, 2, 8, 6, 9]; // 測試數組
/** *【工具方法】交換數組中兩個值 * @param ary 數組 * @param i 下標i * @param j 下標j */function swap(ary, i, j) { var temp = ary[i]; ary[i] = ary[j]; ary[j] = temp;}
冒泡排序
? 之所以稱為冒泡排序是因為使用這種排序演算法時,資料值會像氣泡一樣從數組的一端漂浮到另一端。如,從小到大排序:其會比較相鄰的資料,當左側值大於右側值時將它們進行交換。
冒泡排序演算法的運作如下:(從小到大)
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
function bubbleSort(dataAry) { var len = dataAry.length; for(var i = 0; i < len; i++) { for(var j = 0; j < len - i; j++) { if(dataAry[j] > dataAry[j+1]) { swap(dataAry, j, j+1); } } } return dataAry;}// 測試console.log(bubbleSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
選擇排序
? 從數組的第一個資料開始,將第一個資料和其他資料進行比較。它的工作原理是每一次從待排序的資料中選出最小(或最大)的一個資料,存放在序列的起始位置,直到全部待排序的資料元素排完。
function selectionSort(dataAry) { var len = dataAry.length; for(var i = 0; i < len - 1; i++) { for(var j = i + 1; j < len; j++) { if(dataAry[i] > dataAry[j]) { swap(dataAry, i , j); } } } return dataAry;}// 測試console.log(selectionSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
插入排序
? 插入排序有兩個迴圈,外迴圈將數組元素挨個移動,而內迴圈則對外迴圈中選定的元素及它後面的那個元素比較。如果外迴圈中選中元素小,那麼數組元素會向右移動,為內迴圈中的這個元素騰出位置。每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止。
直接插入排序演算法的說明<有1-9張標有是數位卡片>:
1. 取出第一張卡片直接放到桌子最左側
2. 取出第二張卡片,如果該卡片標註的數字小於第一張,則將第一張卡片向右移動一格;否則放到右側;
3. 取出第n張卡片,將該卡片與第n-1、n-2···第1張對比,小於等於則右移繼續對比,大於則停止對比將該值插入對應位置。
function insertionSort(dataAry) { var temp, inner; // 外迴圈(跳過第一個值,因為第一個直接放到最左側) for(var outer = 1, len = dataAry.length; outer < len; outer++) { temp = dataAry[outer]; inner = outer; // 內迴圈,比左側值小就移動讓位 while(inner > 0 && (dataAry[inner - 1] >= temp)) { dataAry[inner] = dataAry[inner - 1]; inner--; } dataAry[inner] = temp; } return dataAry}// 測試console.log(insertionSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
希爾排序
? 該演算法在插入排序的基礎上做了相應改善。其需要預先(或動態)定義一個間隔序列來表示在排序過程中進行比較的元素之間的間隔。對被間隔的每組元素使用直接插入排序演算法排序;隨著間隔序列中的數逐漸減小,每組包含的元素越來越多,當間隔減至1時,整個檔案恰被分成一組,演算法便終止。這確保了在開始最後一次處理時,大部分元素都已在正確位置,必須再進行多次資料交換,這就是希爾排序比插入排序更高效的地方。
希爾排序演算法說明:
1. 先取一個小於n的整數d1作為第一個間隔,把檔案的全部記錄分組。所有距離為d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;
2. 取第二個間隔值d2<d1重複上述的分組和排序;
3. 直至所取的間隔為1,即所有記錄放在同一組中進行直接插入排序為止。
/** * 希爾排序 * @param dataAry 資料數組 * @param gaps 間隔數組 */function shellSort(dataAry, gaps) { var temp, inner, currentGap; // 遍曆間隔 for(var g = 0, gLen = gaps.length; g < gLen; g++) { // 當前間隔 currentGap = gaps[g]; // 直接插入法外迴圈 for(var outer = currentGap, len = dataAry.length; outer < len; outer++) { temp = dataAry[outer]; inner = outer; // 直接插入法內迴圈(注意對比間隔值) while(inner >= currentGap && (dataAry[inner - currentGap] >= temp)) { dataAry[inner] = dataAry[inner - currentGap]; inner = inner - currentGap; } dataAry[inner] = temp; } } return dataAry;}// 測試console.log(shellSort(dataAry, [3, 2, 1])); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
歸併排序
把一系列排好序的子序列合并成一個大的完整有序序列。歸併排序通常使用遞迴來實現。
自頂向下的歸併排序(遞迴)
function mergeSort(dataAry) { if(dataAry.length === 1) { return dataAry; } var mid = Math.floor(dataAry.length/2); var leftAry = dataAry.slice(0, mid); var rightAry = dataAry.slice(mid); return merge(mergeSort(leftAry), mergeSort(rightAry));}/** * 合并數組 * @param dataAry */function merge(leftAry, rightAry) { var result = []; while(leftAry.length > 0 && rightAry.length > 0) { if(leftAry[0] < rightAry[0]) { result.push(leftAry.shift()); }else { result.push(rightAry.shift()); } } return result.concat(leftAry).concat(rightAry);}// 測試console.log(mergeSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
? 上述mergeSort()被執行了2n-1次(上述被執行了17次),一旦數組元素個數增多,採用遞迴的方式將不再可取,元素個數超過1500在Firfox下就會造成棧溢出!
自底向上的歸併排序(迭代)
? 首先將資料集分解為一組只有一個元素的數組,然後通過建立一組左右數組將其合并,每次合并確保資料已排好序,直到最後所有資料排序完成。
function mergeSort(dataAry) { if(dataAry.length < 2) { return dataAry; } var step = 1; // 控制子序列大小 var left, right; // 左、右下標 while(step < dataAry.length) { left = 0; right = step; while((right + step) <= dataAry.length) { mergeArrays(dataAry, left, left+step, right, right+step); left = right + step; right = left + step; } // 不能被分組的元素 if(right < dataAry.length) { mergeArrays(dataAry, left, left+step, right, dataAry.length); } step = step * 2; } return dataAry;}/** * 合并數組<包含頭,不包含尾> * @param ary * @param startLeft * @param endLeft * @param startRight * @param endRight */function mergeArrays(ary, startLeft, endLeft, startRight, endRight) { var leftAry = new Array(endLeft - startLeft + 1), rightAry = new Array(endRight - startRight + 1); var k = startLeft; for(var i = 0; i < (leftAry.length - 1); i++) { leftAry[i] = ary[k]; k++; } leftAry[leftAry.length - 1] = Infinity; // 哨兵值 k = startRight; for(var j = 0; j < (rightAry.length - 1); j++) { rightAry[j] = ary[k]; k++; } rightAry[rightAry.length - 1] = Infinity; // 哨兵值 // 對比左右元素大小 var m = 0, n = 0; for(var k = startLeft; k < endRight; k++) { if(leftAry[m] <= rightAry[n]) { ary[k] = leftAry[m]; m++; }else { ary[k] = rightAry[n]; n++; } }}// 測試console.log(quickSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
快速排序
? 快速排序是處理大資料集最快的排序演算法之一。其核心思想為“分而治之”。設立基值,通過遞迴的方式將資料一次分解為包含較小元素和較大元素的不同子序列,然後不斷重複,直到所有資料變為有序。
快速排序演算法說明:
1. 選擇一個基準元素,將列表分隔為兩個子序列;
2. 將所有小於基準元素的資料放在基準值的左側,大於基準值元素的資料放在基準值右側;
3. 分別對較小元素的子序列和較大元素的子序列重複步驟1和2。
快速排序<方式一>:從左側起開始迴圈數組與基值進行對比
function quickSort(dataAry) { if(dataAry.length === 0) { return []; } var left = [], right = []; var pivot = dataAry[0]; for(var i = 1; i < dataAry.length; i++) { dataAry[i] < pivot ? left.push(dataAry[i]) : right.push(dataAry[i]); } return quickSort(left).concat(pivot, quickSort(right));}// 測試console.log(quickSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
快速排序<方式二>:從左、右兩側一起移動,與基值進行對比
function quickSort(arr, left, right) { if (left > right) return; var temp, i = left, j = right; // 設基準值 var pivot = arr[left]; while (i != j) { // 先從右側開始(找到小於基值的第一個資料) while (i < j && arr[j] >= pivot) { j--; } // 然後從左側開始(找到大於基值的第一個資料) while (i < j && arr[i] <= pivot) { i++; } // 交換資料上述兩個元素,繼續尋找,直到i和j相遇 if (i < j) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 將基值與相遇資料進行交換<確保了基值位於元素中央位置> temp = arr[i]; arr[i] = arr[left]; arr[left] = temp; // 基值左側數組按上述方式遞迴執行 quickSort(arr, left, i - 1); // 基值右側數組按上述方式遞迴執行 quickSort(arr, i + 1, right); return arr;}// 測試console.log(quickSort(dataAry, 0, dataAry.length - 1)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
總結
冒泡排序、選擇排序、插入排序為基本排序演算法,希爾排序、歸併排序(迭代)、快速排序為進階排序演算法:
| 排序演算法 |
100條所耗時間 |
10000條所耗時間 |
100000條所耗時間 |
| 冒泡排序 |
16毫秒 |
584毫秒 |
54619毫秒 |
| 選擇排序 |
<1毫秒 |
183毫秒 |
18175毫秒 |
| 插入排序 |
<1毫秒 |
27毫秒 |
2660毫秒 |
| 希爾排序 |
<1毫秒 |
13毫秒 |
1384毫秒 |
| 歸併排序(迭代) |
<1毫秒 |
6毫秒 |
40毫秒 |
| 快速排序(方式一) |
<1毫秒 |
17毫秒 |
98毫秒 |
| 快速排序(方式二) |
<1毫秒 |
3毫秒 |
13毫秒 |
JavaScript演算法-排序演算法