zoukankan      html  css  js  c++  java
  • 数据结构中常用的排序算法 && 时间复杂度 && 空间复杂度

    第一部分:数据结构中常用的排序算法

      数据结构中的排序算法一般包括冒泡排序、选择排序、插入排序、归并排序和 快速排序, 当然还有很多其他的排序方式,这里主要介绍这五种排序方式。

      排序是数据结构中的主要内容, 并不限于语言而主要在于思想,这里用js实现。

      

    一、冒泡排序

      由小到大。

      名称由来: 循环时两两比较,每次循环都会将无序数组中的最大值放在后头。

      冒泡排序是我在学习C++时最先学习的一种排序方式,因为它理解简单,所以往往是入门之首选。

      规则: 既然是冒泡,那么越靠近前面(下面--最开始)的泡越小,越靠近后面(上面--结束)泡越大,故最大的泡在最上面。

     <script>
            /*
            * 冒泡排序
            * 遍历一次就把最大的放在最后面
            * 排序完毕
            */
            function bubble(arr) {
                var len = arr.length,
                    i,
                    j,
                    temp;
                for ( i = 0; i < len; i++) {
                    for ( j = 0; j < len - i - 1; j++) {
                        if (arr[j] > arr[j + 1]) {
                            temp = arr[j];
                            arr[j] = arr[j + 1];
                            arr[j + 1] = temp;
                        }
                    }
                }
                console.log(arr);
            }
            bubble([18, 28, 17, 12, 29]); // [12, 17, 18, 28, 29]
            bubble([58, 8, 17, 35, 29]); // [8, 17, 29, 35, 58]
            // 注意事项: 数组的长度需要多次使用,并在使用过程中不会改变,所以提前缓存起来。并注意单变量原则的使用。另外,在写代码的过程中,尽量将数字带入思考,而不是死记硬背,这样效果会好很多。
        </script>

      可以看到,冒泡排序中包含有嵌套的两个循环,这导致了二次方的复杂度。即冒泡排序的复杂度为 O(n2)。

    二、 选择排序

       由小到大。

       名称由来: 将无序数组中的最小值放在最前面。

       选择排序的思路和冒泡排序的思路是差不多的, 冒泡排序是将最大的数放在最后面,以后的每一次不再掺合最后放置的数。

       而选择排序的思路是每次将 “无序数组” 中的第一个数字和后面的每一个做出比较,将最小的放在最前面形成有序数组, 然后后续循环比较的时候就不再比较最前面的有序数组, 而只是将无序数组的最小值放在最前面。

       <script>
            /*
            * 简单选择排序
            * 首先假设“第一个”数最小,如果遇到后面还有更小的,就放在前面即可
            * 排序完毕
            */
            function simpleSelectionSort(arr) {
                var len = arr.length,
                    i,
                    j,
                    temp,
                    min;
                for (i = 0; i < len; i++) {
                    min = i;
                    for (j = i + 1; j < len; j++ ) {
                        if (arr[j] < arr[min]) {
                            min = j;
                        }
                    }
                    if (min !== i) {
                        temp = arr[min];
                        arr[min] = arr[i];
                        arr[i] = temp;
                    }
                }
                console.log(arr);
            }
            simpleSelectionSort([12, 28, 18, 29, 8, 17]); // [8, 12, 17, 18, 28, 29]
            simpleSelectionSort([95, 93, 92, 65, 66, 97]); // [65, 66, 92, 93, 95, 97]
            // 注意事项:很好理解,最好不要使用<=类似的符号,因为需要用<=的,我们一般都可以使用<并改变数字大小来实现。
    
        </script>

      选择排序同样也是一个复杂度为 O(n 2) 的算法,和冒泡排序一样, 它包含有嵌套的两个循环, 这就导致了二次方的复杂度。

    三、插入排序

      由小到大

      名称由来,前面的数组总是确定的, 当前的数字根据大小关系来选择需要插入的位置 --- 插入排序。

      插入排序的思想: 假设第一个数字已经是排序好的,我们循环的时候从第二个数字开始; 先把这个数字用temp存起来, 和前面的做出比较,如果说前面的数字大, 就把前面的数字放在当前位置,那缓存的数字就该放在前面数字的位置吗? 不是的, 还要继续比较缓存的数字和前面的前面的数字的大小关系,如果前面的前面的数字比这个缓存数字小,好,缓存数字就放在前面的数字的位置, 否则,就接着循环,直到满足条件。

    <script>
        /*
        * 插入排序
        * 就像整理扑克牌一样,前面的是有序的,每次拿到一张牌,我们就比较他们之前的大小关系,然后插入即可
        * i从1开始,是因为我们认为第一个数字是排列好的,而每循环一次,就认为前面已经排列好了一次。
        * 排序完毕
        */
        function insertSort(arr) {
            var len = arr.length,
                i,
                j,
                temp;
            for (i = 1; i < len; i++) {
                temp = arr[i];
                j = i;
                while ((temp < arr[j - 1])&&(j > 0)) {
                    arr[j] = arr[j - 1];
                    j--;
                }
                arr[j] = temp;
            }
            console.log(arr);
        }
    
        insertSort([12, 17, 29, 28, 8, 18]); //[8, 12, 17, 18, 28, 29]
        // 注意:一定要提前把arr[i]缓存起来,否则就会被覆盖掉;
    </script>

      排序小型的数组时, 此算法比选择排序和冒泡排序的性能要好。

      

    四、 快速排序

    <script>
        /*
        * 快速排序
        * 即取得中间的值,然后把小于中间的放在左边,大于中间的放在右边,递归执行函数即可  
        */
        Array.prototype.quickSort = function () {
            // 获取长度
            var len = this.length;
            // 如何长度不大于1, 直接返回即可
            if (len <= 1) {
                return this.slice(0);
            }   
            // 定义left、right、mid数组
            // 注意:mid必须要是数组,因为我们希望用到数组的concat方法。 这样才能完成递归调用
            var left = [],
                right = [],
                mid = [this[0]];
            // 这里从i=1开始即可,因为i=0已经让我们看做中间值了,就算是用了i=0,也是做得无意义的比较
            for (var i = 1; i < len; i++) {
                if (this[i] < mid[0]) {
                    left.push(this[i]);
                }
                if (this[i] > mid[0]) {
                    right.push(this[i]);
                }
            }
            // 精髓,递归调用。关键在于当length <= 1时,就返回了,即递归调用一定要有一个出口。
            return left.quickSort().concat(mid.concat(right.quickSort()));
        };
        var arr = [5, 45, 12, 69]; 
        console.log(arr.quickSort()); // [5, 12, 45, 69]
    </script>

     

     

     

    五、归并排序

    <script>
    /*
    * 归并排序
    * 采用的是分治法的思想,即首先将整个数组分成单个的即长度为1的数组,然后最后进行有序的合并。  
    */
    Array.prototype.mergeSort = function () {
        var merge = function (left, right) {
            var final = [];
            while (left.length && right.length) {
                final.push(left[0] < right[0] ? left.shift() : right.shift());
            }
            return final.concat(left.concat(right));
        }
        var len = this.length;
        if (len < 2) {
            return this;
        }
        var mid = len/2;
        return merge(this.slice(0, parseInt(mid)).mergeSort(), this.slice(parseInt(mid)).mergeSort());
    }
    var arr = [15, 18, 28, 56, 45, 8, 12, 29];
    console.log(arr.mergeSort()); //[8, 12, 15, 18, 28, 29, 45, 56]
    </script>

    六、希尔排序

    <script>
        /*
        * 希尔排序
        * 思想: 把数组按照一定的gap抓取即可,抓取得到的使用插入排序 
        * 排序完毕
        */
        Array.prototype.shellSort = function () {
            var gap, i, j;
            var temp;
            for (gap = this.length >> 1; gap > 0; gap >>= 1) {
                for (i = gap; i < this.length; i++) {
                    temp = this[i];
                    for (j = i - gap; j >= 0 && this[j] > temp; j -= gap) {
                        this[j + gap] = this[j];
                    }
                    this[j + gap] = temp;
                }
            }
            console.log(this);
        };
        [564, 54, 25, 18].shellSort();
        </script>

    七、堆排序

    <script>
        /*
        * 堆排序: 即每次建立一个堆,然后把大的往后放,每次都要使用递归
        */
        function heapSort(arr) {
    
            // 交换函数
            function swap(i, j) {
                var tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
    
            // start 即认为是根节点, end认为是最后一个index然后加1
            function heapify(start, end) {
                var dad = start;
                // son为左子树的序号
                var son = dad*2 + 1;
    
                if (son >= end) {
                    return;
                }
                // 选择两者中的大者,将大者和dad比较,然后如果更大就替换
                if (son + 1 < end && arr[son] < arr[son + 1]) {
                    son++;
                }
                if (arr[son] > arr[dad]) {
                    swap(son, dad);
                    heapify(son, end);
                }
            }
            var len = arr.length;
            // 从最后一个父节点开始,进行一次堆排序,因为如何不是父节点,就是没有意义的。
            for (var i = Math.floor(len/2) - 1; i >= 0; i--) {
                heapify(i, len);
            }
    
            // 整个的排序与交换位置
            for (var i = len - 1; i >= 0; i--) {
                swap(0, i);
                heapify(0, i);
            }
            console.log(arr);
        }
        heapSort([15, 25, 482, 555, 89]); //[15, 25, 89, 482, 555]
    </script>

    第二部分:时间复杂度&&空间复杂度

      

    一、复杂度总结

    1、时间复杂度  

      平均情况下,快速排序、希尔排序、归并排序和堆排序的时间复杂度都是O(nlog2n),其他的都是O(n2)。一个特殊的是基数排序,其时间复杂度为 O(d(n + rd))。 

       最坏情况下快速排序的时间复杂度为O(n2), 而其他的都和平均情况下是一样的

     

    2、空间复杂度

       空间复杂度中记住几个比较特殊的就好了, 快速排序为 O(log2n), 归并排序为O(n),基数排序为O(rd),其他的都是O(1)

       

    3、其他

       直接插容易变成O(n), 起泡起的好变成O(n),所谓“容易插”或者“起的好”都是指初始序列已经有序

     

    二、算法稳定性总结

      一句话记忆: 考研复习苦啊,心情不稳定快些选好友来聊天吧。

      这里, “快“指的是快速排序,“些”指的是希尔排序“选”指的是简单选择排序”指的是堆排序,这四种方式都是不稳定的,其他都是稳定的。 

    三、 其他细节

    1. 经过一趟排序,能够保证一个元素可以到达最终位置,这样的排序时交换类的那两种(起泡、快速)和选择类的排序(简单选择和堆)。
    2. 排序方法的元素比较次数和原始序列无关 --- 简单选择排序折半插入排序
    3. 排序方法的排序趟数和原始序列有关 --- 交换类的排序
  • 相关阅读:
    mybatis
    spring mvc
    Spring Boot2
    Spring AOP
    Spring Boot1
    Spring IOC
    Multiple_LinearRegression_Test2
    Multiple_LinearRegression_Test
    Simple_LinearRegression_Test
    写决策树时遇到的坑
  • 原文地址:https://www.cnblogs.com/zhuzhenwei918/p/6384675.html
Copyright © 2011-2022 走看看