日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

作者:神奇的程序員K

轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/3B8dZRfbIuktSBeArXlmcQ

前言

我們?cè)陧?yè)面上渲染數(shù)據(jù)時(shí),通常會(huì)根據(jù)特定規(guī)則來(lái)對(duì)數(shù)據(jù)進(jìn)行一個(gè)排序,然后再將其渲染到頁(yè)面展示給用戶。哪么對(duì)數(shù)據(jù)進(jìn)行排序有很多種方式,哪一種效率高?哪一種穩(wěn)定性好?那一種占用內(nèi)存小?本文將詳解經(jīng)典的八大排序算法以及三種搜索算法,并用TypeScript將其實(shí)現(xiàn),歡迎各位對(duì)上述問(wèn)題迷惑的開(kāi)發(fā)者閱讀本文。

排序算法

我們先來(lái)學(xué)習(xí)下排序算法,八大排序包括:冒泡排序、選擇排序、插入排序、歸并排序、快速排序、計(jì)數(shù)排序、同排序、基數(shù)排序其中有幾個(gè)排序我在之前的文章中已經(jīng)講解了其圖解實(shí)現(xiàn),本文將注重講解其實(shí)現(xiàn),另外幾篇文章鏈接如下:

  • 排序算法:冒泡排序
  • 排序算法:選擇排序
  • 排序算法:插入排序
  • 排序算法:歸并排序
  • 排序算法:快速排序

冒泡排序

冒泡排序是排序算法中最簡(jiǎn)單、最好理解的一個(gè)排序算法,但是從運(yùn)行時(shí)間的角度來(lái)看,它的效率是最低的。本文中所有函數(shù)實(shí)現(xiàn)的代碼地址: Sort.ts

實(shí)現(xiàn)思路

它會(huì)比較相鄰的兩個(gè)項(xiàng),如果第一個(gè)比第二個(gè)大,則交換它們。元素項(xiàng)向上移動(dòng)至正確的順序。為了讓排序算法更靈活,不僅僅只是用于數(shù)字的排序,因此本文中講解的排序算法都會(huì)有一個(gè)比對(duì)函數(shù)作為參數(shù)傳進(jìn)來(lái),用于定義兩個(gè)值的比較方式。

  • 聲明一個(gè)函數(shù)其接受兩個(gè)參數(shù):待排序數(shù)組 & 比對(duì)函數(shù)
  • 第一層循環(huán)i:從待排序數(shù)組的0號(hào)元素開(kāi)始遍歷數(shù)組,遍歷到最后一位,用于控制數(shù)組需要經(jīng)過(guò)多少輪排序
  • 第二層循環(huán)j:從數(shù)組的0號(hào)元素開(kāi)始遍歷,遍歷至數(shù)組的倒數(shù)第二位元素再減去第一層循環(huán)i。
  • 比較大小,在第二層循環(huán)中,將當(dāng)前遍歷到的元素和其下一個(gè)元素比較大小,如果 j > j + 1就交換兩個(gè)元素的位置。

我們通過(guò)一個(gè)例子來(lái)描述下上述思路,如下圖所示,將一個(gè)亂序數(shù)組執(zhí)行冒泡排序后,將其從小到大排列。

實(shí)現(xiàn)代碼

為了讓函數(shù)便于維護(hù),我們新建一個(gè)類(lèi)名為Sort,本文中實(shí)現(xiàn)的所有函數(shù)都為這個(gè)類(lèi)內(nèi)部的方法。

  • 為了函數(shù)的可擴(kuò)展性,我們需要實(shí)現(xiàn)一個(gè)默認(rèn)的比對(duì)函數(shù),此比對(duì)函數(shù)作用于本文中的所有排序方法
export function defaultCompare<T>(a: T, b: T): number {
    if (a === b) {
        return Compare.EQUALS;
    } else if (a > b) {
        return Compare.BIGGER_THAN;
    } else {
        return Compare.LESS_THAN;
    }
}

// 枚舉類(lèi):定義比對(duì)返回值
export enum Compare {
    LESS_THAN = -1,
    BIGGER_THAN = 1,
    EQUALS = 0
}
  • 在類(lèi)中聲明需要的變量,并在構(gòu)造器中聲明需要傳的參數(shù)類(lèi)型,此處適用于本文實(shí)現(xiàn)的所有函數(shù)
    /**
     * 排序算法
     * @param array 需要進(jìn)行排序的數(shù)組
     * @param compareFn 比對(duì)函數(shù)
     */
    constructor(private array: T[] = [], private compareFn: ICompareFunction<T> = defaultCompare) {}
  • 實(shí)現(xiàn)冒泡排序
    // 冒泡排序
    bubbleSort(): void {
        // 獲取數(shù)組長(zhǎng)度
        const { length } = this.array;
        for (let i = 0; i < length; i++) {
            // 從數(shù)組的0號(hào)元素遍歷到數(shù)組的倒數(shù)第2號(hào)元素,然后減去外層已經(jīng)遍歷的輪數(shù)
            for (let j = 0; j < length - 1 - i; j++) {
                // 如果j > j + 1位置的元素就交換他們兩個(gè)元素的位置
                if (this.compareFn(this.array[j], this.array[j + 1]) === Compare.BIGGER_THAN) {
                    this.swap(this.array, j, j + 1);
                }
            }
        }
    }

編寫(xiě)測(cè)試代碼

