之前一篇文章常用的比較算法排序總結介紹了幾種常用的比較排序算法,下面介紹的是幾種非比較排序算法,分別是:計數排序、基數排序以及桶排序。
非比較排序算法內部引用的都是計數排序,當然你也可以將計數排序換為其他的比較排序算法。
計數排序
計數排序的步驟為:
- 遍歷數組(A),借助一個輔助數組(B),將每一個數字放在輔助數組(B)對應索引的位置並計數加1
- 遍歷輔助數組(B),將每項的值變為與前一項相加的和
- 遍歷原始數組(A),取出輔助數組中對應的索引值,將值填入對應的一個新的數組(C)中
計數排序的原理用一個通俗的栗子來講就是這樣的:
// 有一個這樣的數組
var arr = [1, 5, 3, 8, 2];
// 8排在哪個位置? 4 ,如何得來?
1 < 8 // 計數加1
5 < 8 // 計數加1
3 < 8 // 計數加1
2 < 8 // 計數加1
那最后自然是 4
// 算法上是這樣實現:
B: [, 1, 1, 1, , 1, , , 1]
B: [0, 1, 1, 1, 0, 1, 0, 0, 1] // 空余部分用0填充
B: [0, 1, 2, 3, 3, 4, 4, 4, 5] // 計數
// 遍歷原始數組
arr[0]: 1
// 取B中對應的索引值
B[1]: 1
// 放入C
C: [, 1]
// 中間省略N步
// N+1步
arr[4]: 2
B[2]: 2
C: [, 1, 2, 3, 5, 8]
C: [1, 2, 3, 5, 8] // 去除空余
實現:
// 分類 ------------ 內部非比較排序
// 數據結構 --------- 數組
// 最差時間復雜度 ---- O(n + k)
// 最優時間復雜度 ---- O(n + k)
// 平均時間復雜度 ---- O(n + k)
// 所需輔助空間 ------ O(n + k)
// 穩定性 ----------- 穩定
var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6];
var b = [];
var c = [];
// 初始計數
for (var i = 0; i < arr.length; i++) {
var ai = arr[i];
var count = b[ai];
count ? b[ai] = ++count : b[ai] = 1;
}
// 計數
for (var i = 1; i < b.length; i++) {
var p = b[i - 1];
var n = b[i];
if (!p) {
p = 0;
}
if (!n) {
n = 0;
}
b[i] = p + n;
}
// 重新分配
for (var i = arr.length - 1; i >= 0 ; i--) {
var index = b[arr[i]];
while (index >= 0) {
if (typeof c[index] === 'number') {
index--;
} else {
c[index] = arr[i];
break;
}
}
}
// 去除空格
for (var i = 0; i < c.length; i++) {
if (!c[i]) {
c.splice(i, 1);
}
}
console.log(c);
基數排序
基數排序的基本原理是:
- 將所有待比較的數字均看成位數相同的,不同的用0填充,比如
12
和112
這兩個數字,則看成012
和112
- 從最后位置依次向前比較,每次比較會得到一個排序(這里的比較會運用到計數排序),這樣就會得到最終的排序規則
還是用一個栗子來說明一下,這樣更加清楚
// 有這樣一個數組
var arr = [12, 112, 34, 26, 290, 1, 45];
// 1. 補齊
var arr = [012, 112, 034, 026, 290, 001, 045]
// 2. 比較最后一位
012 290
112 001
034 012
026 -> 112
290 034
001 045
045 026
// 3. 比較第二位
290 001
001 012
012 112
112 -> 026
034 034
045 045
026 290
// 4. 比較百位
001 001 1
012 012 12
112 026 26
026 -> 034 -> 34
034 045 45
045 112 112
290 290 290
實現:
// 分類 ------------- 內部非比較排序
// 數據結構 ---------- 數組
// 最差時間復雜度 ---- O(n * max)
// 最優時間復雜度 ---- O(n * max)
// 平均時間復雜度 ---- O(n * max)
// 所需輔助空間 ------ O(n * max)
// 穩定性 ----------- 穩定
var arr = [12, 112, 34, 26, 290, 1, 45];
var max = 1;
// 獲取每次的余數
function getRemainder(n, d) {
var base = Math.pow(10, d);
return Math.floor(n / base) % 10;
}
function countingSort(arr, d) {
var b = [];
var c = [];
for (var j = 0; j < arr.length; j++) {
var l = getRemainder(arr[j], d);
var count = b[l];
count ? b[l] = ++count : b[l] = 1;
}
// 計數
for (var i = 1; i < b.length; i++) {
var p = b[i - 1];
var n = b[i];
if (!p) {
p = 0;
}
if (!n) {
n = 0;
}
b[i] = p + n;
}
// 重新分配
for (var i = arr.length - 1; i >= 0 ; i--) {
var index = b[getRemainder(arr[i], d)];
while (index >= 0) {
if (typeof c[index] === 'number') {
index--;
} else {
c[index] = arr[i];
break;
}
}
}
// 去除空格
for (var i = 0; i < c.length; i++) {
if (!c[i]) {
c.splice(i, 1);
}
}
for (var i = 0; i < c.length; i++) {
arr[i] = c[i];
}
}
// 計算最大位數
for (var i = 0; i < arr.length; i++) {
var t = '' + arr[i];
if (t.length > max) {
max = t.length;
}
}
for (var i = 0; i < max; i++) {
countingSort(arr, i);
}
console.log(arr);
大致運行過程如下:
12 112 34 26 290 1 45
290 1 12 112 34 45 26
1 12 112 26 34 45 290
1 12 26 34 45 112 290
桶排序
桶排序是原理是,將一個數組分成若干個桶(桶的數量根據數據量來確定,比如根據最大值、值的區間范圍等等,但盡量保證每個桶內的數據均勻即可),通過一定的規則來確定這個數字在哪個桶當中,比如下面的我就取的每個桶的范圍為10,那么除以這個范圍即可以得出這個數字屬於哪個桶中。然后再采用非比較排序或者計數排序對桶內數據進行排序,這樣在遍歷所有桶中的數據時,就保證了數據已經排列好了。
下面還是以一個數組為例說明:
var arr = [12, 112, 34, 32, 29, 26, 290, 114, 1, 45, 292];
12 / 10 -> 1
112 / 10 -> 11
34 / 10 -> 3
32 / 10 -> 3
29 / 10 -> 2
26 / 10 -> 2
290 / 10 -> 29
114 / 10 -> 11
1 / 10 -> 0
45 /10 -> 4
0號桶內數據:1
1號桶內數據:12
2號桶內數據:29, 26
3號桶內數據: 34, 32
4號桶內數據:45
11號桶內數據:112, 114
29號桶內數據:290, 292
得出了每個桶內的數據之后,然后在按照基本的排序把桶內數據排序好即可,可見桶的復雜度取決於取桶的數量以及桶內的排序算法。
// 分類 ------------- 內部非比較排序
// 數據結構 --------- 數組
// 最差時間復雜度 ---- O(nlogn)或O(n^2),只有一個桶,取決於桶內排序方式
// 最優時間復雜度 ---- O(n),每個元素占一個桶
// 平均時間復雜度 ---- O(n),保證各個桶內元素個數均勻即可
// 所需輔助空間 ------ O(n + bn),bn為桶的個數
// 穩定性 ----------- 穩定
var arr = [12, 112, 34, 32, 29, 26, 290, 114, 1, 45, 292];
function countingSort(arr) {
var b = [];
var c = [];
for (var i = 0; i < arr.length; i++) {
var ai = arr[i];
var count = b[ai];
count ? b[ai] = ++count : b[ai] = 1;
}
// 計數
for (var i = 1; i < b.length; i++) {
var p = b[i - 1];
var n = b[i];
if (!p) {
p = 0;
}
if (!n) {
n = 0;
}
b[i] = p + n;
}
// 重新分配
for (var i = arr.length - 1; i >= 0 ; i--) {
var index = b[arr[i]];
c[index] ? c[index - 1] = arr[i] : c[index] = arr[i];
}
// 去除空格
for (var i = 0; i < c.length; i++) {
if (!c[i]) {
c.splice(i, 1);
}
}
for (var i = 0; i < c.length; i++) {
arr[i] = c[i];
}
}
function bucketSort(arr) {
var bucketList = [];
var resultList = [];
var base = 10;
// 分桶
for (var i = 0; i < arr.length; i++) {
var bucketNum = Math.floor(arr[i] / 10);
if (bucketList[bucketNum]) {
bucketList[bucketNum].push(arr[i]);
} else {
bucketList[bucketNum] = [arr[i]];
}
}
// 桶內使用計數排序
for (var i = 0; i < bucketList.length; i++) {
bucketList[i] && countingSort(bucketList[i]);
}
// 輸出
for (var i = 0; i < bucketList.length; i++) {
if (bucketList[i]) {
resultList = resultList.concat(bucketList[i]);
}
}
return resultList;
}
console.log(bucketSort(arr));
過程大致如下:
0號桶內數據: 1
1號桶內數據: 12
2號桶內數據: 29 26
3號桶內數據: 34 32
4號桶內數據: 45
11號桶內數據: 112 114
29號桶內數據: 290 292