天天看點

十大排序算法(javascript)

作者:前端少年汪
十大排序算法(javascript)
十大排序算法(javascript)

冒泡排序

通過相鄰元素的比較和交換,使得每一趟循環都能找到未有序數組的最大值或最小值。

内循環: 使用相鄰雙指針 j , j + 1 從左至右周遊,依次比較相鄰元素大小,若左元素大于右元素則将它們交換;周遊完成時,最大元素會被交換至數組最右邊 。

外循環: 不斷重複「内循環」,每輪将目前最大元素交換至 剩餘未排序數組最右邊 ,直至所有元素都被交換至正确位置時結束。

十大排序算法(javascript)

/**

* 冒泡

* 每一趟找出最大的,總共比較次數為arr.length-1次,每次的比較次數為arr.length-1-i次,依次遞減

* @param {*} arr

* @returns array

*/

function bubbleSort(arr) {

/**

比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。

對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。

針對所有的元素重複以上的步驟,除了最後一個。

持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

相同元素的前後順序并沒有改變,是以冒泡排序是一種穩定排序算法。

原始數組: [ 99, 88, 66, 101, 90, 45 ]

第1次循環 [ 88, 66, 99, 90, 45, 101 ]

第2次循環 [ 66, 88, 90, 45, 99, 101 ]

第3次循環 [ 66, 88, 45, 90, 99, 101 ]

第4次循環 [ 66, 45, 88, 90, 99, 101 ]

第5次循環 [ 45, 66, 88, 90, 99, 101 ]

*/

let len = arr.length;

if (!len) {

return [];

}

console.log('原始數組:', arr);

//外循環,對被排序的數組進行周遊,輪數為數組的長度

for (let i = 0; i < len - 1; i++) {

// 内循環,循環比較相鄰元素

for (let j = 0; j < len - 1 - i; j++) {

//如果前一個元素大于後一個元素的話,就交換兩個元素的位置,最後是以從大到小的順序輸出

if (arr[j] > arr[j + 1]) {

[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; //元素交換

}

}

console.log(`第${i+1}次循環`, arr);

}

return arr;

}

優化

普通冒泡排序的時間複雜度恒為 O(N2),與輸入數組的元素分布無關。

通過增加一個标志位 flag ,若在某輪「内循環」中未執行任何交換操作,則說明數組已經完成排序,直接傳回結果即可。

優化後的冒泡排序的最差和平均時間複雜度仍為 O(N2) ;在輸入數組 已排序 時,達到 最佳時間複雜度 (N)

function bubbleSort(arr) {

let len = arr.length;

if (!len) {

return [];

}

console.log('原始數組:', arr);

//外循環,對被排序的數組進行周遊,輪數為數組的長度

for (let i = 0; i < len - 1; i++) {

let flag = false; // 初始化标志位

// 内循環,循環比較相鄰元素

for (let j = 0; j < len - 1 - i; j++) {

//如果前一個元素大于後一個元素的話,就交換兩個元素的位置,最後是以從大到小的順序輸出

if (arr[j] > arr[j + 1]) {

[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; //元素交換

flag = true; // 記錄交換元素

}

}

if (!flag) break; // 内循環未交換任何元素,則跳出

console.log(`第${i+1}次循環`, arr);

}

return arr;

}

選擇排序

思路:依次找到剩餘元素的最小值或者最大值,放置在末尾或者開頭。

十大排序算法(javascript)

/**

* 選擇排序

* 依次找到剩餘元素的最小值或者最大值,放置在末尾或者開頭。

* @param {Array} arr

* @returns

*/

function selectionSort(arr) {

let len = arr.length;

let minIndex;

for (let i = 0; i < len - 1; i++) {

minIndex = i;//先假設第一個數字最小

for (let j = i + 1; j < len; j++) {

if (arr[j] < arr[minIndex]) { //尋找最小的數

minIndex = j; //将最小數的索引儲存

}

}

[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]]//交換兩個元素

}

return arr;

}

插入排序

思路:以第一個元素為有序數組,其後的元素通過再這個已有序的數組中找到合适的元素并插入。

function insertSort(arr) {

let length = arr.length,

preIndex, current;

for (let i = 1; i < length; i++) {

preIndex = i - 1;

current = arr[i];

// 和已經排序好的序列進行比較,插入到合适的位置

while (preIndex >= 0 && arr[preIndex] > current) {

arr[preIndex + 1] = arr[preIndex];

preIndex--;

}

arr[preIndex + 1] = current;

console.log(`第${i}次循環`, arr);

}

return arr;

}

希爾排序

通過某個增量 gap,将整個序列分給若幹組,從後往前進行組内成員的比較和交換,随後逐漸縮小增量至 1。希爾排序類似于插入排序,隻是一開始向前移動的步數從 1 變成了 gap

function shellSort(arr) {

let len = arr.length;

// 初始步數

let gap = parseInt(len / 2);

// 逐漸縮小步數

while (gap) {

// 從第gap個元素開始周遊

for (let i = gap; i < len; i++) {

// 逐漸其和前面其他的組成員進行比較和交換

for (let j = i - gap; j >= 0; j -= gap) {

if (arr[j] > arr[j + gap]) {

[arr[j], arr[j + gap]] = [arr[j + gap], arr[j]];

} else {

break;

}

}

}

gap = parseInt(gap / 2);

}

}

歸并排序

遞歸将數組分為兩個序列,有序合并這兩個序列。作為一種典型的分而治之思想的算法應用,歸并排序的實作由兩種方法:

  1. 自上而下的遞歸(所有遞歸的方法都可以用疊代重寫,是以就有了第2種方法)。
  1. 自下而上的疊代。

function /**

* 歸并排序

*/

mergeSort(arr) {

let len = arr.length;

if (len < 2) {

return arr;

}

let middle = Math.floor(len / 2),

left = arr.slice(0, middle),

right = arr.slice(middle);

console.log(`處理過程:`, arr);

return this.merge(this.mergeSort(left), this.mergeSort(right));

},

/**

* 歸并排序輔助方法

*/

function merge(left, right) {

let result = [];

while (left.length && right.length) {

if (left[0] <= right[0]) {

result.push(left.shift());

} else {

result.push(right.shift());

}

}

while (left.length) {

result.push(left.shift());

}

while (right.length){

result.push(right.shift());

}

return result;

}

快速排序

快速排序算法是一種基于分治思想的排序算法,其核心思路在于通過選取一個基準值,将待排序數組劃分為左右兩個子序列,其中左側序列所有元素均小于基準值,右側序列所有元素均大于基準值。之後對左右子序列遞歸進行快排操作,最終将整個序列排好序。

以下是使用 TypeScript 實作的快速排序算法代碼:

function quickSort(arr: number[]): number[] {

if (arr.length <= 1) {

return arr;

}

const pivotIndex = Math.floor(arr.length / 2);

const pivot = arr[pivotIndex];

const left = [];

const right = [];

for (let i = 0; i < arr.length; i++) {

if (i === pivotIndex) {

continue;

}

const currentItem = arr[i];

if (currentItem < pivot) {

left.push(currentItem);

} else {

right.push(currentItem);

}

}

return [...quickSort(left), pivot, ...quickSort(right)];

}

堆排序

堆排序算法是一種基于堆資料結構的排序算法,其核心思路在于将待排序數組看做二叉樹,通過建構大頂堆或小頂堆來實作排序。對于大頂堆,每個節點的值均大于或等于它的子節點;對于小頂堆,每個節點的值均小于或等于它的子節點。排序時,取堆頂元素,将其存儲到已排序數組中,并從堆中删除;然後重新調整剩餘元素形成新的堆,重複以上操作直至所有元素排序完成。

以下是使用 TypeScript 實作的堆排序算法代碼:

function heapSort(arr: number[]): number[] {

const len = arr.length;

// 初始化大頂堆,從第一個非葉子結點開始

for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {

heapify(arr, len, i);

}

// 排序,每次将堆頂元素與未排定部分的最後一個元素交換,并重新構造大頂堆

for (let i = len - 1; i > 0; i--) {

[arr[0], arr[i]] = [arr[i], arr[0]];

heapify(arr, i, 0);

}

return arr;

}

// 堆化函數,将以i為根節點的子樹調整為大頂堆

function heapify(arr: number[], len: number, i: number) {

let largest = i; // 最大值預設為根節點

const left = 2 * i + 1; // 左子節點下标

const right = 2 * i + 2; // 右子節點下标

// 如果左子節點比目前最大值大,則更新最大值

if (left < len && arr[left] > arr[largest]) {

largest = left;

}

// 如果右子節點比目前最大值大,則更新最大值

if (right < len && arr[right] > arr[largest]) {

largest = right;

}

// 如果最大值不是根節點,則交換根節點和最大值,并繼續調整以最大值為根的子樹

if (largest !== i) {

[arr[i], arr[largest]] = [arr[largest], arr[i]];

heapify(arr, len, largest);

}

}

記數排序

記數排序(Counting Sort)是一種非基于比較的排序算法,其時間複雜度為O(n+k),其中k表示待排序數組中最大元素與最小元素之差加1。該算法的基本思想是統計每個元素在待排序數組中出現的次數,然後根據統計結果建構有序序列。

/**

* 計數排序

* @param arr 待排序數組

* @returns 排序後數組

*/

function countingSort(arr: number[]): number[] {

const max = Math.max(...arr);

const count = new Array(max + 1).fill(0);

for (let i = 0; i < arr.length; i++) {

count[arr[i]]++;

}

const res = [];

for (let i = 0; i <= max; i++) {

while (count[i]--) {

res.push(i);

}

}

return res;

}

桶排序

桶排序(Bucket Sort)是一種線性排序算法,它利用了函數的映射關系,将要排序的資料分到有限數量的桶子裡,每個桶子再分别排序。桶排序的時間複雜度取決于桶的數量和桶内使用的排序算法,通常情況下是O(n+k)。

/**

* 桶排序

* @param arr 待排序數組

* @param bucketSize 桶大小

* @returns 排序後數組

*/

function bucketSort(arr: number[], bucketSize = 5): number[] {

if (arr.length === 0) {

return arr;

}

// 找出最大值和最小值

let min = arr[0];

let max = arr[0];

for (let i = 1; i < arr.length; i++) {

if (arr[i] < min) {

min = arr[i];

} else if (arr[i] > max) {

max = arr[i];

}

}

// 計算桶的數量

const bucketCount = Math.floor((max - min) / bucketSize) + 1;

const buckets: number[][] = [];

for (let i = 0; i < bucketCount; i++) {

buckets[i] = [];

}

// 将元素配置設定到桶中

for (let i = 0; i < arr.length; i++) {

const index = Math.floor((arr[i] - min) / bucketSize);

buckets[index].push(arr[i]);

}

// 對每個桶進行排序,并将結果合并

const res = [];

for (let i = 0; i < buckets.length; i++) {

if (buckets[i]) {

const sortedBucket = countingSort(buckets[i]);

for (let j = 0; j < sortedBucket.length; j++) {

res.push(sortedBucket[j]);

}

}

}

return res;

}

基數排序

基數排序(Radix Sort)是一種多關鍵字排序算法,可用于對數字序列進行排序。基數排序先按照最低有效位(LSB)對元素進行排序,然後依次按照次低有效位、次次低有效位……最高有效位進行排序。該算法的時間複雜度為O(d*(n+k)),其中d表示數字位數,k表示每個數字可能的取值範圍。

/**

* 基數排序

* @param arr 待排序數組

* @returns 排序後數組

*/

function radixSort(arr: number[]): number[] {

const max = Math.max(...arr);

const buckets: number[][] = [];

// 初始化桶

for (let i = 0; i < 10; i++) {

buckets[i] = [];

}

// 計算最大數字的位數

let digitCount = 0;

while (max > 0) {

max = Math.floor(max / 10);

digitCount++;

}

// 根據每一位進行排序

for (let i = 0; i < digitCount; i++) {

for (let j = 0; j < arr.length; j++) {

const num = arr[j];

const digit = Math.floor(num / Math.pow(10, i)) % 10;

buckets[digit].push(num);

}

arr = [];

for (let k = 0; k < buckets.length; k++) {

while (buckets[k].length) {

arr.push(buckets[k].shift()!);

}

}

}

return arr;

}