我們將上圖中的例子放在代碼中,測(cè)試下執(zhí)行結(jié)果是否正確。

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
sort.bubbleSort();
console.log(array.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

選擇排序

選擇排序是一種原址比較排序算法,它的大致思路是找到數(shù)據(jù)結(jié)構(gòu)中的最小值并將其放置在第一位,接著找到第二小的值將其放在第二位,依次類(lèi)推。

實(shí)現(xiàn)思路

  • 聲明一個(gè)輔助變量:indexMin用于存儲(chǔ)數(shù)組中的最小值
  • 第一層循環(huán)i,從數(shù)組的0號(hào)元素遍歷到數(shù)組的倒數(shù)第二位。indexMin默認(rèn)賦值為i,用于控制輪數(shù)
  • 第二層循環(huán)j,從數(shù)組的i號(hào)元素遍歷到數(shù)組的末尾,用于尋找數(shù)組的最小值,如果indexMin位置的元素大于j號(hào)位置的元素就覆蓋indxMin的值為j
  • 在第二層循環(huán)結(jié)束后,比較i與indexMin是否相等,如果不相等就交換兩個(gè)元素的位置。

下圖中的示例描述了上述思路![7d4cc6a280d6d13955475ae268e50a2f](https://www.kaisir.cn/uploads/MarkDownImg/202008140013/Image 3.png)

實(shí)現(xiàn)代碼

    // 選擇排序
    selectionSort(): void {
        const { length } = this.array;
        // 聲明一個(gè)變量用于存儲(chǔ)最小元素的位置
        let indexMin = 0;
        for (let i = 0; i < length; i++) {
            // 初始值為外層循環(huán)當(dāng)前遍歷到的位置i
            indexMin = i;
            for (let j = i; j < length; j++) {
                // 如果當(dāng)前遍歷到元素小于indexMin位置的元素,就將當(dāng)前遍歷到的位置j賦值給indexMin
                if (this.compareFn(this.array[indexMin], this.array[j]) === Compare.BIGGER_THAN) {
                    indexMin = j;
                }
            }
            if (i !== indexMin) {
                this.swap(this.array, i, indexMin);
            }
        }
    }

編寫(xiě)測(cè)試代碼

我們將圖中的示例,放在代碼中,測(cè)試排序函數(shù)是否都正確執(zhí)行。

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
// const result = sort.radixSort(array);
// console.log(result.join());

sort.selectionSort();
console.log(array.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

插入排序

插入排序會(huì)將數(shù)組分為已排序區(qū)域和未排序區(qū)域,將未排序區(qū)域的數(shù)組項(xiàng)和已排序區(qū)域的數(shù)組進(jìn)行大小比較,確立要插入的位置,然后將其插入到對(duì)應(yīng)的位置。

實(shí)現(xiàn)思路

  • 第一層循環(huán)i:從數(shù)組的1號(hào)元素開(kāi)始,遍歷到數(shù)組末尾,因?yàn)槲覀儠?huì)先假設(shè)0號(hào)元素已經(jīng)排好序,所以從1號(hào)元素開(kāi)始。
  • 用一個(gè)臨時(shí)變量temp存儲(chǔ)當(dāng)前i號(hào)位置的元素,用一個(gè)變量j存儲(chǔ)i
  • while循環(huán): j > 0且j - 1位置的元素大于temp,就把j位置的值設(shè)置為j - 1 位置的值,最后j--,繼續(xù)下一輪遍歷。
  • while循環(huán)結(jié)束后,將temp放到正確的位置array[ j ]

如下圖所示,我們通過(guò)一個(gè)例子描述了上述插入排序的過(guò)程

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

接下來(lái)我們將上述思路轉(zhuǎn)換為代碼。

    // 插入排序
    insertionSort(array: T[] = this.array): void {
        const { length } = array;
        let temp;
        // 假設(shè)0號(hào)元素已經(jīng)排好序,從1號(hào)元素開(kāi)始遍歷數(shù)組
        for (let i = 1; i < length; i++) {
            // 聲明輔助變量存儲(chǔ)當(dāng)前i的位置以及其對(duì)應(yīng)的值
            let j = i;
            temp = array[i];
            // j大于0且j-1位置的元素大于i號(hào)位置的元素就把j-1處的值移動(dòng)到j(luò)處,最后j--
            while (j > 0 && this.compareFn(array[j - 1], temp) === Compare.BIGGER_THAN) {
                array[j] = array[j - 1];
                j--;
            }
            // 將temp放到正確的位置
            array[j] = temp;
        }
    }

編寫(xiě)測(cè)試代碼

我們將圖中的例子放在代碼里,執(zhí)行下查看排序函數(shù)是否正常運(yùn)行。

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
sort.insertionSort(array);
console.log(array.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

歸并排序

歸并排序是一個(gè)可以實(shí)際使用的排序算法,在JAVAScript中Array類(lèi)定義了一個(gè)Sort函數(shù),用以排序JavaScript數(shù)組。ECMScript沒(méi)有定義用哪個(gè)排序算法,所以瀏覽器廠商可以自己去實(shí)現(xiàn)算法。谷歌瀏覽器的V8引擎使用快速排序?qū)崿F(xiàn),火狐瀏覽器使用歸并排序?qū)崿F(xiàn)。

實(shí)現(xiàn)思路

歸并排序是一個(gè)分而治之算法,就是將原始數(shù)組切分成比較小的數(shù)組,直到每個(gè)小數(shù)組只有一個(gè)位置,接著將小數(shù)組歸并成比較大的數(shù)組,直到最后只有一個(gè)排序完畢的大數(shù)組。由于是分治法,歸并排序也是遞歸的。我們要將算法分為兩個(gè)函數(shù):一個(gè)用于將函數(shù)分割成小數(shù)組,一個(gè)用于將小數(shù)組合并成大數(shù)組。我們先來(lái)看看主函數(shù),將數(shù)組分割成小數(shù)組。

  • 遞歸終止條件:由于是遞歸,所以我們需要先設(shè)立遞歸終止條件,當(dāng)數(shù)組的長(zhǎng)度大于1時(shí)就進(jìn)行歸并排序。
  • 獲取數(shù)組的中間值: 我們通過(guò)數(shù)組的中間值來(lái)切割數(shù)組,將其分成左、右兩部分。對(duì)左右兩部分繼續(xù)執(zhí)行分割
  • 合并數(shù)組: 我們將數(shù)組分割完后,對(duì)小數(shù)組進(jìn)行排序,然后將其合并成大數(shù)組并返回。

