遊戲陪玩系統原始碼中不同排序演算法的實現方式

雲豹科技程式設計師發表於2021-10-26

排序是遊戲陪玩系統原始碼開發中經常會經歷的事,為了實現方便,開發者往往會通過排序演算法實現各個元素的排序,在遊戲陪玩系統原始碼開發中,常見的排序演算法有哪些呢?

1 內部排序

內部排序指待排序完全放在遊戲陪玩系統原始碼記憶體中進行的排序過程,適合不太大的元素序列

  • 插入排序
  • 交換排序
  • 選擇排序
  • 其他內部排序

1.1 插入排序

迴圈將待排序的一個元素插入到已排序的序列中

  • 直接插入排序
  • 折半插入排序
  • 希爾排序

1.1.1 直接插入排序

演算法思想

  • 將原序列分為已排序與未排序的兩部分
  • 外層迴圈遍歷遊戲陪玩系統原始碼整個序列,標記當前待插入元素
  • 內層迴圈遍歷已排序序列,從有序表的尾部開始與當前值進行比較移動

演算法實現

function insertSort(arr) {
    //遍歷整個序列
    for (let i = 0; i < arr.length; i++) {
        //當前待插入值
        let temp = arr[i]
        //遍歷已排序序列,待插入值小則與前值交換
        for (let j = i - 1; j >= 0 && arr[j] > temp; j--) {
            //解構賦值方式交換
            [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
        }
    }
    return arr
}

邊找邊移動

1.1.2 折半插入排序

演算法思想

  • 與直接插入排序的思想基本一致,需要將遊戲陪玩系統原始碼原序列分為已排序和未排序兩部分
  • 不同的在於在比較時不從末尾以此比較,而是使用折半查詢的方式查詢排序位置

演算法實現

function midInsertSort(arr) {
    //折半向下取整
    function absInt(a, b) {
        return Math.abs(parseInt((a + b) / 2))
    }
    for (let i = 1; i < arr.length; i++) {
        let temp = arr[i];
        let left = 0, right = i - 1, mid = absInt(left, right)
        let tag = 0
        //折半查詢插入位置
        while (left < right) {
            if (temp > arr[mid]) {
                left = mid + 1
                mid = absInt(left, right)
            } else if (temp < arr[mid]) {
                right = mid - 1
                mid = absInt(left, right)
            } else break
        }
        //保證穩定性
        if (temp >= arr[mid]) tag = 1
        //移動
        for (let move = mid + tag; move <= i; move++) {
            [arr[move], temp] = [temp, arr[move]]
        }
    }
    return arr
}

其核心是折半查詢(也稱為二分查詢),先通過查詢演算法找到插入位置再統一移動

1.1.3 希爾排序

演算法思想

  • 對遊戲陪玩系統原始碼原序列按照一個增量(間隔)進行分組
  • 對已分組的子序列使用直接插入排序
  • 減小增量再分組然後排序,以此類推
  • 當增量為1時,使用直接插入排序返回結果

演算法實現

function shelltSort(arr) {
    //定義初始增量
    let gap = Math.floor(arr.length / 2)
    //增量減至1時執行最後一次直接插入排序
    while (gap >= 1) {
        //子序列的直接插入排序
        for (let i = gap; i < arr.length; i += gap) {
            let temp = arr[i]
            for (let j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {
                [arr[j], arr[j + gap]] = [arr[j + gap], arr[j]]
            }
        }
        //增量減半
        gap = Math.floor(gap / 2)
    }
    return arr
}

希爾排序的核心排序演算法還是直接插入排序,先按照增量進行分組,組內使用直接插入排序,不斷縮小增量重分組再排序,至到增量縮小為1進行最後一次直接插入排序
如果gap為1其排序的部分就是上面介紹的直接插入排序

1.2 交換排序

  • 氣泡排序
  • 快速排序

1.2.1 氣泡排序

演算法思想

  • 相鄰兩兩比較交換位置,先將最大或最小的值交換至頂端
  • 外層的一次迴圈可以確定一個最大或最小值
  • 根據已經確定的值不斷縮小比較區間,最終返回結果

該演算法是遊戲陪玩系統原始碼開發中最簡單最暴力效能最差的一種排序演算法

演算法實現

function bubbleSort(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
            }
        }
    }
    return arr
}

