1. 程式人生 > 實用技巧 >第十三章 排序演算法 下部分

第十三章 排序演算法 下部分

排序演算法 下部分

以下被稱為分散式排序的演算法,原始陣列中的資料會分發到多箇中間結構(桶),再合起來放回原始陣列。最著名的分散式演算法有計數排序、桶排序和基數排序,這三種演算法非常相似。

計數排序

計數排序是一種用來排序整數的優秀演算法,時間複雜度為O(n + k),其中k是臨時計數陣列的大小,但是,他需要更多的記憶體來存放臨時變數.

  • 先建立一個排序資料最大值 + 1建立一個數組(關於這部分我剛開始也不是很明白,js的陣列不是動態的嗎!何必多此一舉去找排序資料的最大值,然後建立一個數組呢???後來想了一下陣列的擴容是不是要資料遷移,那樣的確不如提前建立大一點,然後找了一下資料: 探究JS V8引擎下的“陣列”底層實現
    ),感興趣的可以瞧一瞧.
  • 然後開始計數,其實就是以下標代表資料,然後以值作為資料出現的次數(分散式中的第一步)
  • 迴圈計數的陣列,然後將其迴歸到原陣列上(分散式中的第二步)
export function sortArray(arr: Array<number>) {
    
    if (arr.length < 2){
        return;
    }
    
    let len = findMaxNumber(arr) + 1;
    let countArr = new Array(len).fill(0);

    for (let i = 0; i < arr.length; i++) {
        countArr[arr[i]]++;
    }

    for (let i = 0,index = 0; i < countArr.length; i++) {

        for (let j = 0; j < countArr[i]; j++) {
            arr[index++] = i;
        }
    }
}

/**
 * 找最大數
 * @param arr
 */
function findMaxNumber(arr: Array<number>):number {
    let max:number = 0;
    
    for (let i = 0; i < arr.length; i++) {
        if(arr[i] > max){
            max = arr[i];
        }
    }
    return max;
}


let arr = [54,8,45,7, 1,2,45,9,8,452,35,754,127,6,21,124,454]
sortArray(arr)

// @ts-ignore
console.log(arr)

可以看出這種排序很適合那種有多個重複資料的整數陣列,但是資料中有一個特別大的時候,計數陣列將會佔用很大的記憶體

還有這裡建立的陣列是 最大值 + 1,因為陣列的下標是0開始的,所以 +1確保最大值也能加入資料

桶排序

桶排序也稱之為箱排序,也是分散式排序演算法中的一種,它將元素分為不同的桶(較小的陣列),然後使用一個簡單的排序演算法排序(比如說插入排序),然後合併陣列為結果

對於桶排序演算法,我們需要指定需要多少個桶來排序各個元素,預設情況下,我們會使用5個桶(這裡我認為是每個桶的容量為5,而且看這個作者命名bucketSize,也覺得就是桶容量,不過確定了一個桶的容量,自然就確定了桶的個數,後面我還是以桶容量來說明該引數).桶排序在所有元素平分到各個桶時的表現最好.如果元素非常稀疏,則使用更多的桶會更好.如果元素非常密集,則使用較少的桶會比較好,因此我們允許bucketSize

以引數的形式傳遞

  • 第一步: 建立桶

    建立桶看似很簡單,實際上還是有點難度的,需要做到幾點

    • 算出桶的個數: Math.floor((最大值 - 最小值 ) / 單個桶的容量) + 1
    • 然後建立一個桶的個數對應大小的陣列,內部填充[],注意這裡不能使用fill填充,fill填充的陣列是同一個引用,所以會導致失敗(這裡建立了一個二維陣列)
    • 將排序元素塞到對應的桶裡去: (當前值 - 最小值) / 單個桶的容量,其實這個塞入的過程就已經對桶內資料進行了一次粗略排序
  • 第二步: 對桶排序

    傳回的是一個二維陣列,桶是有序的(0號桶的內容會都小於1號桶的內容)

    使用插入排序對桶內資料排序

    組合為新的陣列返回

import {sortArray as insertSort} from "./InsertSort"

export function sortArray(arr: Array<number>, bucketSize: number = 5) {

    if (arr.length < 2) {
        return arr;
    }

    // 建立桶
    let buckets = createBuckets(arr, bucketSize);
    // 桶排序並返回陣列
    return sortBuckets(buckets);
}


/**
 * 建立桶
 * @param arr
 * @param bucketSize  單個桶的容量
 */
function createBuckets(arr: Array<number>, bucketSize: number):any[][] {
    let minNum = arr[0];
    let maxNum = arr[0];

    // 迴圈查詢最大值最小值
    for (let i = 1; i < arr.length; i++) {

        if (maxNum < arr[i]) {
            maxNum = arr[i];
        } else if (arr[i] < minNum) {
            minNum = arr[i];
        }

    }

    // 計算桶數
    const bucketCount = Math.floor((maxNum - minNum) / bucketSize) + 1;
    // 建立並初始化裝桶的陣列,這裡就是一個二元陣列了
    const buckets = [];
    
    //這個for迴圈不要使用fill替換
    for (let i = 0; i < bucketCount; i++) {
        buckets[i] = [];
    }

    for (let i = 0; i < arr.length; i++) {
        // 計算下標的過程其實是對資料進行了一次簡單的排序,然後桶就會呈現出有序性
        const index = Math.floor((arr[i] - minNum) / bucketSize)
        buckets[index].push(arr[i]);
    }

    return buckets;
}

/**
 * 對桶進行排序
 * @param buckets
 */
function sortBuckets(buckets: any[][]) {
    let result = [];
    for (let i = 0; i < buckets.length; i++) {
        let element = buckets[i];

        if(element !== null){
            insertSort(element)
            result.push(...element);
        }
    }

    return result;
}

let arr = [54,8,45,7, 1,2,45,9,8,452,35,754,127,6,21,124,454]
let sortArr = sortArray(arr)

// @ts-ignore
console.log(sortArr)