接下來(lái),我們?cè)賮?lái)看下合并函數(shù)的實(shí)現(xiàn)

  • 合并函數(shù)接收兩個(gè)參數(shù):左、右數(shù)組
  • 聲明三個(gè)輔助變量: i, j, result,分別表示左、右數(shù)組的指針以及存儲(chǔ)合并后的數(shù)組
  • 如果i < left.length && j < right.length,代表指針數(shù)組尚未排序完,因此執(zhí)行下述邏輯
    • 如果lef[i] < right[j],就往result中放left[i]的值隨后i++,否則就放right[j]的值隨后j++
  • 最后,將left或right數(shù)組的剩余項(xiàng)添加進(jìn)result中

下圖用一個(gè)例子描述了上述歸并排序的過(guò)程

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

接下來(lái),我們將上述思路轉(zhuǎn)換為代碼。

    // 歸并排序
    mergeSort(array: T[] = this.array): T[] {
        if (array.length > 1) {
            const { length } = array;
            // 獲取中間值
            const middle = Math.floor(length / 2);
            // 遞歸填充左右數(shù)組
            const left = this.mergeSort(array.slice(0, middle));
            const right = this.mergeSort(array.slice(middle, length));
            // 合并左右數(shù)組
            array = this.merge(left, right);
        }
        return array;
    }

    private merge(left: T[], right: T[]) {
        let i = 0;
        let j = 0;
        const result: T[] = [];
        while (i < left.length && j < right.length) {
            // 如果left[i] < right[j]將left[i]加進(jìn)result中,隨后i自增,否則把right[j]加進(jìn)result中,隨后j自增
            result.push(this.compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++]);
        }
        // 將left或right數(shù)組的剩余項(xiàng)添加進(jìn)result中
        return result.concat(i < left.length ? left.slice(i) : right.slice(j));
    }

編寫(xiě)測(cè)試代碼

我們將上圖中的例子放到代碼中執(zhí)行用以驗(yàn)證我們的函數(shù)是否正確執(zhí)行

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
const result = sort.mergeSort();
console.log(result.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

快速排序

快速排序與歸并排序一樣都是可用作實(shí)際應(yīng)用的算法,它也是使用分而治之的方法,將原始數(shù)組分為較小的數(shù)組,但它沒(méi)有像歸并排序那樣將他們分割開(kāi)。

實(shí)現(xiàn)思路

我們需要三個(gè)函數(shù):主函數(shù)、排序函數(shù)、切分函數(shù)。

  • 主函數(shù)(quickSort),調(diào)用排序函數(shù),返回排序函數(shù)最終排好的數(shù)組。
  • 排序函數(shù)(quick),接收3個(gè)參數(shù): 待排序數(shù)組(array)、數(shù)組的左邊界(left)、數(shù)組的右邊界(right)
    • 聲明一個(gè)輔助變量index,用于存儲(chǔ)分割子數(shù)組的的位置。
    • 確立遞歸終止條件:array < 1 ,如果array.length > 1,就執(zhí)行下述操作:
      執(zhí)行劃分函數(shù),計(jì)算劃分點(diǎn),將得到的值賦值給index;
      如果left > index - 1,代表子數(shù)組中存在較小值的元素,從數(shù)組的left邊界到index-1邊界遞歸執(zhí)行排序函數(shù);
      如果index < right,代表子數(shù)組存在較大值的元素,從數(shù)組的index到right邊界遞歸執(zhí)行排序函數(shù);
  • 劃分函數(shù)(partition),與排序函數(shù)一樣,它也接收3個(gè)參數(shù)。
    • 從數(shù)組中選擇一個(gè)主元pivot,選擇數(shù)組的中間值
    • 聲明兩個(gè)指針:i, j,分別指向左邊數(shù)組的第一個(gè)值和右邊數(shù)組的第一個(gè)值。
    • 如果i <= j,則代表left指針和right指針沒(méi)有相互交錯(cuò),則執(zhí)行下述劃分操作:
      移動(dòng)left指針直至找到一個(gè)比主元大的元素,即array[i] > pivot;
      移動(dòng)right指針直至找到一個(gè)比主元小的元素,即array[j] < pivot;
      當(dāng)左指針指向的元素比主元大且右指針指向的元素比主元小,并且左指針?biāo)饕龥](méi)有右指針?biāo)饕髸r(shí)就交換i號(hào)和j號(hào)元素的位置,隨后移動(dòng)兩個(gè)指針;
      最后,劃分結(jié)束,返回i的值;

實(shí)現(xiàn)代碼