通過不斷交換,每一趟確定一個值的位置 下一趟該值不再參與排序

1.2.2 快速排序

演算法思想

  • 從序列中選取一個值作為基準
  • 通過不斷的交換,將所有元素中比基準小或大的值放在其兩側
  • 這樣就確定了一個值的位置,稱為一趟換分
  • 再遞迴的將兩側的區間進行同樣的劃分,最終得到排序的序列

演算法實現

function quickSort(arr, low, high) {
    //確定兩側指標
    low = typeof low != 'number' ? 0 : low
    high = typeof high != 'number' ? arr.length - 1 : high;
    //指標重合結束迴圈
    if (low < high) {
        //根據基準進行劃分,返回基準座標
        let pivotIndex = partition(arr, low, high)
        //基準左側遞迴
        quickSort(arr, low, pivotIndex - 1)
        //基準右側遞迴
        quickSort(arr, pivotIndex + 1, high)
    }
    return arr
}
function partition(arr, low, high) {
    //以子序列起點為基準
    let pivot = arr[low]
    //指標重合結束迴圈
    while (low < high) {
        //從右側尋找小於等於基準的值
        while (low < high && arr[high] > pivot) --high
        //將小於等於基準的值前移
        arr[low] = arr[high]
        //從左側找大於基準的值
        while (low < high && arr[low] <= pivot) ++low
        //將大於基準的值後移
        arr[high] = arr[low]
    }
    //將基準放入重合的指標左邊
    arr[low] = pivot
    //返回基準座標 - 左側均小於基準,右側均大於基準
    return low
}

上述快速排序演算法是遊戲陪玩系統原始碼中經典的以子序列為基準來遞迴劃分 也可以以中間值或尾部為基準進行劃分,實現不太相同,思想基本一致

1.3 選擇排序

  • 簡單選擇排序
  • 堆排序

1.3.1 簡單選擇排序

演算法思想

  • 外層遍歷確定位置
  • 內層遍歷尋找子序列中最大或最小的值

也是遊戲陪玩系統原始碼中一種簡單的暴力的效能差的排序演算法,時間複雜度始終是O(n2)

演算法實現

function selectSort(arr) {
    for (let i = 0; i < arr.length - 1; i++) {
        let index = i
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[index]) {
                index = j
            }
        }
        [arr[i], arr[index]] = [arr[index], arr[i]]
    }
    return arr
}

1.3.2 堆排序

演算法思想

  • 根據原序列建立一個完全二叉樹
  • 將完全二叉樹重排為堆(父節點大於/小於子節點)
  • 將堆首和堆尾互換,堆首為最大/最小值,將其選出,堆尺寸縮小1
  • 將互換後的完全二叉樹再重排位堆
  • 重複3 4 只到堆的尺寸為1

演算法實現

function heapSort(arr) {
    let len = arr.length
    //建立一個大根堆 - 從最後一個非葉子節點開始,保證能比較交換出最大值
    for (let i = Math.floor(len / 2 - 1); i >= 0; i--) {
        heapify(arr, i, len)
    }
    //交換當前堆頂與堆尾,堆尺寸減1,調整堆
    for (let i = arr.length - 1; i > 0; i--) {
        [arr[0], arr[i]] = [arr[i], arr[0]]
        len--
        //從對頂調整即可,次大值就是其左右子節點中的一個
        heapify(arr, 0, len)
    }
    return arr
}
function heapify(arr, i, len) {
    //確定父節點的兩個子節點下標
    let left = i * 2 + 1,
        right = i * 2 + 2,
        largest = i
    //找出父節點、左右子節點中的最大值,並標記下標
    if (left < len && arr[left] > arr[largest]) largest = left
    if (right < len && arr[right] > arr[largest]) largest = right
    //最大值不是父節點
    if (largest !== i) {
        //交換父節點與子節點的值
        [arr[largest], arr[i]] = [arr[i], arr[largest]]
        //子節點向下繼續調整
        heapify(arr, largest, len)
    }
}

