zoukankan      html  css  js  c++  java
  • 交换排序:冒泡排序vs快速排序

    在开发的过程中, 经常会遇到集合排序, 那么一般情况下, 我们都是使用list.OrderBy()的方式来排序, 也无需关注到里面算法的实现是个什么样子. 正好这几天准备回顾一下数据结构与算法. 

    首先来了解一下, 排序大致可以分为哪几种:

      交换排序: 包括冒泡排序,快速排序。

          选择排序: 包括直接选择排序,堆排序。

          插入排序: 包括直接插入排序,希尔排序。

          合并排序: 合并排序。

    List.OrderBy()采取的就是快速排序方式. 冒泡排序既然和它是同一种排序方式, 那么我们将他们比较一下看看. 看看哪一种排序更好一点.

    一、示例(先看结果, 再讲思想)

    static void Test()
    {
        //五次比较
        for (int i = 1; i <= 5; i++)
        {
            List<int> list = new List<int>();
            //插入2k个随机数到数组中
            for (int j = 0; j < 2000; j++)
            {
                Thread.Sleep(3);
                list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 100000));
            }
    
            Console.WriteLine("" + i + "次比较:{0}...", string.Join(",", list.Take(10)));
    
            Stopwatch watch = new Stopwatch();
            watch.Start();
            var result = list.OrderBy(single => single).ToList();
            watch.Stop();
            Console.WriteLine("
    快速排序耗费时间:" + watch.ElapsedMilliseconds);
            Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList()));
            watch.Start();
            result = BubbleSort(list);
            watch.Stop();
            Console.WriteLine("
    冒泡排序耗费时间:" + watch.ElapsedMilliseconds);
            Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList()));
        }
    }
    
    //冒泡排序算法
    static List<int> BubbleSort(List<int> list)
    {
        int temp;
        int count = list.Count;
        //第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
        for (int i = 0; i < count - 1; i++)
        {
            //list.count-1:取数据最后一个数下标,
            //j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
            for (int j = count - 1; j > i; j--)
            {
                //如果前面一个数大于后面一个数则交换
                if (list[j - 1] > list[j])
                {
                    temp = list[j - 1];
                    list[j - 1] = list[j];
                    list[j] = temp;
                }
            }
        }
        return list;
    }

    这段代码是从参考链接里面拿的, 个人比较懒啊, 而且冒泡排序算法还是很简单的, 就不自己写了

    从上面的结果来看, 快速排序比冒泡排序快的不是一星半点啊. 

    看到了神奇之处, 接下来就是深入了解一下, 算法的思想和实现.

    二、思想及实现

    1. 冒泡排序

    首先来解释一下冒泡吧, 在水里面, 呼出一口气, 形成一个泡泡, 这个泡泡会在上升的过程中, 逐渐变大(水压越来越小导致的). 最后露出说面破掉了.

    联系着这种思想, 可以想到, 冒泡排序, 应该就是让大的数, 逐渐往上升, 一直升到比它大的数前面, 破掉了.

    根据这种思想, 就大致有一个过程在脑海中形成了, 来看一下代码: (下面的还蛮形象的, 就偷过来了)

    //冒泡排序算法
    static List<int> BubbleSort1(List<int> list)
    {
        int temp;
        int count = list.Count;
        for (int i = 0; i < count - 1; i++)
        {
            for (int j = 1; j < count; j++)
            {
                //如果前面一个数大于后面一个数则交换
                if (list[j - 1] > list[j])
                {
                    temp = list[j - 1];
                    list[j - 1] = list[j];
                    list[j] = temp;
                }
            }
        }
        return list;
    }

    首先, 每一次循环, 我都能确定一个数的位置, 那么需要循环多少次呢? 最后一个数应该不需要再循环比较了吧, 也没数跟他比较了. 所以循环n-1次就行了.

    接着, 这里面的每一次循环, 其实就是一个冒泡的过程了. 把开始的数与后面一位数进行比较, 如果大于后面的数, 就向后移一位. (其实想想, 这个也挺麻烦的, 为啥比较一次就要移动一次呢? 我不能找到他的位置, 才互换么? 起码减少了换的操作了)

    我这里的代码与上面示例的稍有不同, 稍微注意一下.

    来看一下这里的时间复杂度, 虽然外面只循环了n-1次, 里面也只循环了n-3次, 看似复杂度为(n-1)*(n-3), 但是如果n够大的话, -1或者-3甚至-100, 对最后的结果影响都是很小的. 

    按照最坏的情况算的话, 这里的冒泡排序的时间复杂度, 极限情况是O(n2), 那么他有没有理想情况呢? 好像没有啊, 就算这个数组已经排好序了, 好像程序还是要这样从头走到尾啊, 一点都没有少什么. 所以这里的平均复杂度, 也是 O(n2). 这么看来, 冒泡排序并不是一种理想的排序方式. 

    如果不能提供更好的排序方式的话, 还是老老实实的使用List.OrderBy的方式去排序吧.

    2. 快速排序

    快速排序算法其实也叫分治法, 其步骤大致可以分为这么几步:

      1. 先从数列中取出一个数作为基准数Num(取得好的话, 是可以减少步骤的)

      2. 分区, 将大于Num的数放在它的右边, 小于或等于它的数放在它的左边

      3. 再对左右区间重复前两操作, 直到各个区间只有一个数为止.

    从上面的文字可能还是不太好理解这个过程, 那么我用一张图片来描绘一下这个过程

    经过一轮比较之后, 总感觉这里要递归啊. 牵涉到递归, 那么他的空间复杂度可能会比冒泡排序高一点. 

    既然一轮的过程已经清楚了, 那么就先写出一轮的代码好了

    static int Division(List<int> list, int left, int right)
    {
        int baseNum = list[left];
    
        while (left < right)
        {
            while (left < right && list[right] > baseNum)
            {
                right -= 1;
            }
    
            list[left] = list[right];
         while (left < right && list[left] <= baseNum) { left += 1; } list[right] = list[left]; } list[left] = baseNum; return left; }

    这里的Left+=1和Right-=1都是有前提条件的, 前提条件为:Left < Right

    接下来就比较简单了, 一个递归调用, 这个递归的思想是很简单的:

    static void QuickSort(List<int> list, int left, int right)
    {
        if (left < right)
        {
            int i = Division(list, left, right);
    
            QuickSort(list, left, i - 1);
    
            QuickSort(list, i + 1, right);
        }
    }

     快速排序的复杂度:

      最糟糕的情况下, 复杂度为: O(n2)

      最优的情况下, 复杂度为: O(nlog2n)

    其复杂度的计算和证明, 额, 我是给不出来了, 但是参考里面是有的. 

    最差情况下, 快速排序和冒泡排序的时间复杂度是一样的,  但是最优情况下, 冒泡排序是 n * n, 而快速排序的是 n * log2n,

    如果n=16,

    则冒泡是 16 * 16

    快速排序是 16 * 4

    可见, 只要你不是背到家, 都是比冒泡来的快的.

    参考:

    算法系列15天速成——第一天 七大经典排序【上】

    快速排序时间复杂度为O(n×log(n))的证明

  • 相关阅读:
    FZU 2150 Fire Game
    POJ 3414 Pots
    POJ 3087 Shuffle'm Up
    POJ 3126 Prime Path
    POJ 1426 Find The Multiple
    POJ 3278 Catch That Cow
    字符数组
    HDU 1238 Substing
    欧几里德和扩展欧几里德详解 以及例题CodeForces 7C
    Codeforces 591B Rebranding
  • 原文地址:https://www.cnblogs.com/elvinle/p/6655763.html
Copyright © 2011-2022 走看看