接下來(lái)我們將上述思路轉(zhuǎn)換為代碼:

    /**
     *
     * @param array 待排序數(shù)組
     * @param left 左邊界
     * @param right 右邊界
     * @private
     */
    private quick(array: T[], left: number, right: number) {
        // 該變量用于將子數(shù)組分離為較小值數(shù)組和較大值數(shù)組
        let index;
        if (array.length > 1) {
            // 對(duì)給定子數(shù)組執(zhí)行劃分操作,得到正確的index
            index = this.partition(array, left, right);
            // 如果子數(shù)組存在較小值的元素,則對(duì)該數(shù)組重復(fù)這個(gè)過(guò)程
            if (left < index - 1) {
                this.quick(array, left, index - 1);
            }
            // 如果子數(shù)組存在較大值的元素,也對(duì)該數(shù)組重復(fù)這個(gè)過(guò)程
            if (index < right) {
                this.quick(array, index, right);
            }
        }
        return array;
    }

    // 劃分函數(shù)
    private partition(array: T[], left: number, right: number): number {
        // 從數(shù)組中選擇一個(gè)值做主元,此處選擇數(shù)組的中間值
        const pivot = array[Math.floor((right + left) / 2)];
        // 創(chuàng)建數(shù)組引用,分別指向左邊數(shù)組的第一個(gè)值和右邊數(shù)組的第一個(gè)值
        let i = left;
        let j = right;

        // left指針和right指針沒(méi)有相互交錯(cuò),就執(zhí)行劃分操作
        while (i <= j) {
            // 移動(dòng)left指針直至找到一個(gè)比主元大的元素
            while (this.compareFn(array[i], pivot) === Compare.LESS_THAN) {
                i++;
            }

            // 移動(dòng)right指針直至找到一個(gè)比主元小的元素
            while (this.compareFn(array[j], pivot) === Compare.BIGGER_THAN) {
                j--;
            }

            // 當(dāng)左指針指向的元素比主元大且右指針指向的元素比主元小,并且左指針?biāo)饕龥](méi)有右指針?biāo)饕髸r(shí)就交換i和j號(hào)元素的位置,隨后移動(dòng)兩個(gè)指針
            if (i <= j) {
                this.swap(array, i, j);
                i++;
                j--;
            }
        }
        // 劃分結(jié)束,返回左指針?biāo)饕?        return i;
    }

編寫(xiě)測(cè)試代碼

我們通過(guò)一個(gè)例子,來(lái)測(cè)試下上述代碼是否都正確執(zhí)行。

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
const result = sort.quickSort();
console.log(result.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

計(jì)數(shù)排序

計(jì)數(shù)排序是一個(gè)分布式排序,它是用來(lái)排序整數(shù)的優(yōu)秀算法。分布式排序使用已組織好的輔助數(shù)據(jù)結(jié)構(gòu),然后進(jìn)行合并,得到排好序的數(shù)組。計(jì)數(shù)排序使用一個(gè)用來(lái)存儲(chǔ)每個(gè)元素在原始數(shù)組中出現(xiàn)次數(shù)的臨時(shí)數(shù)組。在所有元素都計(jì)數(shù)完成后,臨時(shí)數(shù)組已排好序并可迭代以構(gòu)建排序后的結(jié)果數(shù)組。

實(shí)現(xiàn)思路

  • 找到要排序數(shù)組的最大值
  • 以上一步找到的最大值+1為長(zhǎng)度創(chuàng)建一個(gè)計(jì)數(shù)數(shù)組
  • 遍歷要排序的數(shù)組,以當(dāng)前遍歷的元素為索引,尋找計(jì)數(shù)數(shù)組中對(duì)應(yīng)的位置將其初始化為0,如果此處位置有相同元素的話就自增
  • 遍歷計(jì)數(shù)數(shù)組,判斷當(dāng)前遍歷到的元素是否大于0,如果大于0就取當(dāng)前遍歷到的索引,替換待排序數(shù)組中的元素

接下來(lái)我們通過(guò)一個(gè)例子來(lái)講解下上述過(guò)程,如下圖所示:

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

接下來(lái)我們將上述思路轉(zhuǎn)換為代碼

    countingSort(array: number[]): number[] {
        // 待排序數(shù)組為空或只有一個(gè)元素則不用排序
        if (array.length < 2) {
            return array;
        }
        // 找到待排序數(shù)組中的最大值
        const maxValue = this.findMaxValue(array);

        // 創(chuàng)建計(jì)數(shù)數(shù)組,數(shù)組長(zhǎng)度為待排序數(shù)組的最大值+1
        const counts = new Array(maxValue + 1);
        // 遍歷待排序數(shù)組,為計(jì)數(shù)數(shù)組賦值
        array.forEach((element) => {
            // 以當(dāng)前遍歷到的元素值為索引將對(duì)應(yīng)位置元素值初始化為0
            if (!counts[element]) {
                counts[element] = 0;
            }
            // 當(dāng)前位置的值進(jìn)行自增,順便應(yīng)對(duì)數(shù)組中有重復(fù)值的情況,有重復(fù)值時(shí)當(dāng)前位置的值必定大于1
            counts[element]++;
        });

        // 聲明一個(gè)變量用于數(shù)組最終排序
        let sortedIndex = 0;
        // 遍歷計(jì)數(shù)數(shù)組,根據(jù)計(jì)數(shù)數(shù)組的元素位置對(duì)待排序數(shù)組進(jìn)行排序
        counts.forEach((count, i) => {
            // 如果當(dāng)前遍歷到的元素值大于0,則執(zhí)行替換操作進(jìn)行排序
            while (count > 0) {
                // 將當(dāng)前元素索引賦值給array的sortedIndex號(hào)元素,隨后sortedIndex自增
                array[sortedIndex++] = i;
                // 當(dāng)前元素值自減,如果其值大于1,證明此處有重復(fù)元素,那么我們就繼續(xù)執(zhí)行while循環(huán)
                count--;
            }
        });
        // 最后,排序完成,返回排序好的數(shù)組
        return array;
    }

編寫(xiě)測(cè)試代碼

我們將上述圖中的例子放進(jìn)代碼中執(zhí)行,看看我們寫(xiě)的函數(shù)是否正確執(zhí)行。

const array = [12, 6, 3, 4, 1, 7];
const sort = new Sort(array);
const result = sort.countingSort(array);
console.log(result.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

桶排序

桶排序也是一種分布式排序算法,它將元素分為不同的桶,再使用一個(gè)簡(jiǎn)單的排序算法,例如插入排序,來(lái)對(duì)每個(gè)桶進(jìn)行排序,最后,它將所有的桶合并為結(jié)果數(shù)組。

實(shí)現(xiàn)思路

首先,我們需要指定需要多個(gè)桶來(lái)排序各個(gè)元素,默認(rèn)情況,我們會(huì)使用5個(gè)桶。桶排序在所有元素平分到各個(gè)桶中時(shí)的表現(xiàn)最好。如果元素非常稀疏,則使用更多的桶會(huì)更好。如果元素非常密集,則使用較少的桶會(huì)更好。因此我們?yōu)榱怂惴ǖ男剩瑫?huì)讓調(diào)用者根據(jù)實(shí)際需求將桶的數(shù)量作為參數(shù)傳進(jìn)來(lái)。我們將算法分為兩個(gè)部分:

  • 創(chuàng)建桶,并將桶分布到不同的桶中
  • 對(duì)每個(gè)桶中的元素執(zhí)行排序算法并將所有桶合并成排序好后的結(jié)果數(shù)組

