javascript5种常见的排序算法

2 篇文章 0 订阅
2 篇文章 0 订阅

近期除了做专题以外,每天都要抽出时间学习算法和计算机网络的知识,因为感觉这2个方面比较薄弱。

算法上除了通过google以外,主要就是看《学习javascript数据结构与算法》和《javascript高级程序设计》。

这篇文章主要就是归纳了5种常见的javascript的排序算法。

本来书中是有6种排序算法,但是堆排序我并没有很理解。

打算等专题做完后再花时间好好的学习基础的算法。

1.冒泡排序


冒泡排序是所有排序算法种最简单的一个,但是性能是最差的。

下面是初步的冒泡排序方法。

javascript代码

var arr = [1,5,4,3,2], // [n1,n2,n3,n4,n5]
    len = arr.length;

for (let i = 0; i < len; i++) { // 外循环
    for (let j = 0; j < len-1; j++) { // 内循环
        if (arr[j] > arr[j+1]) { // 如果前一项大于后一项,就调换位置。
            var n = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = n;
        }
    };
};
console.log(arr); // [1,2,3,4,5]

冒泡排序原理:

        1. 外循环是根据数组长度,比如数组长度为5,就表示外循环5个迭代,每个迭代都包含一个内循环。即执行5内循环

一共进行了5内循环,第一轮内循环过程分析:

         1. 首先arr的第一项和第二项比较,如果第一项大于第二项,调换位置。即这2项的最大值放在了第二项的位置。

         2. 接下来第二项和第三项进行比较,如果第二项大于第三项,调换位置。即第二项和第三项的最大值放在第三项位置,这就能得出前三项的最大值,并放在第三的位置上。

         3. 接下来是第三项和第四项......以此类推,就能得出[*,*,*,*,5](*代表不确定的数字)。最大的一项,也就是5放在了最后一个位置。

然后是第二次内循环,重复上面的步骤,就能够得出[*,*,*,4,5]的结果。每一次内循环就能得出一个数字在arr中的位置。

所以我们根据arr的长度进行循环,就能得出[1,2,3,4,5]的结果。

原理很简单,但其实这是有缺陷的,因为有些步骤是没必要进行的。

以下是改进后的冒泡排序

var arr = [1,5,4,3,2],
    len = arr.length;

for (let i = 0; i < len; i++) { // 外循环
    for (let j = 0; j < len-1-i; j++) { // 内循环
        if (arr[j] > arr[j+1]) { // 如果前一项大于后一项,就调换位置
            // var n = arr[j];
            // arr[j] = arr[j+1];
            // arr[j+1] = n;
            [arr[j],arr[j+1]] = [arr[j+1],arr[j]]; // ES6语法
        }
    };
};
console.log(arr); // [1,2,3,4,5]

主要有2个不同,第一是运用了ES6的结构赋值,简化了代码。

第二是将len-1改成了len-1-i,我们来分析为什么这样做。

上面一种方法,一共数组有5项。在第一次内循环的时候,两两比较,共进行了4次比较,才得出了[*,*,*,*,5]的结论。

然后进行第二次内循环,还是进行了4次比较,得出[*,*,*,4,5]

这不是最优解,当第四项和第五项进行比较,得出4 < 5,最后将4放在了第四项的位置上。

这一步完全可以忽略。因为在第一次内循环中,我们早已经得出5是最大值,放在最后一项的结论。

我们只要比较前四项,两两比较,进行3次。就能得出4的位置。

同理在第三次内循环中,我们因为在前2内循环得到了45分别在最后2项的结论,所以只要比较1,2,3就够了。也就是2次比较。

我们发现每进行一次内循环,比较的次数就-1

所以我们在内循环len-1的基础上,添加为len-1- i(因为每个迭代 i 都会比上一个迭代+1)。

2.选择排序


选择排序和冒泡排序一样,复杂度为O(n^2),都包含有嵌套的2个循环。

javascript代码

var arr = [5,4,1,3,2],
    len = arr.length,
    minIndex;

for (var i = 0; i < len-1; i++) { // 外循环
    minIndex = i;
    for (var j = i; j < len; j++) { // 内循环
       // 如果原来的最小值比j位置的值小,就将原来的最小值转换为j位置的值,即新的最小值,继续比较。  
       if (arr[minIndex] > arr[j]) {
           minIndex = j;
       }
    }
    if (i !== minIndex) {
        // var n = arr[minIndex]; // 最小值 
        // arr[minIndex] = arr[i];
        // arr[i] = n;
        [ arr[i] , arr[minIndex] ] = [ arr[minIndex] , arr[i] ]; // ES6语法
    }
}
console.log(arr) // [1,2,3,4,5]

选择排序原理:找到数组中的最小值并将它放在第一位,接着找到第二小的放在第二位,以此类推。

外循环表示内部代码执行次数为数组长度-1次。因为当得出前面数字的位置时,我们自然知道最后一项要放在哪里。

