js常见的排序算法

1.冒泡排序

冒泡算法算是排序里面比较基础的一种算法。

它的实现原理为:从第一个元素开始,将它与下一个索引元素比较。如果当前元素的值比下一个元素的值要大,则两者交换位置,否则位置不变。接下来,索引往后移一位,继续比较接下来两个数的值。一直重复上面的操作,直到比较到最后一个元素,因为大的元素位置会交换位置向后移,所以,最后一个元素已经是最大的。那么下次在比较的时候,不需要比较最后一个元素,只需要比较到length-2的位置

分步解释一下过程:

  1. 最开始要从位置为0开始比较到整个length。那我们设置 i = length - 1;如果这次排序流程走完,那么最后数组最后一位已经是最大值,接下来只需要比较到length-2。重复以上操作。
  2. 那么,i 限定了每次比较的位置。接下来我们来设置另外一个变量 j,来控制每次相邻两个元素是否交换。代码如下:

//首先我们先声明两个通用函数,在下面的排序算法中都会用到。
function checkArray(array){
    if (Object.prototype.toString.call(array) !== "[object Array]") return;
}

function swap(array,left,right){
    let rightValue = array[right];
    let array[right] = array[left];
    let array[left] = rightValue;
}


//冒泡排序
function bubble(array){
    checkArray(array);
    for (let i=array.length-1;i>0;i--){
        for (let j=0;j<i;j++){
            if (array[j]>array[j+1]){
                swap(array,j,j+1)
            }        
         }
      }
    return array
}
复制代码

排序过程如图所示:



2.插入排序

插入排序原理: 从整个待排序列中选出一个元素插入到已经有序的子序列中去,得到一个有序的、元素加一的子序列,直到整个序列的待插入元素为0,则整个序列全部有序。

分步来说:

  1. 我们首先默认第一个元素是已经有序的(因为只有一个元素)。
  2. 我们将后面的未排序的元素逐渐插入到有序的序列中。即第二个和第一个有序的比较,如果比第一个小,则插入到第一个有序元素的前面。我们用i来控制每次待比较的元素。用j来控制每个元素大小的对比。
  3. 接下来到第三个元素,和前面两个有序的元素比较,将第三个插入到前面两个有序的序列中,使得前三个序列有序。
  4. 重复3操作,直到整个序列有序。

function insertion(array){
    checkArray(array)
    for (let i=1;i<array.length;i++){
        for (let j=i-1;j>=0 && array[j]>array[j+1];j--){
            swap(array,j,j+1);
        }
    }
    return array

}复制代码

插入排序过程如图所示:



3.选择排序

选择排序原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放待排序序列的起始位置(或末尾位置),直到全部待排序的数据元素排完。

分步说明:

1.找出带排序元素中的最小值。如何找?我们可以先假设最小值的索引为0,如果取出的值要比最小索引上面的值小,则将最小值与索引为0的值交换。这样索引为0的值就是最小值。

2.将索引置为1,然后向后找出,比索引1上的值小的,然后与索引1上的值交换。

3.重复步骤2,直到整个序列有序。

function selector(array){
    checkArray(array)
    for (let i=0;i<array.length-1;i++){
        let minIndex = i
        for(let j=i+1;j<array.length;j++){
            minIndex = array[j] > array[minIndex]?minIndex:j;
        }
        swap(array,i,minIndex)
    }
    return array

}复制代码

选择排序过程如图:



4.归并排序

归并原理:归并排序的原理是分而治之。先将数组两两分开直到最多包含两个元素,然后进行排序。之后在进行合并,将两两合并的数列进行比较然后排序。直到排序完成。

分步如下:

1.假如我们有这样一组序列 [1,2,6,3,8,4,7,5]

2.先分解数列成左右两部分[1,2,6,3]和[8,4,7,5]

3.继续分解左边的数组为[1,2]和[6,3]

4.排序上面的数组为[1,2]和[3,6].

5.在排序数组[1,2,3,6]

6.左边排序完成,然后进行右边数组的排序,过程不变.

代码如下:

function mergeSortArray(array){
    checkArray(array)
    mergeSort(array,0,array.length-1)
    return array
}

function mergeSort(array,left,right){
     if (left === right) return;
     let mid = parseInt(left + (right-left)/2) ;
     mergeSort(array,left,mid);
     mergeSort(array,mid+1,right)
    
    let tmp = [];
    let p1 = left;
    let p2 = mid + 1;
    let i = 0;
    while(p1 <= mid && p2 <= right){
        tmp[i++] = array[p1] < array[p2] ? array[p1++]:array[p2++]
    }
    while (p1 <= mid){
        tmp[i++] = array[p1++]
    }
    while (p2 <= right){
        tmp[i++] = array[p2++]
    }
    for (let i=0;i<tmp.length;i++){
        array[left + i] = tmp[i]
    }
    return array
}复制代码

排序算法如图:



5.快速排序

快速排序原理:快速排序算法应该算是排序算法里比较复杂的一种算法了

快速排序算法的原理简单来说,就是我们找出一个基准值(以第一个数为基准值):使得基准值的左边的数都比基准值小,右边的数都比基准值大。接下来对基准值左边的序列,以第一个数为基准值,使得基准值左边的数比基准值小,后面的数比基准值大。