我們先來(lái)看看創(chuàng)建桶的思路

  • 聲明創(chuàng)建桶函數(shù)(createBuckets),接收兩個(gè)參數(shù):待排序數(shù)組(array),桶大小(bucketSize)
  • 計(jì)算array中的最大值(maxValue)和最小值(minValue)
  • 計(jì)算每個(gè)需要的桶數(shù)量,公式為:(maxValue - minValue) / bucketSize)+1
  • 聲明一個(gè)二維數(shù)組buckets,用于存放所有桶
  • 根據(jù)桶數(shù)量,初始化每個(gè)桶
  • 遍歷array,將每個(gè)元素分布到桶中
    • 計(jì)算需要將元素放到哪個(gè)桶中(bucketIndex),公式為:(array[i] - minValue) / bucketSize
    • 將元素與放進(jìn)合適的桶中,即buckets[bucketIndex].push(array[i])
  • 將桶返回

接下來(lái)我們來(lái)看看對(duì)每個(gè)桶里的元素進(jìn)行排序的思路

  • 創(chuàng)建排序桶函數(shù)(sortBuckets),接收一個(gè)參數(shù): 桶buckets
  • 需要一個(gè)輔助數(shù)組sortedArray,用于存放排序好的結(jié)果數(shù)組
  • 遍歷sortedArray
    • 如果當(dāng)前桶內(nèi)存儲(chǔ)的桶buckets[i]不為null,就對(duì)其執(zhí)行插入排序
    • 將排序好的桶buckets[i]解構(gòu)后放進(jìn)sortedArray中
  • 最后,返回sortedArray

我們通過(guò)一個(gè)例子,來(lái)講解上述思路,如下圖所示

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

接下來(lái),我們將上述思路轉(zhuǎn)換為代碼。

    // 桶排序
    bucketSort(array: number[], bucketSize = 5): T[] | number[] {
        if (array.length < 2) {
            return array;
        }
        // 創(chuàng)建桶,對(duì)桶進(jìn)行排序
        return this.sortBuckets(<[][]>this.createBuckets(array, bucketSize));
    }

    /**
     * 創(chuàng)建桶
     * @param array 待排序數(shù)組
     * @param bucketSize 桶大小,即每個(gè)桶里的元素?cái)?shù)量
     *
     * @return 二維數(shù)組類(lèi)型的桶
     */
    private createBuckets = (array: number[], bucketSize: number): number[][] => {
        // 計(jì)算數(shù)組最大值與最小值
        let minValue = array[0];
        let maxValue = array[0];
        for (let i = 1; i < array.length; i++) {
            if (array[i] < minValue) {
                minValue = array[i];
            } else if (array[i] > maxValue) {
                maxValue = array[i];
            }
        }

        // 計(jì)算需要的桶數(shù)量,公式為: 數(shù)組最大值與最小值的差值與桶大小進(jìn)行除法運(yùn)算
        const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
        // 用于存放桶的二維數(shù)組
        const buckets: number[][] = [];

        // 計(jì)算出桶數(shù)量后,初始化每個(gè)桶
        for (let i = 0; i < bucketCount; i++) {
            buckets[i] = [];
        }

        // 遍歷數(shù)組,將每個(gè)元素分布到桶中
        for (let i = 0; i < array.length; i++) {
            // 計(jì)算需要將元素放到哪個(gè)桶中,公式為: 當(dāng)前遍歷到的元素值與數(shù)組的最小值的差值與桶大小進(jìn)行除法運(yùn)算
            const bucketIndex: number = Math.floor((array[i] - minValue) / bucketSize);
            // 將元素放進(jìn)合適的桶中
            buckets[bucketIndex].push(array[i]);
        }
        // 將桶返回
        return buckets;
    };

    // 對(duì)每個(gè)桶進(jìn)行排序
    private sortBuckets(buckets: T[][]): T[] {
        const sortedArray: T[] = [];
        for (let i = 0; i < buckets.length; i++) {
            if (buckets[i] != null) {
                // 調(diào)用插入排序
                this.insertionSort(buckets[i]);
                // 將排序好的桶取出來(lái),放進(jìn)sortedArray中
                sortedArray.push(...buckets[i]);
            }
        }
        return sortedArray;
    }

編寫(xiě)測(cè)試代碼

我們將上述圖中的例子放進(jìn)代碼中看其是否能正確執(zhí)行。

