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]))

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值