所以问题就来到了,如何使得基准值的左边比基准值小,右边比基准值大呢?

一般方法有两钟,一种是挖坑法,一种是指针交换法。我们来介绍一下指针交换法如何实现。

1.如果有这样一个数组[4,2,5,3,6,1]我们首先选第一个数6为基准数。

2.首先我们规定两个指针分别指向数组的左边和右边。简称哨兵左和哨兵右。

3.我们首先(重要)从哨兵右开始,从右往左移动,直到找到第一个比基准数小的数,然后停止,很幸运,第一次就找到了,为1。

4.接下来我们哨兵左开始行动,从左往右找,直到找到一个比基准数大的数,然后停止,此时为5.

5.接下来,交换哨兵所在位置的元素  即数组变成 4,2,1,3,6,5

6.接下来继续,先移动哨兵左,(在元素为5的位置),继续找比基准元素小的,此时找到了       3,然后停下。

7.哨兵右开始从左往右找,发现,当移动到3的时候,哨兵左和哨兵右相遇了。此时将基准值与哨兵位置上的值做交换。得到了3,2,1,4,6,5这样使得基准值左边的值都比基准值小,右边的值都比基准值大。接下来对3,2,1进行同样的操作。直到长度为1的时候返回。

所以代码如下:

function quickSort(array){
    checkArray(array);
    if (arr.length<2) return;
    let left = 0;
    let right = arr.length-1;
    while (left<right){
        while(array[right] > array[0] && left < right ){
            right = right -1;
        }
        while(array[left] < array[0] && left < right) {
            left = left + 1;
        } 
        // 两个指针相遇了,就将基准值与哨兵位置上的值交换
        if (left === right) {
            swap(array,0,left)
        }
        //找到了,左边指针小于右边指针,就交换上面的值
        swap(array,left,right)
    }
    // 递归实现,获取哨兵左边的值,哨兵位置上的值,哨兵右边的值
    return quickSort(arr.slice(0,left)).concat(arr.slice(left,right+1)).concat(quickSort(arr.slice(right+1)));
}复制代码

排序过程如图:


6.堆排序

首先我们要理解什么是完全二叉树?

  1. 从作为第一层的根开始,除了最后一层之外,第N层的元素个数都必须是2的N次方;第一层2个元素,第二层4个,第三层8个,以此类推。
  2. 而最后一行的元素,都要紧贴在左边,换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲,具备了这两个特点的树,就是一棵完全二叉树。

那么完全二叉树和堆又有什么关系呢?

我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递推,就是根节点的值最小,这样的树,称为小根堆。


左边为大根堆,右边则为小根堆。

所谓的堆排序,就是我们要将给我们的数组模拟成堆的数据结构,然后将最大的根节点,与最后一个节点交换。交换过后,继续维护堆结构,使得根节点上的数又是最大数,然后继续交换,直到排序完成。

1.假如给定无序序列:节点的左边子节点索引是 i * 2 + 1,右边是 i * 2 + 2,父节点是 (i - 1) /2

我们分步来看,首先构造一个大根堆:


2.由于叶子节点没有元素去移动位置,所以我们要从最后一个非叶子节点来开始调整。即我们从6开始,比较其子元素和父元素的大小,如果子元素大于父元素,则两者交换位置。交换位置后,继续比较,保证父元素比子元素大就行。



3.继续找非叶节点,9,4,8中9最大,所以和根元素交换位置



4.交换完成后,要继续看是不是每个非叶子节点都满足最大堆的要求,如果不满足,就继续交换位置,直到满足最大堆的要求。

5.第一次构建最大堆完成,接下来就是交换的过程。

6.将最大堆的根元素,与末尾的值交换。


7.然后使最大堆的size(5-1)减1.

8.继续调整结构,使其满足最大堆的定义


9.将最大堆的根元素8与末尾元素5交换。


10.将最大堆的size减1(4-1)。然后继续上面的过程,直到size=0停止。



以上就是堆排序的流程,接下来,是代码时间:

function heap(array){
    checkArray(array);
    //第一次将无序数组排列成大根堆
    for (let i=0;i<array.length;i++){
        heapInsert(array,i)
    }
    let size = array,length
    swap(array,0,--size)
    while(size>0){
        heapify(array,0,size);
        swap(array,0,--size);
    }
    return array

}

function heapInsert(array,index){
    while(array[index] > array[parseInt((index-1)/2)]){
        swap(array,index,parseInt((index-1)/2));
        //将索引变成父节点
        index = parseInt((index-1)/2)
    
    }
    
}

function heapify(array,index,size){
    let left = 2*index + 1 
    while (left > size){
        //先判断左右子节点的大小
        let largest = left + 1>size && array[left] > array[left+1]?left:left+1
        //再判断子节点和父节点的大小
        largest = array[index] < array[largest]?largest:index;
        if (largest === index) break;
        swap(array,index,largest);
        index = largest;
        left = 2*index + 1
    }

}复制代码

排序如图所示:


参考文章:

白话讲排序系列(六) 堆排序

漫画:什么是快速排序?(完整版)

前端面试之道


转载于:https://juejin.im/post/5d0906935188255f89065b01

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值