当第一次外循环的时候,我们比较数组中的最小值。

通过minIndex = i,我们得到arr[0],也就是第一项的值,如上例,就是5

然后我们将它和数组内的元素进行比较,如果碰到比它小的,就 minIndex = j

如上例,当5碰到4的时候,发现它更小,通过minIndex = j,这样arr[minIndex] = 4

这样就将原来的最小值转换为新的最小值(比原来更小)。

然后以此类推,继续比较。只要碰到更小的,就通过minIndex = j,获得那个更小的值的位置 和值。

当第一次内循环结束,我们就得到最小值的位置和值。

然后接下来判断这个最小值的位置是不是和原来的最小值arr[0]一样,如果arr[0]为最小值,我们什么都不用做。

如果不是的话,就将它们调换一下位置,这样数组中的最小值就放在了第一项。

然后i=1,开始第二次外循环,我们获取整个数组中的第二小值放在第二项,以此类推。

3.插入排序


插入排序在理解上会比上面2个难点。

javascript代码

var arr = [3,5,1,4,2],
    len = arr.length,
    j,
    temp;

for (let i = 1; i < len; i++) { // 外循环
    j = i;
    temp = arr[j];

    while (j > 0 && arr[j-1] > temp) { // 内循环
        arr[j] = arr[j-1];
        j--;
    }
    arr[j] = temp;
}
console.log(arr); // [1,2,3,4,5]

插入排序原理:假定第一项已经排序,接着,它和第二项进行比较。然后条件判断,第二项是应该呆在原地还是插入到第一项之前?这样就能正确排序前两项了。接着再和第三项比较(判断它是该插入第一、第二还是第三的位置)。

       1. 首先我们假设第一项已经排列好了,然后开始第一次外循环。这时候 j = 1,我们获取数组中第二项的值5,将它和第一项3进行比较。

        2. [3,5,1,4,2],我们很容易就得出3 > 5 === false,跳过了这次内循环,位置不变。

        3. 然后开始第二次外循环,这时候 j = 2。我们获取第三项的值1,将它和第二项进行比较,发现5 > 1成立。所以我们将arr[1]的值,赋给了arr[2],也就是第三项变成了5。[3,*,5,4,2]。

        4. 其实这时候*是5,但因为我们已经通过temp记录下1的值,所以在最后arr[ j ] = temp就行了。

        5. 接下来我们将1的值和3进行比较。因为 j--,这时候j=1。我们比较arr[j-1]和temp的大小(其实就是3和1)。因为3 > 1成立,所以我们将3放在了2的位置上,内循环结束。[3,3,5,4,2]

       5. 最后一步arr[ j ] = temp。因为3已经找到了新的位置,所以我们将原来的位置给了1。这样前三项就能很好的排序了。然后再执行下一次外循环,以此类推。

插入排序的算法比选择排序和冒泡排序性能更好,但是还是太复杂了。

4.归并排序


归并算法是一种分治算法,有不错的性能。火狐浏览器就是使用归并排序来实现sort方法。

其思想是将原始的数组切分成较小的数组,直到每个数组只有一个位置。通过比较,将小数组归并成大数组,直到最后一个排序完成的大数组。

因为是分治算法,所以归并算法需要用到递归。

javascript代码

var arr = [4,1,3,2];

console.log(mergeSort(arr)); // [1,2,3,4]

function mergeSort (arr) { // 将数组拆分成一个位置的小数组
    var len = arr.length;

    if (len === 1) {
        return arr;
    }
    var mid = Math.floor(len/2),
        left = arr.slice(0,mid),
        right = arr.slice(mid,len);
        
    return merge(mergeSort(left),mergeSort(right));
}; 

function merge(left,right) { // 将多个一个位置的小数组合并成一个大数组
    var result = [],
        n1 = 0,
        n2 = 0;

    while (n1 < left.length && n2 < right.length) {
        if (left[n1] < right[n2]) {
            result.push(left[n1++]); // push进left[n1],n1 = n1 + 1;
        } else {
            result.push(right[n2++]);
        }
    }

    while (n1 < left.length) {
        result.push(left[n1++]);
    }

    while (n2 < right.length) {
        result.push(right[n2++]);
    }

    return result;
}

归并排序主要分成2部分,一个是拆分,一个是合并。这2个过程分别在2个函数中。

首先来分析怎么拆分。

javascipt代码

var arr = [4,1,3,2];

function mergeSort (arr) { // 将数组拆分成一个位置的小数组
    var len = arr.length;

    if (len === 1) { // 递归终止条件
        return arr;
    }
   // 将大数组拆分成2个小数组
    var mid = Math.floor(len/2),
        left = arr.slice(0,mid),
        right = arr.slice(mid,len);
        
    return merge(mergeSort(left),mergeSort(right));
};