const array = [12, 5, 6, 7, 8, 9, 11, 3, 4, 19];
const sort = new Sort(array);
const result = sort.bucketSort(array);
console.log(result.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

基數(shù)排序

基數(shù)排序也是一個(gè)分布式排序算法,它根據(jù)數(shù)字的有效位或基數(shù)將整數(shù)分布到桶中。比如,十進(jìn)制數(shù),使用的基數(shù)是10.因此,算法將會(huì)使用10個(gè)桶用來(lái)分布元素并且首先基于各位數(shù)字進(jìn)行排序,然后基于十位數(shù)字,然后基于百位數(shù)字,以此類(lèi)推。

實(shí)現(xiàn)思路

基數(shù)排序也是用來(lái)排序整數(shù),因此我們從最后一位開(kāi)始排序所有的數(shù)。首先,只會(huì)基于最后一位有效位對(duì)數(shù)字進(jìn)行排序,在下次迭代時(shí),我們會(huì)基于第二個(gè)有效位進(jìn)行排序(十位數(shù)字),然后是第三個(gè)有效位(百位數(shù)字),以此類(lèi)推。我們繼續(xù)這個(gè)過(guò)程直到?jīng)]有待排序的有效位,因此我們需要知道數(shù)組中的最小值和最大值。實(shí)現(xiàn)基數(shù)排序我們需要一個(gè)輔助函數(shù)(countingSortForRadix),即根據(jù)有效位對(duì)數(shù)組進(jìn)行排序。接下來(lái),我們先來(lái)看下基數(shù)排序主函數(shù)的實(shí)現(xiàn)思路。

  • 創(chuàng)建基數(shù)排序函數(shù)(radixSort),接受2個(gè)參數(shù):待排序數(shù)組array,基數(shù)radixBash
  • 首先,獲取array的最大值maxValue和最小值minValue
  • 聲明一個(gè)輔助變量significantDigit,用于存儲(chǔ)當(dāng)前有效數(shù)字,默認(rèn)從最后一位有效數(shù)字,即1
  • 計(jì)算有效位,公式為:(maxValue - minValue) / significantDigit,計(jì)算出的值如果大于等于1執(zhí)行下述邏輯:
    • 以當(dāng)前有效位作為參數(shù)調(diào)用singnificantDigit函數(shù)對(duì)數(shù)組進(jìn)行排序
    • 當(dāng)前有效數(shù)字*基數(shù),繼續(xù)執(zhí)行上述過(guò)程
  • 最后,執(zhí)行完成返回排序好多的數(shù)組

接下來(lái),我們來(lái)看下基于有效位進(jìn)行排序的函數(shù)實(shí)現(xiàn)思路。

  • 創(chuàng)建countingSortForRadix函數(shù),接受4個(gè)參數(shù):待排序數(shù)組array,基數(shù)radixBase、有效位significantDigit、數(shù)組的最小值minValue
  • 聲明桶索引bucketsIndex以及桶buckets以及輔助數(shù)組aux
  • 通過(guò)radixBase來(lái)初始化桶,默認(rèn)初始化為0
  • 遍歷array,基于有效位計(jì)算桶索引執(zhí)行計(jì)數(shù)排序
    • 計(jì)算桶索引,公式為:((array[i] - minValue) / significantDigt) % radixBase
    • 根據(jù)桶索引,執(zhí)行計(jì)數(shù)操作,即buckets[bucketsIndex++]
  • 計(jì)算累積結(jié)果得到正確的計(jì)數(shù)值,從1開(kāi)始遍歷到radixBase位置。
    • buckets[i]等于buckets[i] 加上buckrts[i - 1]的值
  • 計(jì)數(shù)完成,遍歷array將值移回原始數(shù)組中,用aux輔助數(shù)組來(lái)存儲(chǔ)
    • 計(jì)算當(dāng)前元素的桶索引bucketsIndex,公式為:((array[i] - minValue) / significantDigit) % radixBase
    • 對(duì)當(dāng)前桶索引內(nèi)的元素執(zhí)行自檢操作,得到其在數(shù)組中的正確位置index
    • 計(jì)算出索引后,在aux中的對(duì)應(yīng)位置存儲(chǔ)當(dāng)前遍歷到的元素
  • 最后排序完成,返回axu。

我們通過(guò)一個(gè)例子來(lái)描述上述過(guò)程,如下圖所示。

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

