如果出現錯誤,請在評論中指出,我也好自己糾正自己的錯誤
author: thomaszhou
排序
先定義一些變數和方法
function ArrayList() {
let arr = [];
this.insert = function(item) { // 插入
arr.push(item);
};
this.toString = function() { // 字串形式顯示數值
return arr.join();
};
複製程式碼
很多排序演算法用到兩個值的交換功能,所以我抽象出一個函式:
this.swap = function(i, j, arr) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
複製程式碼
直接插入排序
這個方法是排序中最簡答的方法
- 思想:依次將待排序序列中的每一個值插入到一個已經排好序的序列中,直到全部記錄都排好序
- 需要解決的問題:
- 如何構造初始的有序佇列?
- 如何查詢待插入記錄的插入位置?
- 思路:如圖中第四趟排序結果,從第三趟的結果開始,我們要插入值6,然後我們只跟當前要插入的值(值為6)的前一個值比較(如果當前是arr[i],那前一個就是arr[i-1])來比較,( 因為看到的用[ ]包裹的是排好序的序列,最右邊是其中最大的值 ),我們將當前要排序的值存到變數current當中,如果current值比arr[i-1]小,那就繼續向前移動,繼續比較current和arr[i-2],依次類推,並且每次移動,都將值向後移(20移到之前6的位置,15移到之前20的位置,程式碼是arr[index + 1] = arr[index])。當找到current > arr[i-n]的時候,將current賦值給arr[i-n+1].
// 設定arr = [12, 15, 9, 20, 6, 31, 24]
this.InsertSort = function() {
let len = arr.length,
current,
index;
for (let i = 1; i < len; i++) {
current = arr[i]; // current儲存的是當前要插入的值
for ( index = i - 1; arr[index] > current; index--) {
// index是有序序列的最後一個值,即current的前一個值
arr[index + 1] = arr[index];
}
arr[index + 1] = current;
}
};
複製程式碼
let arraylist = new ArrayList();
arraylist.InsertSort();
console.log(`InsertSort陣列是:${arraylist.toString()}`); // InsertSort陣列是:6,9,12,15,20,24,31
複製程式碼
簡單選擇排序
主要思想:每趟排序在當前待排序序列中選出最小值,和下標為i的值交換(i 初始為0,每躺排序結束後 i++)
從演算法邏輯上看, 選擇排序是一種簡單且直觀的排序演算法. 它也是兩層迴圈. 內層迴圈每執行一遍, 將選出本次待排序的元素中最小(或最大)的一個, 存放在陣列位置i. 外層迴圈就是設定迴圈結束條件(i < n)。
選擇排序每次交換的元素都有可能不是相鄰的, 因此它有可能打破原來值為相同的元素之間的順序. 比如陣列[2,2,1,3], 正向排序時, 第一個數字2將與數字1交換, 那麼兩個數字2之間的順序將和原來的順序不一致, 雖然它們的值相同, 但它們相對的順序卻發生了變化. 我們將這種現象稱作 不穩定性 .
this.SlectSort = function() {
let len = arr.length;
for (let i = 0; i < len; i++) {
let index = i;
for (let j = i; j < len; j++) {
if (arr[j] < arr[index]) { // 找最小值/或者最大值(< 是升序 > 是降序)
index = j;
}
}
// exchange
index !== i && this.swap(i,index,arr);
}
};
複製程式碼
快速排序
快排被人們認為是最快的,所以呢,面試題中也會經常考。它將陣列拆分為兩個子陣列(也就是一次劃分操作), 其中一個子陣列的所有元素都比另一個子陣列的元素小, 然後對這兩個子陣列再重複進行上述操作(遞迴下去,不斷劃分), 直到陣列不可拆分, 排序完成.
如圖是快排的一次h劃分的過程;
虛擬碼表示一次劃分:
- 將i和j分別指向待劃分割槽間的最左側和最右側記錄的位置:
- 重複下述過程,直到i = j
- 右側掃描,直到下標 j 的值小於下標 i 的值
- 將r[j]與r[i]交換,並執行i++
- 左側掃描,直到下標 i 的值大於下標j 的值
- 將r[i]與r[j]交換,並執行j--
- 退出迴圈,說明i和j指向了軸值記錄的所在位置,返回該位置
//呼叫函式:arraylist.Partition(1, 7);但是陣列下標是0-6
this.Partition = function(first, end) { // 如果提取出來會傳遞一個arr陣列,本例是直接呼叫全域性的arr陣列
let i = first - 1, // arr下標是(first-1, end-1)
j = end - 1;
while (i < j) {
while (i < j && arr[i] <= arr[j]) { j--; }
if (i < j) {
this.swap(i, j, arr); // 交換
i++;
}
while(i < j && arr[i] <= arr[j]) { i++; }
if (i < j) {
this.swap(i, j, arr);
j--;
}
}
return i+1;
};
複製程式碼
如圖,一次劃分後,我們可以得到[19, 13, 6]和[31, 49 ,28],然後再對這兩個子區間進行劃分,依次類推,此處需要遞迴
this.QuickSort = function(first, end) {// 如果提取出來會傳遞一個arr陣列,本例是直接呼叫全域性的arr陣列
if (first < end) {
let pivot = this.Partition(first, end);
this.QuickSort(first,pivot - 1); // 前半部分
this.QuickSort(pivot + 1, end); // 後半部分
}
};
複製程式碼
驗證
console.log(arraylist.Partition(1, 7));
console.log(`第一個劃分的結果${arraylist.toString()}`); // 第一個劃分的結果19,13,6,23,31,49,28
arraylist.QuickSort(1, 7);
console.log(`最終結果是:${arraylist.toString()}`); // 最終結果是:6,13,19,23,28,31,49
複製程式碼
歸併排序
首先將具有n個待排序的記錄序列變成n個長度為1的有序序列,然後再兩兩合併,邊合併邊排序。
歸併排序嚴格按照從左往右(或從右往左)的順序去合併子陣列, 它並不會改變相同元素之間的相對順序, 因此它也是一種穩定的排序演算法.
- 合併過程(建議結合圖片和合並的程式碼理解!):我們看倒數第二行的右側,[1,7]和[4,5]進行合併,建立一個陣列result,先比較兩個陣列的首位置數值,1比4小,將1加入result,然後 7大於4,將4加入result,7大於5,將5加入result,然後原本[4,5]陣列長度為空,跳出迴圈
拆分過程:
function mergeSort(arr) { //輸入一個長度為n的陣列,輸出長度為1的n個陣列
var length = arr.length;
if(length < 2) {
return arr;
}
var m = (length >> 1),
left = arr.slice(0, m),
right = arr.slice(m); //拆分為兩個子陣列
return merge(mergeSort(left), mergeSort(right));//子陣列繼續遞迴拆分,然後再合併
};
複製程式碼
注: x>>1 是位運算中的右移運算, 等同於x除以2再取整, 即 x>>1 == Math.floor(x/2) 合併過程:
function merge(left, right){ // 合併陣列
let result = [];
while (left.length && right.length) {
// 注意:判斷的條件是小於或等於,如果只是小於,那麼排序將不穩定.
let item = left[0] <= right[0] ? left.shift() : right.shift();
}
// 只要left.length和right.length其中一個不滿足,就會跳出while迴圈
// 假設left為空,跳出迴圈,那麼right還剩一個值,反之同理
return result.concat(left.length ? left : right); // 將最終的return返回
};
複製程式碼
由上, 長度為n的陣列, 最終會呼叫mergeSort函式2n-1次.
這個演算法會出現錯誤,棧溢位
Uncaught RangeError: Maximum call stack size exceeded
複製程式碼