算法之排序的JS实现

178 阅读1分钟

使用javascript实现主要排序算法。

// 测试用例
const list = [2, 5, 4, 1, 9, 7, 10, 8, 3, 6]

1-排序操作

排序涉及比较、交换、拷贝等操作。

// utils/index.js
export default class Comparator {    
    /** 
     * @param {function(a: *,b: *)} [compareFunction]
     */     
    constructor(compareFunction) {        
        this.compare = compareFunction || Comparator.defaultCompareFunction    
    }    
    /**     
    * @param {(string|number)} a     
    * @param {(string|number)} b     
    * @returns {number}     
    */   
    static defaultCompareFunction(a, b) {        
        if (a === b) return 0        
        return a < b ? -1 : 1    
    }   

    equal(a, b) {        
        return this.compare(a, b) === 0    
    }    

    lessThan(a, b) {        
        return this.compare(a, b) < 0    
    } 
       
    greaterThan(a, b) {        
        return this.compare(a, b) > 0    
    }    
  
    lessThanOrEqual(a, b) {       
         return this.lessThan(a, b) || this.equal(a, b)    
    }    
    greaterThanOrEqual(a, b) {        
        return this.greaterThan(a, b) || this.equal(a, b)    
    }
} 
export function swap(fromIndex, toIndex, list) {
     const temp = list[fromIndex]
     list[fromIndex] = list[toIndex]
     list[toIndex] = temp
}

const comparator = new Comparator()

export const pairsInOrder = (firstElement, secondElement, ascending = true) => {     
    const lessThan = comparator.lessThan(firstElement, secondElement)     
    const greaterThan = comparator.greaterThan(firstElement, secondElement)
     
    return ascending ? lessThan : greaterThan
}

export function copy(destination, source,  length, sStart = 0 , dStart = 0) {
    
    if (sStart + length > source.length) {       
        length = souce.length - sStart    
    }    

    length = Math.min(destination.length - dStart, length)    
    while (dStart < length) {        
        destination[dStart] = source[sStart]        
        dStart++        
        sStart++    
    }
}

2-选择排序

常见的选择排序:简单选择排序和堆排序。

1-1 简单选择排序

循环遍历列表,依次选择列表中中的最小值进行增序排列, 或最大值进行降序

// sort/select/selection.js
import { pairsInOrder } from "./utils"

export default function selectSort(list, ascending = true) {    
    for (let i = 0; i < list.length - 1; i++) {        
        let temp = list[i]        
        for (let j = i + 1; j < list.length; j++) {            
            if (pairsInOrder(list[j], temp, ascending)) {                
                temp = list[j]               
                list[j] = list[i]                
                list[i] = temp           
             }        
        }    
    }    
    return list
}

1-2 堆排序

构造最大堆进行升序排列,或最小堆进行降序排列。

// sort/select/heap.js
export default class Heap {
    /**     
     * @param {function(a: *,b: *)} [compareFunction]     
     * @param {boolean} ascending --- true 升序,最大堆; false 降序, 最小堆     
     * @returns {number}     
     */    
    constructor(compareFunction, ascending = true) {
        this.heap = []
        this.ascending = ascending        this.lastIndex = -1
        this.comparator = new Comparator(compareFunction)
    }

    getParentIndex(childIndex) {
        return Math.floor((childIndex - 1) / 2) 
    }

    getLeftChildIndex(parentIndex) {
        return 2 * parentIndex + 1
    }

    getRightChildIndex(parentIndex) {
        return this.getLeftChildIndex(parentIndex) + 1
    }

    append(node) {
        this.heap[++this.lastIndex] = node
        this.trickleUp(this.lastIndex)
    }

    swap(fromIndex, toIndex) {
         const temp = this.heap[fromIndex]
         this.heap[fromIndex] = this.heap[toIndex]
         this.heap[toIndex] = temp
    }

    remove() {
        if(this.lastIndex === -1) return null
        const temp = this.heap[0]
        this.swap(0, this.lastIndex--)
        this.trickleDown()
        return temp
    }