該演算法的核心在堆的調整 建立堆時從最後一個非葉子節點開始比較交換 首尾互換後,從首節點開始向下比較交換一個方向即可

1.4 其他內部排序

  • 基數排序:根據鍵值的每位資料來分配桶
  • 桶排序:每個桶儲存一定範圍的數值
  • 計數排序:每個桶只儲存單一鍵值

這三種排序演算法都是基於桶,將數值進行分類後再排序 這裡僅介紹基數排序的方式

1.4.1 基數排序

演算法思想

  • 從個位開始按順序入桶
  • 從桶號最小的開始,先進桶的先出桶
  • 再從十位開始入桶出桶的操作,以此類推

演算法實現

function radixSort(arr, maxDigit) {
    //初始化容器,進位制,被除數
    let counter = [],
        mod = 10,
        dev = 1
    //根據最大位數進行遍歷進桶
    for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        //根據當前位按資料進桶
        for (let j = 0; j < arr.length; j++) {
            let bucket = parseInt((arr[j] % mod) / dev)
            if (counter[bucket] == null) {
                counter[bucket] = []
            }
            counter[bucket].push(arr[j])
        }
        let pos = 0
        //從小為桶號開始,先進桶的先出桶
        for (let z = 0; z < counter.length; z++) {
            let value = null
            if (counter[z] != null) {
                while ((value = counter[z].shift()) != null) {
                    arr[pos++] = value
                }
            }
        }
    }
    return arr
}

2 外部排序

  • 歸併排序
  • 多路歸併排序

2.1 歸併排序

演算法思想

  • 自上而下將遊戲陪玩系統原始碼原序列遞迴的分成兩部分,直至左右長度為1
  • 雙指標法逐次比較兩個序列的值,小值新增至合併空間並移動其指標
  • 將另一序列未新增到合併空間的剩餘值與合併空間進行拼接
  • 自下而上的迭代2 3進行合併,最終完成頂層左右序列的合併

演算法實現

function mergeSort(arr) {
    //遞迴分治 - 直到長度為1
    if (arr.length > 1) {
        let middle = Math.floor(arr.length / 2),
            left = mergeSort(arr.slice(0, middle)),
            right = mergeSort(arr.slice(middle))
        arr = merge(left, right)
    }
    return arr
}
function merge(left, right) {
    //定義兩個指標,在左右序列中移動
    let i = 0,
        j = 0,
        result = []
    //雙指標法依次比較兩個序列的值,
    //選擇小值新增到result中,並移動其指標
    //有一個移動結束迴圈結束
    while (i < left.length && j < right.length) {
        result.push(left[i] < right[j] ? left[i++] : right[j++])
    }
    //將另一個指標未移動結束序列的剩餘值與result進行拼接
    return result.concat(i < left.length ? left.slice(i) : right.slice(j))
}

此演算法使用的是自上而下的遞迴,遞迴的深度較深 也可以使用自下而上的迭代演算法

2.2 多路歸併排序

  • 迴圈遍歷
  • 最小堆K路歸併排序
  • 失敗樹K路歸併排序

後續單獨介紹

以上便是“遊戲陪玩系統原始碼開發,常見的排序演算法有哪些?”的全部內容,希望對大家有幫助。

本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:https://segmentfault.com/a/1190000040854168


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2839377/,如需轉載,請註明出處,否則將追究法律責任。

相關文章