接下來(lái),我們將上述思路轉(zhuǎn)換為代碼.

    /**
     * 基數(shù)排序
     * @param array 待排序數(shù)組
     * @param radixBase 10進(jìn)制排序,基數(shù)為10
     */
    radixSort(array: number[], radixBase = 10): number[] {
        if (array.length < 2) {
            return array;
        }
        // 獲取數(shù)組的最小值和最大值
        const minValue = this.findMinValue(array);
        const maxValue = this.findMaxValue(array);
        // 當(dāng)前有效數(shù)字,默認(rèn)會(huì)從最后一位有效數(shù)字開(kāi)始排序
        let significantDigit = 1;

        /**
         * 計(jì)算有效位
         * 最大值和最小值的差與有效數(shù)字進(jìn)行除法運(yùn)算,其值大于等于1就代表還有待排序的有效位
         */
        while ((maxValue - minValue) / significantDigit >= 1) {
            // 以當(dāng)前有效位為參數(shù)對(duì)數(shù)組進(jìn)行排序
            array = this.countingSortForRadix(array, radixBase, significantDigit, minValue);
            // 當(dāng)前有效數(shù)字乘以基數(shù),繼續(xù)執(zhí)行while循環(huán)進(jìn)行基數(shù)排序
            significantDigit *= radixBase;
        }
        return array;
    }

    /**
     * 基于有效位進(jìn)行排序
     * @param array 待排序數(shù)組
     * @param radixBase 基數(shù)
     * @param significantDigit 有效位
     * @param minValue 待排序數(shù)組的最小值
     */
    private countingSortForRadix = (array: number[], radixBase: number, significantDigit: number, minValue: number) => {
        // 聲明桶索引以及桶
        let bucketsIndex;
        const buckets = [];
        // 輔助數(shù)組,用于計(jì)數(shù)完成的值移動(dòng)會(huì)原數(shù)組
        const aux = [];
        // 通過(guò)基數(shù)來(lái)初始化桶
        for (let i = 0; i < radixBase; i++) {
            buckets[i] = 0;
        }

        // 遍歷待排序數(shù)組,基于有效位計(jì)算桶索引執(zhí)行計(jì)數(shù)排序
        for (let i = 0; i < array.length; i++) {
            // 計(jì)算桶索引
            bucketsIndex = Math.floor(((array[i] - minValue) / significantDigit) % radixBase);
            // 執(zhí)行計(jì)數(shù)操作
            buckets[bucketsIndex]++;
        }

        // 計(jì)算累積結(jié)果得到正確的計(jì)數(shù)值
        for (let i = 1; i < radixBase; i++) {
            buckets[i] = buckets[i] + buckets[i - 1];
        }

        // 計(jì)數(shù)完成,將值移回原始數(shù)組中,用aux輔助數(shù)組來(lái)存儲(chǔ)
        for (let i = array.length - 1; i >= 0; i--) {
            // 計(jì)算當(dāng)前元素的桶索引
            bucketsIndex = Math.floor(((array[i] - minValue) / significantDigit) % radixBase);
            // 對(duì)當(dāng)前桶索引內(nèi)的元素執(zhí)行自減操作,得到其在數(shù)組中的正確的位置
            const index = --buckets[bucketsIndex];
            // 計(jì)算出索引后,在aux中的對(duì)應(yīng)位置存儲(chǔ)當(dāng)前遍歷到的元素
            aux[index] = array[i];
        }
        console.log(aux);
        return aux;
    };

編寫(xiě)測(cè)試代碼

接下來(lái),我們將上圖中的例子放進(jìn)代碼中,觀察函數(shù)是否正確執(zhí)行。