    trickleUp(childIndex) {
        if(childIndex === 0) return 
        const parentIndex = this.getParentIndex(childIndex)
        if(this.pairsInOrder(this.heap[parentIndex], this.heap[childIndex])) {
             this.swap(parentIndex, childIndex)
             this.trickleUp(parentIndex)   
        }
   }
   trickleDown(parentIndex = 0) {      
        if (this.lastIndex === -1) return      
        const leftChildIndex = this.getLeftChildIndex(parentIndex)      
        const rightChildIndex = this.getRightChildIndex(parentIndex)   
   
        if (leftChildIndex === this.lastIndex  && this.pairsInOrder(this.heap[parentIndex], this.heap[leftChildIndex])) {          
            this.swap(parentIndex, leftChildIndex)         
             return     
        } 
      
        if (this.comparator.lessThanOrEqual(this.lastIndex, leftChildIndex) || this.comparator.lessThan(this.lastIndex, rightChildIndex)) return  
    
        if (this.pairsInOrder(this.heap[rightChildIndex], this.heap[leftChildIndex]) && this.pairsInOrder(this.heap[parentIndex], this.heap[leftChildIndex])) {            
            this.swap(parentIndex, leftChildIndex)
            this.trickleDown(leftChildIndex)      
        }     
 
       if (this.pairsInOrder(this.heap[leftChildIndex], this.heap[rightChildIndex]) && this.pairsInOrder(this.heap[parentIndex], this.heap[rightChildIndex])) {           
            this.swap(parentIndex, rightChildIndex)
            this.trickleDown(rightIndex)      
       }   
    }

    pairsInOrder(firstElement, secondElement) {
        const lessThan = this.comparator.lessThan(firstElement, secondElement)
        const greaterThan = this.comparator.greaterThan (firstElement, secondElement)

        return this.ascending ? lessThan : greaterThan  
    }
}

3-交换排序

常见的交换排序:冒泡排序和快速排序。

3-1 冒泡排序

// sort/exchange/bubble.js
import { pairsInOrder, swap } from "./utils"

export function bubbleSort(list, ascending) {
    for (let i = 1; i < list.length; i++) {        
        for (let j = 0; j < list.length - i; j++) {           
            if (pairsInOrder(list[j+1], list[j], ascending)) {                
                swap(j, j + 1, list)            
            }        
        }    
    }   
    
    return list
}

export default function improvedBubbleSort(list, ascending) {      
    let exchange = list.length    
    while (exchange) {       
        const bound = exchange        
        exchange = 0        
        for (let i = 0; i < bound; i++) {            
            if (pairsInOrder(list[i + 1], list[i], ascending)) {                
                swap(i, i + 1, list)                
                exchange = i            
            }        
        }    
    }    
    
    return list
}

3-2 快速排序

// sort/excahnge/quick.js
const { swap, pairsInOrder } = require("./compare")

export function quickSort(list, ascending) {    
    const quickPass = (from, to) => {        
        if (to <= from) return        
        let i = j = from        
        while (i < to) {            
            if (pairsInOrder(list[i], list[to], ascending)) {                
                swap(i, j, list)                
                j++            
            }            
           i++        
       }       
       swap(j, to, list)   
     
       quickPass(from, j - 1)       
       quickPass(j + 1, to)    
    }   

     quickPass(0, list.length - 1)    

     return list
}

4-插入排序

4-1 简单插入

// sort/insert/simple.js
const { swap, pairsInOrder } = require("./compare")
export function insertSort(list, ascending) {    
    for (let i = 1; i < list.length; i++) {        
        let temp = list[i]        
        let j = i - 1;        
        while (j >= 0) {            
            if (!pairsInOrder(temp, list[j], ascending)) break            
                swap(j, j + 1, list)            
                j--        
            }        
        list[j + 1] = temp    
    }   
 
    return list
}

4-2 希尔排序

// sort/insert/shell.js
const { swap, pairsInOrder } = require("./compare")

