JavaScript实现常见排序算法:冒泡,插入,选择,归并,快速,堆排序

Posted cwxblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript实现常见排序算法:冒泡,插入,选择,归并,快速,堆排序相关的知识,希望对你有一定的参考价值。

1.冒泡排序

转自百度百科:

冒泡排序,这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名“冒泡排序”。

冒泡排序算法的运作如下:(从后往前)

 

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

以下过程动图摘自依韵的博客:

/**
 * 冒泡排序:每个元素跟剩下的元素相比,直到冒到剩下的最大
 * 时间复杂度O(n^2);
 * 空间复杂度 O(1);
 * 是否稳定:
 * @param array arr 
 * @returns 
 */
const bubbleSort = function(arr) 
    let arrLen = arr.length;
    for(let i = 0; i < arrLen; i++) 
        for(let j = 0 ; j < arrLen - i; j++) 
            // 相邻元素交换
            if (arr[j] > arr[j+1]) 
                // swap(arr[j], arr[j+1])
                let temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            
        
    
    return arr;

console.time('bubble')
console.log(bubbleSort([3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]))
console.timeEnd('bubble')

2. 选择排序

/*
 * @description: 选择排序,不断在剩余元素中选择最小/最大的元素
 * 时间复杂度O(n^2);
 * 空间复杂度 O(1);
 * 是否稳定:
*/
const selectSort = function(arr) 
    let len = arr.length;
    for(let i=0; i<len; i++) 
        let minNum = arr[i]
        for(let j=i; j<len; j++) 
            if(minNum > arr[j]) 
                // swap(minNum, arr[j])
                let temp = minNum;
                minNum = arr[j];
                arr[j] = temp;
            
        
        arr[i] = minNum
    
    return arr;

console.log(selectSort([2,5,34,6,3]))

3. 插入排序

/*
 * @description: 每个元素插入前面已经拍好序的元素中合适的位置,其他元素依次移动; 适合基本有序队列的排序 
 * 时间复杂度O(n^2);
 * 空间复杂度 O(1);
 * 是否稳定:
*/
const insertSort = function(arr) 
    const n = arr.length;
    for (let i=0; i<n; i++) 
        for (let j=i; j>0; j--) 
            if (arr[j] < arr[j-1]) 
                // swap(arr[j], arr[j-1])
                [arr[j], arr[j-1]] = [arr[j-1], arr[j]]
            
        
    
    return arr;

console.time("time")
console.log(insertSort([3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]))
console.timeEnd('time');

4. 快速排序



/**
 * 快速排序的主要思想是通过划分将待排序的序列分成前后两部分,其中前一部分的数据都比后一部分的数据要小,然后再递归调用函数对两部分的序列分别进行快速排序,以此使整个序列达到有序。
 * 快速排序:  快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
 * 1. 从数列中挑出一个元素,称为 "基准"(pivot);
 * 2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
 * 3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(h)
 * 是否稳定:不稳定
 * @param * arr 
 */
function quickSort(arr) 
    QSort(arr, 0, arr.length-1)


/**
 * 快速排序函数
 * @param * arr 待排序数组
 * @param Number low 最小下标值
 * @param Number high 最大下标值 
 */
function QSort(arr, low, high) 
    let pivot = 0;
    if (low < high) 
        // 获取基准值的下标
        pivot = Partition(arr, low, high);
        
        QSort(arr, low, pivot - 1);
        QSort(arr, pivot + 1, high);
    


/**
 * 切分函数:
 * @param * arr 
 * @param Number low 
 * @param Number high 
 * @returns 基准值下标
 */
function Partition(arr, low, high) 
    // 子数组的第一个值作为基准值,存在性能陷阱(取到最小值,或者最大值),理想情况是取到中间的数值
    // let pivotkey = arr[low];

    // or三数取中法获得基准点(效率好一点)
    let m = low + (high - low) / 2;
    if (arr[low] > arr[high]) 
        swap(arr, low, high);
    
    if (arr[m] > arr[high]) 
        swap(arr, m, high)
    
    if (arr[low] > arr[m]) 
        swap(arr, low, m)
    
    let pivotkey = arr[low];
    // 大于基准点的放在右边,小于基准点的放在左边
    
    while(low < high) 
        while(low < high && arr[high] >= pivotkey) 
            high--;
        
        swap(arr, low, high)
        while(low < high && arr[low] <= pivotkey) 
            low++
        
        swap(arr, low, high)
    
    return low;

/**
 * 交换函数
 */
function swap(arr, a, b) 
    let temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;

let arr = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.time("time")
quickSort(arr);
console.log(arr);
console.timeEnd('time');

5. 堆排序

/**
 * 堆排序
 * 实际上是选择排序的一种,我们可以看到堆的结构是一个完全二叉树,采用一维数组存储
 * 以大顶堆为例
 * 性质:这里用到了完全二叉树性质,对于一个n个节点的完全二叉树的节点按层序变化,对任一节点i (1<=i<=n)。
 *      1. 如果i = 1, 则节点 i 是二叉树的根,无双亲;如果 i>1 ,其双亲节点是Math.floor(i/2),向下取整。
 *      2. 如果 2i > n, 则节点无左孩子; 否则左孩子是节点 2i。
 *      3. 如果 2i+1 > n, 则节点无右孩子,否则右孩子是节点 2i+1。
 *  算法中之所以为Math.floor(n/2) - 1, 因其存储下标从0开始,
 */
function heapSort(arr) 
    /**
     * 建堆
     */
    const n = arr.length;
    // 索引从0开始,最后一个非叶子节点Math.floor(n/2) - 1
    for(let i = Math.floor(n/2) - 1; i >= 0; i--) 
        adjustHeap(arr, i, n);
    
    // 堆排,每次选择堆顶元素 arr[0] (剩余最大元素),与末尾元素 arr[i] 进行交换
    for( let i = n - 1; i > 0; i--) 
        swap(arr, 0, i);
        adjustHeap(arr, 0, i);
    
    return arr;

/**
 * 堆调整
 * @param 待排序数组 arr 
 * @param 当前起始节点 i 
 * @param 堆的长度 n
 */
function adjustHeap(arr, i, n) 
    let leftChild = 2 * i + 1;
    let rightChild = 2 * i + 2;
    // 假设初始节点最大
    let largeNode = i;
    // 左节点存在 && 左节点大于初始节点
    if ( leftChild < n && arr[leftChild] > arr[largeNode]) 
        largeNode = leftChild;
     
    // 右节点存在 && 右节点大于初始节点/左节点
    if (rightChild < n && arr[rightChild] > arr[largeNode]) 
        largeNode = rightChild;
    
    if (largeNode !== i) 
        // 将最大的节点和初始节点调整位置
        swap(arr, i, largeNode);
        // 再往下继续调整
        adjustHeap(arr, largeNode, n)
    

/**
 * 交换元素
 * @param  arr 
 * @param 交换元素下标 a 
 * @param 交换元素下标 b 
 */
function swap(arr, a, b) 
    let temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;

console.log(heapSort([90,70,80,60,10,40,50,30,20]))

 

以上是关于JavaScript实现常见排序算法:冒泡,插入,选择,归并,快速,堆排序的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript算法(冒泡排序选择排序与插入排序)

常见的排序算法总结(JavaScript)

常见排序算法的实现(归并排序快速排序堆排序选择排序插入排序希尔排序)

几种常见的排序算法分析学习

常见排序算法及其JS实现

排序算法3种简单排序(选择,冒泡,插入)