export enum Compare { LESS = -1, EQUAL = 0, GREATER = 1 } // 比較大小的方法 export function defaultCompareFunction<T>(a: T, b: T){ if(a === b){ return Compare.EQUAL } return a > b ? Compare.GREATER : Compare.LESS; } // 數組元素交換的方法 export function swap<T>(arr:Array<T>, beginIndex:number , endIndex:number){ [arr[beginIndex], arr[endIndex]] = [arr[endIndex], arr[beginIndex]] }
比較全部相鄰的兩個項,若是第一個比第二個大,則交換它們.元素向上移動到正確的位置,就好像氣泡升至表面同樣,冒泡排序所以得名算法
let arr = [9,5,4,5,4,8,4,2,45,1,5,41]; import {defaultCompareFunction, Compare ,swap} from "./common" export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction){ let len = arr.length; for (let i = 0; i < len; i++) { // 冒上去的元素都不須要在作比較,因此這裏的 for (let j = 0; j < len - 1 - i; j++) { // 前一個元素 const before = arr[j]; // 後一個元素 const after = arr[j + 1]; // 若是前一個元素比後一個元素的大,交換位置 if(compareFn(before, after) === Compare.GREATER){ swap(arr, j , j + 1); } } } } sortArray(arr) console.log(arr);
性能差: 須要雙循環typescript
- 外循環保證循環次數足夠
- 內循環調換相鄰元素的位置
注意點:數組
- 已經冒到水面的元素(已排好的元素),不須要在作比較 [每次一定會有一個最大值到最後]
- 是前一個元素和後一個元素的比較大小,因此外層循環只是爲了保證循環次數
將當前數組的劃分紅兩部份,前部分爲已排序部分 , 後部分爲未排序部分,而後將後部分數據一個一個的向前部分插入性能
let arr = [9,5,4,5,4,8,4,2,45,1,5,41]; import {defaultCompareFunction, Compare ,swap} from "./common" function sortArray<T>(arr:Array<T>, compareFn:Function = defaultCompareFunction){ for (let i = 0; i < arr.length; i++) { let current = i; // 注意循環從i開始,0結束,注意要從後面開始交換,這樣子才能作到一步一步的向前挪 // 若是從0開始,向後的話 會把當前數據移動回來 for (let j = i; j >= 0; j--) { const external = arr[current]; const within = arr[j]; // 比較外層的值是否小於當前值 if(compareFn(external, within) === Compare.LESS){ // 交換位置 swap(arr, current, j); // 並更新當前min指針 current = j; } } } } sortArray(arr) console.log(arr)
第一個元素默認有序ui
後面的元素須要從有序部分的尾部插入指針
注意交換位置後要追蹤當前元素code
找到數據中最小值並將其放置在第一位,接這找第二小的值,放在第二位 ....排序
let arr = [9,5,4,5,4,8,4,2,45,1,5,41]; import {defaultCompareFunction, Compare ,swap} from"./common" function sortArray<T>(arr:Array<T>, compareFn:Function = defaultCompareFunction){ for (let i = 0; i < arr.length; i++) { // 記錄當前爲最小下標 let min = i; for (let j = i + 1; j < arr.length; j++) { const external = arr[min]; const within = arr[j]; // 比較最小的與當前大小 ,若是最小的值比 if(compareFn(external, within) === Compare.GREATER){ // 將最小值轉爲當前下標 min = j; } } // 若是最小下標不等於當前下標 ,就交換位置 if(min !== i)swap(arr, min, i); } } sortArray(arr); console.log(arr)
定義最小的位置,而後記錄下其下標ip
外層循環表明着順序(第幾小的數)it
找到最小下標後,與外層的下標交換(不相同的狀況,相同交換沒有意義)
歸併使用的是分而治之的思想,將一個大數組分紅n個小數組,當分紅一個元素的時候就至關於默認有序,而後將有序的數組進行合併排序,最後達到排序的效果
import {defaultCompareFunction, Compare } from "./Common" // 拆分 export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction) { const len:number = arr.length; if(len > 1){ // 算出中位數 let middle = Math.floor(len / 2); // 拆分左邊 注意slice分割數組是不會改變數組自己的 let left = sortArray(arr.slice(0, middle), compareFn) // 拆分右邊 let right = sortArray(arr.slice(middle, len), compareFn) // 將左右倆個數組歸併, 並排序, 而後返回回去 arr = mergeSort(left , right , compareFn); } return arr; } // 歸併 function mergeSort<T>(leftArr: Array<T>,rightArr: Array<T>, compareFn: Function = defaultCompareFunction):Array<T> { let result = []; // 左邊數組指針 let left = 0; // 右邊數組指針 let right = 0; // 無論左右數組那邊越界都結束循環 while(left < leftArr.length && right < rightArr.length){ // 若是左邊小(大)就將左邊丟入數組中,並左數組指針+1, 反之亦然 // 這裏注意必定要指針變化 result.push(compareFn(leftArr[left], rightArr[right]) === Compare.LESS ? leftArr[left++] : rightArr[right++]) // if(compareFn(leftArr[left], rightArr[right]) === Compare.LESS){ // result.push(leftArr[left]); // left++; // }else{ // result.push(rightArr[right]); // right++; // } } return result.concat(left < leftArr.length ? leftArr.slice(left) : rightArr.slice(right)); }
先切割數組
後合併並排序這個兩個數組
注意的是這個裏面涉及到一個有序的數組的排序
排序使用雙指針,而後若是左指針的值(對應作邊數組)小於右指針的值(對應右邊數組),那麼就添加左邊數組的值到新數組中,而且左指針+1,可是右指針不動,直到最後那邊指針出界,可是兩個都是有序的數組,因此直接拼接上去便可
快速排序也是使用了分而治之的思想,可是並不會真的切分(指針上的區分)
import {defaultCompareFunction, Compare ,swap} from "./Common" export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction){ quickSort(arr, 0, arr.length - 1, compareFn) } function quickSort<T>(arr: Array<T>, left:number, right:number, compareFn: Function) { // 最小數組和最大數組的中界線 let index:number; // 當前數組長度大於1個 if(arr.length > 1){ // 獲取劃分的界限 左邊爲最小值 右邊爲最大值 index = partition(arr, left ,right, compareFn); if(left < index - 1){ // 快排左部分 quickSort(arr, left, index - 1, compareFn); } if(index < right){ // 快排右部分 quickSort(arr, index, right, compareFn) } } //當前數組長度等於1 一個元素是有序的 直接返回 return arr; } // 劃分 function partition<T>(arr:Array<T>, left:number, right:number, compareFn:Function):number { // 選擇主元的位置 :這裏選取數組中的中間值 const pivotIndex = Math.floor((left + right) / 2) //取主元 具體值 const pivot = arr[pivotIndex]; // 當左右兩邊交叉的時候結束 while(left <= right){ // 左邊值大於(等於)主元的時候結束循環 = 獲得左邊大於主元的值 while (compareFn(arr[left] , pivot) === Compare.LESS){ left++; } // 右邊值中有值小於(等於)主元的時候結束循環 = 獲得右邊小於主元的值 while (compareFn(arr[right] , pivot) === Compare.GREATER){ right--; } // 當left小於right 就交換 if(left <= right){ // 將元素進行交換 swap(arr, left , right); // 左指針 +1 縮小範圍 left++; // 右指針 -1 縮小範圍 right--; } } return left; } let arr = [1,2,54,8,45,7, 45,9,8,452,35,754,127,6,21,124,454] sortArray(arr) // @ts-ignore console.log(arr)
從數組中選擇一個值做爲主元,也就是數組中間的那個值
建立兩個指針,左邊指向數組的第一個值,右邊指向數組的最後一個值.移動左邊的指針直到找到比主元小的值,而後移動右邊的指針,找到比主元小的值,而後交換位置,重複該過程,直到左指針超過了右指針.這個過程將使比主元小的值集中在主元的左邊,比主元大的值集中在主元的右邊.這一步叫作劃分
算法對劃分後的小數組,繼續上面的步驟,直到數組徹底有序