export default function shellSort(list, steps, ascending) {    
    const shellPass = (step) => {        
        for (let i = step; i < list.length; i++) {            
            let temp = list[i]            
            let j = i - step;           
            while (j >= 0) {                
                if (!pairsInOrder(temp, list[j], ascending)) break                
                swap(j, j + step, list)               
                 j -= step            
            }           
            list[j + step] = temp        
        }    
    }     
   
    steps.forEach(s => shellPass(s))

    return list
}

5-归并排序

// sort/merge/index.js
const { pairsInOrder } = require("./compare")

export default function mergeSort(list, ascending) {
    const copyList = Array(list.length)    
    const merge = (low, mid, high) => {
        let i = low, j = mid + 1, k = low    
    
        while (i <= mid && j <= high) {            
            copyList[k++] = pairsInOrder(list[i], list[j], ascending) ? list[i++] : list[j++]        
        }     
   
        while (i <= mid) {            
            copyList[k++] = list[i++]        
        }     
   
        while (j <= high) {            
            copyList[k++] = list[j++]        
        }   
    
        for (let i = low; i <= high; i++) {            
            list[i] = copyList[i]        
        }    
    }    

    const split = (low, high) => {       
        if (high <= low) return        
        const mid = Math.floor((low + high) / 2)       
        split(low, mid)        
        split(mid + 1, high)        
        merge(low, mid, high)    
    }    

    split(0, list.length - 1)   

    return list
}

6-基数排序

       桶排序是一种分类方式的分治思想,排序基数排序是一种特殊的桶排序。设数据规模N,关键字规模M; 当 N >>> M时,桶排序较为适合; 当N <<< M时,基数排序较为适合。基数排序主要包括两个操作:以关键字的范围(桶划分)分布数据,然后根据桶序号并以先进先出的方式收集数据;循环进行上述两个操作。

// sort/radix/index.js
export default function radixSort(list) {    
    // Char a = 97, use with english char to map for index    
    if (!list.length) return list   
 
    const BASE_CHAR_CODE = 97;    
    const ENGLISH_ALPHABET_LENGTH = 26;    
``  const DIGIT_LENGTH = 10;   

    const getMostDigit = (list) => {        
        return Math.floor(Math.log10(Math.max(...list))) + 1    
    }  
  
    const getLongestLength = (list) => {        
        return list.reduce((acc, s) => {            
            return s.length > acc ? s.length : acc        
        }, -Infinity)    
    }    

    //   1234   4在第1位,3在第2位,依次类推 nth >= 1    
    const getIntBucketIndex = (elem, nth) => {        
        const moded = 10 ** nth       
        const divided = 10 ** (nth - 1)        
        return elem < divided ? 0 : Math.floor((elem % moded) / divided)   
    }    

    const getCharBucketIndex = (elem, nth, length) => {        
        if ((length - nth) > elem.length - 1) {            
            return ENGLISH_ALPHABET_LENGTH - 1        
        }      
  
        const charPos = elem.length < nth ? 0 : elem.length - nth        
        return element.toLowerCase().charCodeAt(charPos) - BASE_CHAR_CODE    
    }    

    const createBuckets = (radix) => {       
         return Array(radix).fill(null).map(_ => [])    
    }    

    const sort = (list) => {        
        let sortedList = [...list]        
        const isInteger = Number.isInteger(list[0])        
        const getBucketIndex = isInteger ? getIntBucketIndex : getCharBucketIndex        
        const len = isInteger ? getMostDigit(list) : getLongestLength(list)        
        for (let i = 1; i <= len; i++) {            
            const buckets = isInteger ? createBuckets(DIGIT_LENGTH) : createBuckets(ENGLISH_ALPHABET_LENGTH)            
            // distribute             
            sortedList.forEach(elem => {               
                 const index = getBucketIndex(elem, i)               
                 buckets[index].push(elem)            
            })            
            // collect            
            sortedList = buckets.reduce((acc, bucket) => {                
                return [...acc, ...bucket]   
            }, [])}    
    
        return sortedList   
     }  
 
     return sort(list)
}