const array = [12, 5, 6, 7, 8, 9, 11, 3, 4, 19];
const sort = new Sort(array);
const result = sort.radixSort(array);
console.log(result.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

搜索算法

接下來(lái),我們來(lái)學(xué)習(xí)搜索算法,搜索算法分為兩種:順序(線性)搜索和二分搜索。在之前文章中,我已經(jīng)詳細(xì)講解了這兩種搜索算法的基礎(chǔ)原理以及圖解實(shí)現(xiàn),所以此處只講其代碼實(shí)現(xiàn)。文章傳送門(mén):數(shù)組查找: 線性查找與二分查找本文示例代碼地址:SearchArithmetic.ts

順序搜索

順序搜索是最基本的搜索算法,它會(huì)將每一個(gè)數(shù)據(jù)結(jié)構(gòu)中的元素和我們要找的元素做比較。它也是最低效的一種搜索算法。

實(shí)現(xiàn)代碼

    linearSearch(): number | null {
        for (let i = 0; i < this.array.length; i++) {
            if (this.array[i] === this.target) {
                return i;
            }
        }
        return null;
    }

編寫(xiě)測(cè)試代碼

const array = [7, 8, 1, 2, 3, 5, 12, 13, 16, 19];
const searchArithmetic = new SearchArithmetic(array, 7);
const mid = searchArithmetic.linearSearch();
console.log(mid);
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

二分搜索

二分搜索要求被搜索的數(shù)據(jù)結(jié)構(gòu)已經(jīng)排好序,它的基本原理就是找到數(shù)組的中間值,然后將目標(biāo)值和找到的值進(jìn)行大小比較,如果比中間值大就往中間值的右邊找,否則就往中間值的左邊找。

實(shí)現(xiàn)思路

  • 選擇數(shù)組的中間值
  • 如果選中值是待搜索值,那么算法執(zhí)行完畢
  • 如果待搜索值比選中值要小,則返回步驟1并在選中值左邊的子數(shù)組中尋找(較小)
  • 如果待搜索值比選中值要大,則返回步驟1并在選中值右邊的子數(shù)組中尋找(較大)

實(shí)現(xiàn)代碼

    binarySearch(): number | null {
        // 對(duì)數(shù)組進(jìn)行排序
        this.sort.quickSort();
        // 設(shè)置指針作為數(shù)組邊界
        let low = 0;
        let high = this.array.length - 1;
        while (low <= high) {
            // 獲取數(shù)組中間值
            const mid = Math.floor((low + high) / 2);
            // 獲取數(shù)組的中間值
            const element = this.array[mid];
            // 如果數(shù)組中間值小于目標(biāo)值,low+1,向其右邊繼續(xù)找
            if (this.compareFn(element, this.target) === Compare.LESS_THAN) {
                low = mid + 1;
            } else if (this.compareFn(element, this.target) === Compare.BIGGER_THAN) {
                // 如果中間值大于目標(biāo)值,向其左邊繼續(xù)找
                high = mid - 1;
            } else {
                // 中間值等于目標(biāo)值,元素找到,返回mid即當(dāng)前元素在數(shù)組的位置
                return mid;
            }
        }
        // 未找到,返回null
        return null;
    }

編寫(xiě)測(cè)試代碼

const array = [7, 8, 1, 2, 3, 5, 12, 13, 16, 19];
const searchArithmetic = new SearchArithmetic(array, 7);
const mid = searchArithmetic.binarySearch();
console.log(mid);
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

內(nèi)插搜索

內(nèi)插搜索是改良版的二分搜索。二分搜索總是檢查mid位置上的值,而內(nèi)插搜索可能會(huì)根據(jù)要搜索的值檢查數(shù)組中的不同地方。

實(shí)現(xiàn)思路

它遵循以下步驟:

  • 使用position公式選中一個(gè)值
  • 如果待搜索值比選中值要小,則返回步驟1并在選中值左邊的子數(shù)組中尋找(較小)
  • 如果待搜索值比選中值要大,則返回步驟1并在選中值右邊的子數(shù)組中尋找(較大)

實(shí)現(xiàn)代碼

    /**
     * 內(nèi)插搜索
     *  二分查找的優(yōu)化,通過(guò)特定算法計(jì)算delta和position的值優(yōu)化掉二分查找的尋找中間值做法
     * @param equalsFn 校驗(yàn)兩個(gè)值是否相等函數(shù)
     * @param diffFn 計(jì)算兩個(gè)數(shù)的差值函數(shù)
     */
    interpolationSearch(equalsFn = defaultEquals, diffFn: IDiffFunction<T> = defaultDiff): number | null {
        // 排序
        this.sort.quickSort();
        // 獲取數(shù)組程度
        const { length } = this.array;
        // 定義指針,確定數(shù)組邊界
        let low = 0;
        let high = length - 1;
        // 聲明position,用于公式
        let position = -1;
        let delta = -1;
        // 目標(biāo)值大于等于數(shù)組的low邊界值且目標(biāo)值小于等于high邊界值就執(zhí)行循環(huán)里的內(nèi)容
        while (low <= high && biggerEquals(this.target, this.array[low], this.compareFn) && lesserEquals(this.target, this.array[high], this.compareFn)) {
            // 目標(biāo)值與array的low邊界的值做差
            // 與array的high邊界的值和low邊界的值做差
            // 最后將二者得出的值做除法運(yùn)算,計(jì)算出delta值
            delta = diffFn(this.target, this.array[low]) / diffFn(this.array[high], this.array[low]);
            // 計(jì)算比較值的位置
            position = low + Math.floor((high - low) * delta);
            // 如果比較值位置的元素等于目標(biāo)值,則返回當(dāng)前索引
            if (equalsFn(this.array[position], this.target)) {
                return position;
            }
            // 如果比較值位置的元素小于目標(biāo)值,則向其右邊繼續(xù)找
            if (this.compareFn(this.array[position], this.target) === Compare.LESS_THAN) {
                low = position + 1;
            } else {
                // 如果比較值位置的元素大于目標(biāo)值,則向其左邊繼續(xù)查找
                high = position - 1;
            }
        }
        // 未找到
        return null;
    }

編寫(xiě)測(cè)試代碼

const array = [7, 8, 1, 2, 3, 5, 12, 13, 16, 19];
const searchArithmetic = new SearchArithmetic(array, 7);
const mid = searchArithmetic.interpolationSearch();
console.log(mid);
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

隨機(jī)算法

在日常開(kāi)發(fā)中,我們會(huì)遇到將數(shù)組中的元素打亂位置,這樣的場(chǎng)景,那么此時(shí)我們就需要設(shè)計(jì)一種隨機(jī)算法來(lái)實(shí)現(xiàn)了,現(xiàn)實(shí)中一個(gè)很常見(jiàn)的場(chǎng)景就是洗撲克牌。

Fisher-Yates算法

此處我們講解一種隨機(jī)算法,名為Fisher-Yates,顧名思義,該算法就是由Fisher 和 Yates 創(chuàng)造。

實(shí)現(xiàn)思路

該算法的核心思想就是,從數(shù)組的最后一位開(kāi)始迭代數(shù)組,將迭代到的元素和一個(gè)隨機(jī)位置進(jìn)行交換。這個(gè)隨機(jī)位置比當(dāng)前位置小。這樣這個(gè)就算法可以保證隨機(jī)過(guò)的位置不會(huì)再被隨機(jī)一次。下圖展示了這個(gè)算法的操作過(guò)程

TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

實(shí)現(xiàn)代碼

export class ShuffleArithmetic<T> {
    constructor(private array: T[]) {}
    // Fisher-Yates隨機(jī)算法
    fisherYates(): T[] {
        // 從數(shù)組的最后一位向前遍歷數(shù)組
        for (let i = this.array.length - 1; i > 0; i--) {
            // 計(jì)算隨機(jī)位置,用一個(gè)隨機(jī)數(shù)與i+1相加,得出的隨機(jī)位置一定比當(dāng)前位置小
            const randomIndex = Math.floor(Math.random() * (i + 1));
            // 交換當(dāng)前位置的元素和隨機(jī)位置的元素
            this.swap(i, randomIndex);
        }

        return this.array;
    }

    /**
     * 交換數(shù)組的元素
     * @param a
     * @param b
     * @private
     */
    private swap(a: number, b: number) {
        const temp: T = this.array[a];
        this.array[a] = this.array[b];
        this.array[b] = temp;
    }
}

編寫(xiě)測(cè)試代碼

import { ShuffleArithmetic } from "./lib/ShuffleArithmetic.ts";

const array = [4, 5, 6, 1, 2, 3, 4, 5, 8, 9, 0, 11, 22, 41];
const shuffleArithmetic = new ShuffleArithmetic(array);
shuffleArithmetic.fisherYates();
console.log("隨機(jī)排序后的數(shù)組元素", array.join());
TypeScript實(shí)現(xiàn)八大排序與搜索算法

 

作者:神奇的程序員K

轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/3B8dZRfbIuktSBeArXlmcQ

分享到:
標(biāo)簽:TypeScript
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定