我们通过mergeSort函数进行拆分,因为是递归,首先设定递归终止条件,即数组长度为1,这表示大数组已经拆分完毕。

如果长度不为1,我们就将大数组依照中间位置,拆分成left和right两个小数组。直到数组只剩下一个位置为止。

分析上面例子:

        1. 传入数组[4,1,3,2],再判断终止条件,数组长度不为1,继续执行。

         2. 依照中间位置拆分成left,right两个小数组 left = [4,1]和right = [3,2],然后return。

         3. 这是函数的返回结果:merge( mergeSort( [4,1] ) , mergeSort( [3,2] ) ),将大数组拆分成2个单位的小数组,然后接着执行mergeSort函数。====> mergeSort([4,1])和mergeSort([3,2])。

         4. mergeSort([4,1]):因为[4,1]还是不满足长度为1的终止条件,会被拆分成merge( mergeSort( [4] ) , mergeSort( [1] ) )。然后接着执行mergeSort( [4],因为数组length为1,所以直接返回数组[4]。所以merge( mergeSort( [4] ) , mergeSort( [1] ) )的结果就是:merge( [4],[1] )。也就是说mergeSort( [4,1] )的最终结果是merge([4],[1])。

         5. 同理,mergeSort([3,2])也是一样。所以传入[4,1,3,2],就会返回merge( merge( [4],[1] ) , merge( [3],[2] )   )

这时候我们已经将它拆分成一个单位的小数组,并传入merge函数,在这个函数内部完成合并的过程。

javascript代码

var result = merge( merge([4],[1]) , merge([3],[2]) );

console.log(result); // [1,2,3,4]

function merge(left,right) { // 将多个一个位置的小数组合并成一个大数组
    var result = [],
        n1 = 0,
        n2 = 0;

    while (n1 < left.length && n2 < right.length) { // 控制2个长度为循环次数
        if (left[n1] < right[n2]) {
            result.push(left[n1++]); // push进left[n1],n1 = n1 + 1;
        } else {
            result.push(right[n2++]);
        }
    }

    while (n1 < left.length) {
        result.push(left[n1++]);
    }

    while (n2 < right.length) {
        result.push(right[n2++]);
    }

    return result;
}

首先我们执行merge函数:merge( merge( [4],[1] ) , merge( [3],[2] )   ),merge( [4],[1] )其实就是大的merge的参数,所以越内部就会先执行。

过程分析:

       1. 首先会执行merge( [4],[1] )函数。我们的目的是合并成内部元素从小到大排序的数组[1,4]。

        2. left = [4],right = [1],因为都只有一项,所以第一个while循环只执行一次。然后我们比较left的第一项和right的第一项,如果left的小,就将它添加进数组result中。然后控制循环的变量n1++。

        3. 接下来因为n1 = left.length,不满足要求,就没有继续循环,就执行后面的内容。然后因为n2 < right.length,将right[0]添加进result数组中。这样就完成了我们的目的,将小数组合并成[1,4]。

其实merge函数的原理就是通过比较left和right项的大小,将小的一项添加进result中。直到left和right中有一个数组的项全部添加进去。如上例就是将通过循环将left的所有项添加进result数组中,然后right数组直接将剩余项添加到result中就好了。

所以merge( [4],[1] )的返回值就是[1,4],同理,merge( [3],[2] )的返回值是[2,3]。完成了第一步归并的结果就是merge( [1,4],[2,3] )

接下来继续执行第二步的归并,再次通过merge函数将两个数组合并成一个大数组。然后返回归并后的结果就行了。

5.快速排序


快速排序是最常用的排序算法,并且性能也很好。

var arr = [5,4,2,3,1];

function arrSort(arr) { // 快速排序

    var len = arr.length;

    if (len <= 1) { // 递归终止条件
        return arr;
    }

    var index = Math.floor(len/2),
        num = arr.splice(index,1), // 选择的基准值
        leftArr = [],
        rightArr = [];

    arr.forEach((val)=>{ // 和基准值进行比较
        if (val <= num) { // [2]和数字进行比较,会转化为数字再进行比较
            leftArr.push(val);
        } else {
            rightArr.push(val);
        }
    });

    return arguments.callee(leftArr).concat(num,arguments.callee(rightArr)); // 数组合并

}
console.log(arrSort(arr)); // [1,2,3,4,5]

快速排序原理:

       1. 在数组中选择中间元素作为基准值。上例为[2]

        2. 将数组的其他项和这个基准值进行比较,形成2个子集,一个是大于2的,一个是小于2的。leftArr = [1],rightArr = [3,4,5]。

        3. 对2个子集重复1、2步骤,直到这2个子集只剩下一个元素为止。

我在可能有8个月前就接触过快速排序,当时因为对递归没有足够了解,所以一直不懂它。需要注意的是,arguments.callee()这个方法是函数调用自身函数。

目前app的商品发布快做完了,下一次博客会总结图片上传功能的各种实现方式。

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值