摘自wiki和http://bubkoo.com/2014/01/17/sort-algorithm/archives/
什么是算法
https://zh.wikipedia.org/zh-hans/%E7%AE%97%E6%B3%95
排序演示 https://visualgo.net/zh/sorting
以下是高德納在他的著作《計算機程序設計藝術》里對算法的特征歸納:
輸入:一個算法必須有零個或以上輸入量。
輸出:一個算法應有一個或以上輸出量,輸出量是算法計算的結果。
明確性:算法的描述必須無歧義,以保證算法的實際執行結果是精確地匹配要求或期望,通常要求實際運行結果是確定的。
有限性:依據圖靈的定義,一個算法是能夠被任何圖靈完備系統模擬的一串運算,而圖靈機只有有限個狀態、有限個輸入符號和有限個轉移函數(指令)。而一些定義更規定算法必須在有限個步驟內完成任務。
有效性:又稱可行性。能夠實現,算法中描述的操作都是可以通過已經實現的基本運算執行有限次來實現
什么是數據結構
就是數據的結構。
一般來說是這樣的:
我們要解決一個跟數據相關的問題
分析這個問題,想出對應的數據結構
分析數據結構,想出算法
數據結構和算法是互相依存、不可分開的
你學習完排序算法,就能了解常見的數據結構
大分類
分治法:把一個問題分區成互相獨立的多個部分分別求解的思路。這種求解思路帶來的好處之一是便于進行并行計算。
動態規劃法:當問題的整體最優解就是由局部最優解組成的時候,經常采用的一種方法。
貪婪算法:常見的近似求解思路。當問題的整體最優解不是(或無法證明是)由局部最優解組成,且對解的最優性沒有要求的時候,可以采用的一種方法。
線性規劃法:見詞條。
簡并法:把一個問題通過邏輯或數學推理,簡化成與之等價或者近似的、相對簡單的模型,進而求解的方法。
我們前端主要使用分治法——分而治之。
快速排序
快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分為兩個子序列(sub-lists)。
步驟為:
從數列中挑出一個元素,稱為"基準"(pivot),
重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準后面(相同的數可以到任何一邊)。在這個分區結束之后,該基準就處于數列的中間位置。這個稱為分區(partition)操作。
遞歸地(recursively)把小于基準值元素的子數列和大于基準值元素的子數列排序。
遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法一定會結束,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。
Array.prototype.quick_sort = function() {
var len = this.length;
if (len <= 1)
return this.slice(0);
var left = [];
var right = [];
var mid = [this[0]];
for (var i = 1; i < len; i++)
if (this[i] < mid[0])
left.push(this[i]);
else
right.push(this[i]);
return left.quick_sort().concat(mid.concat(right.quick_sort()));
};
var arr = [5, 3, 7, 4, 1, 9, 8, 6, 2];
arr = arr.quick_sort();
for (i = 0; i < arr.length; i++)
document.body.innerHTML += arr[i] + " ";
document.body.innerHTML += "<br>";
冒泡排序
冒泡排序算法的運作如下:
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
針對所有的元素重復以上的步驟,除了最后一個。
持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
Array.prototype.quick_sort = function() {
var len = this.length;
if (len <= 1)
return this.slice(0);
var left = [];
var right = [];
var mid = [this[0]];
for (var i = 1; i < len; i++)
if (this[i] < mid[0])
left.push(this[i]);
else
right.push(this[i]);
return left.quick_sort().concat(mid.concat(right.quick_sort()));
};
var arr = [5, 3, 7, 4, 1, 9, 8, 6, 2];
arr = arr.quick_sort();
for (i = 0; i < arr.length; i++)
document.body.innerHTML += arr[i] + " ";
document.body.innerHTML += "<br>";
插入排序
一般來說,插入排序都采用in-place在數組上實現。具體算法描述如下:
從第一個元素開始,該元素可以認為已經被排序
取出下一個元素,在已經排序的元素序列中從后向前掃描
如果該元素(已排序)大于新元素,將該元素移到下一位置
重復步驟3,直到找到已排序的元素小于或者等于新元素的位置
將新元素插入到該位置后
重復步驟2~5
function insertionSort(array) {
function swap(array, i, j) {
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
var length = array.length,
i,
j;
for (i = 1; i < length; i++) {
for (j = i; j > 0; j--) {
if (array[j - 1] > array[j]) {
swap(array, j - 1, j);
} else {
break;
}
}
}
return array;
}
下面這種方式可以減少交換次數:
function insertionSort(array) {
var length = array.length,
i,
j,
temp;
for (i = 1; i < length; i++) {
temp = array[i];
for (j = i; j >= 0; j--) {
if (array[j - 1] > temp) {
array[j] = array[j - 1];
} else {
array[j] = temp;
break;
}
}
}
return array;
}
桶排序
設置一個定量的數組當作空桶子。
尋訪序列,并且把項目一個一個放到對應的桶子去。
對每個不是空的桶子進行排序。
從不是空的桶子里把項目再放回原來的序列中。
首先用最笨的方法,每一個桶只能放相同的數字,最大桶的數量為數組中的正數最大值加上負數最小值的絕對值。
function bucketSort(array) {
var bucket = [], // 正數桶
negativeBucket = [], // 負數桶
result = [],
l = array.length,
i,
j,
k,
abs;
// 入桶
for (i = 0; i < l; i++) {
if (array[i] < 0) {
abs = Math.abs(array[i]);
if (!negativeBucket[abs]) {
negativeBucket[abs] = [];
}
negativeBucket[abs].push(array[i]);
} else {
if (!bucket[array[i]]) {
bucket[array[i]] = [];
}
bucket[array[i]].push(array[i]);
}
}
// 出桶
l = negativeBucket.length;
for (i = l - 1; i >= 0; i--) {
if (negativeBucket[i]) {
k = negativeBucket[i].length;
for (j = 0; j < k; j++) {
result.push(negativeBucket[i][j]);
}
}
}
l = bucket.length;
for (i = 0; i < l; i++) {
if (bucket[i]) {
k = bucket[i].length;
for (j = 0; j < k; j++) {
result.push(bucket[i][j]);
}
}
}
return result;
}
下面這種方式就是文中舉例分析的那樣,每個桶存放一定范圍的數字,用 step 參數來設置該范圍,取 step 為 1 就退化成前一種實現方式。關鍵部位代碼有注釋,慢慢看,邏輯稍微有點復雜。
/*
* @array 將要排序的數組
*
* @step 劃分桶的步長,比如 step = 5,表示每個桶存放的數字的范圍是 5,像 -4<sub>1、0</sub>5、6~11
*/
function bucketSort(array, step) {
var result = [],
bucket = [],
bucketCount,
l = array.length,
i,
j,
k,
s,
max = array[0],
min = array[0],
temp;
for (i = 1; i < l; i++) {
if (array[i] > max) {
max = array[i]
}
if (array[i] < min) {
min = array[i];
}
}
min = min - 1;
bucketCount = Math.ceil((max - min) / step); // 需要桶的數量
for (i = 0; i < l; i++) {
temp = array[i];
for (j = 0; j < bucketCount; j++) {
if (temp > (min + step * j) && temp <= (min + step * (j + 1))) { // 判斷放入哪個桶
if (!bucket[j]) {
bucket[j] = [];
}
// 通過插入排序將數字插入到桶中的合適位置
s = bucket[j].length;
if (s > 0) {
for (k = s - 1; k >= 0; k--) {
if (bucket[j][k] > temp) {
bucket[j][k + 1] = bucket[j][k];
} else {
break;
}
}
bucket[j][k + 1] = temp;
} else {
bucket[j].push(temp);
}
}
}
}
for (i = 0; i < bucketCount; i++) { // 循環取出桶中數據
if (bucket[i]) {
k = bucket[i].length;
for (j = 0; j < k; j++) {
result.push(bucket[i][j]);
}
}
}
return result;
}