十種常見排序算法能夠分爲兩大類:javascript
非線性時間比較類排序:經過比較來決定元素間的相對次序,因爲其時間複雜度不能突破O(nlogn),所以稱爲非線性時間比較類排序。html
線性時間非比較類排序:不經過比較來決定元素間的相對次序,它能夠突破基於比較排序的時間下界,以線性時間運行,所以稱爲線性時間非比較類排序。前端
穩定:若是a本來在b前面,而a=b,排序以後a仍然在b的前面。java
不穩定:若是a本來在b的前面,而a=b,排序以後 a 可能會出如今 b 的後面。算法
時間複雜度:對排序數據的總的操做次數。反映當n變化時,操做次數呈現什麼規律。segmentfault
空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。數組
比較兩個相鄰的元素,將值大的元素交換到右邊函數
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let flag;
let len = arr.length;
let num1 = 0; // 比較的次數
let num2 = 0; // 交換的次數
// 有15個數,只須要選出14個最大的數,最後一個數就是最小的,不用進行比較
for(let i = 0; i < len -1 ; i++){
// 每次i變化以後最大的值已經排序到最後一位,無需對最後一位進行比較,因此j的最大值爲len-i-1
for(let j = 0; j < len - i - 1; j++){
num1 += 1;
// 若是當前位置的數比下一位置的數大,則交換位置
if(arr[j] > arr[j+1]){
num2 =+ 1;
flag = arr[j];
arr[j] = arr[j+1];
arr[j+1] = flag
}
}
}
console.log(arr,num);
複製代碼
輸出的結果爲:優化
計數器num1和num2的值分別爲:105和46ui
從代碼中看出,排序過程當中,所須要的臨時變量一直都沒有變化,所以空間複雜度爲O(1);代碼進行了兩次for循環且是嵌套循環,所以時間複雜度爲O(n²)。
冒泡排序的最優狀況是原數組默認正序排序,此時比較的次數num1仍爲105,而交換次數num2爲0,此時的時間複雜度仍然爲O(n²),那麼爲何前面的複雜度表格中說是O(n)呢?通過一番研究發現,須要對上述代碼進行簡單優化。
若是排序的數組是:[1,2,3,4,5],此時徹底符合最優複雜度狀況,當咱們進行第一次循環發現,兩兩相鄰的數據一次都沒有進行交換,也就是說全部的數都比前一個數大,此時就是正序,無需再進行下次排序,因此咱們只須要加上一個變量進行判斷:
// 初始未產生交換
let isSwap = false;
for(let i = 0; i < len -1 ; i++){
// 每次i變化以後最大的值已經排序到最後一位,無需對最後一位進行比較,因此j的最大值爲len-i-1
for(let j = 0; j < len - i - 1; j++){
num1 += 1;
// 若是當前位置的數比下一位置的數大,則交換位置
if(arr[j] > arr[j+1]){
num2 =+ 1;
isSwap = true;
flag = arr[j];
arr[j] = arr[j+1];
arr[j+1] = flag
}
}
// 若是產生交換,直接結束循環
if(!isSwap){
return
}
}
複製代碼
當產生交換時,isSwap變成true,第一次循環結束以後,若是isSwap若是仍是false表示未通過交換,數組已是正序,無需繼續排序,此時的時間複雜度爲O(n)
在比較過程當中只判斷了大於後一個數,若是兩個數相等無需交換,因此冒泡排序是穩定的排序。
將序列分爲未排序和已排序,從未排序序列中找到最小的數,放到無序序列起始位置,而後繼續從剩餘未排序序列中繼續尋找最小值
// 選擇出無序序列中最小的值放到無序第一位
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let len = arr.length;
let minIndex;
let flag;
let num1 = 0; // 比較次數
let num2 = 0; // 交換次數
for(let i = 0; i < len - 1;i++){
// 每次選擇最小值以後,無序區的開始位置日後推1
minIndex = i;
// j循環到最後一位,選擇出當前無序數組中數值最小的索引值
for(let j = i + 1; j < len;j++){
num1 += 1;
if(arr[minIndex]>arr[j]){
minIndex = j;
}
}
num2 += 1;
flag = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = flag;
}
console.log(arr,num1,num2)
複製代碼
輸出結果爲:
計數器num1和num2的值分別爲:105和14
也就是說,選擇排序的比較次數和冒泡排序未優化時同樣,而交換的次數只有14次
再舉剛剛的栗子:[1,2,3,4,5],正序序列,無需進行任何交換,咱們對最後交換代碼進行優化:
if(minIndex == i){
num2 += 1;
flag = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = flag;
}
複製代碼
此時沒有發生數據交換。
其實從代碼中能夠看到,不管如何,選擇排序總會通過N^2/2
次比較,而受原始數列影響,交換的次數最大爲n-1,最小次數爲0。
所以選擇排序時間複雜度總爲:O(n平方),空間複雜度爲:O(1)
選擇排序是不穩定的,爲何這麼說,看個栗子:[5,3,8,5,2],好了不說也能看出來了。
構建有序序列,對於未排序的數據,從有序序列後向前掃描,找到相應位置插入
let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
let len = arr.length;
// 定義當前未排序數據起始位置值也就是即將插入的數據
let currentValue;
// 有序序列遍歷位置
let preIndex;
let num1 = 0; // 比較次數
let num2 = 0; // 交換次數
for(let i = 1;i < len; i++){
// 定義原始數據第二位爲未排序數據第一位,默認原始數據第一位已排序
currentValue = arr[i];
// 當前有序序列最大索引
preIndex = i - 1;
// 當索引大於等於0且當前索引值大於須要插入的數據時
for(let j = preIndex;j>=0;j--){
// 第一次比較,若是有序序列最大索引值其實就是待插入數據前一位,比待插入數據大,則後移一位
num1+=1;
if(arr[preIndex]>currentValue){
arr[preIndex+1] = arr[preIndex];
preIndex --;
// 索引減1,繼續向前比較
}
}
// 當出現索引所在位置值比待插入數據小時,將待插入數據插入
// 爲何是preIndex+1,由於在while循環裏面後移一位以後,當前索引已經變化
num2 += 1;
arr[preIndex+1] = currentValue;
}
console.log(...arr,num1,num2)
複製代碼
輸出的結果爲:
計數器num1和num2的值分別爲:105和14
插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
在時間複雜度上,若是按照上述寫法,執行的次數依次爲,1,2,3....n-1,所以時間複雜度爲O(n²)
冒泡排序出現了優化以後最優複雜度變小的狀況,看看前面的表格,插入排序的最優時間複雜度爲O(n),那麼這又是什麼緣由呢?
咱們看看判斷待插入值是否小於當前索引位置值的地方,用了一個for循環和一個if,咱們能不能改寫一下呢?
while(preIndex>=0&&arr[preIndex]>currentValue){
arr[preIndex+1] = arr[preIndex];
preIndex --;
}
複製代碼
這樣看,若是在正序狀況下:[1,2,3,4,5],每次比較都不會進入while循環,所以只執行了n-1次比較操做,所以此時時間複雜度爲O(n),那麼最壞複雜度其實也就是逆序狀況了,須要執行1,2,3...n次,所以最壞時間複雜度爲O(n²)。
綜合比較一下最簡單的三種排序方法:
選擇排序在冒泡排序上作了優化,冒泡排序兩兩比較每一輪選出一個最大值,而選擇排序則從序列中直接選擇出最小值插入無序序列首部(進行交換),相對於冒泡排序減小了沒必要要的換位操做。
插入排序在思想上和選擇排序差很少,選擇排序是從無序序列中找出最小值,與無序序列的首位進行交換,從而生成一個有序序列,而插入排序則從無序序列中直接選出首位,將首位與有序序列進行比較,插入相應的位置。
對於通常工做來講,這三種使用沒什麼體驗上的差距
若是非要選擇的話,插入排序比選擇排序少一些比較的次數,但選擇排序有時候比插入排序少挪動次數,建議數據較大時用插入排序,數據量較小時能夠用選擇排序。(其實這倆差很少--)
新手上路請多指教,若有錯誤,輕噴
【小小前端】前端排序算法第二期(繞人的希爾排序)