• 【转】交换排序:冒泡排序vs快速排序


    交换排序:冒泡排序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

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

  • 相关阅读:
    delete
    js混淆代码还原-js反混淆:利用js进行赋值实现
    Microservice Trade-Offs
    tagged by: microservices 【martinfowler.com】
    Tackle Business Complexity in a Microservice with DDD and CQRS Patterns
    Why Can't I Access A Protected Member From A Derived Class
    tagged by: domain driven design 【martinfowler.com】
    Domain Driven Design
    Why Would I Ever Need to Use C# Nested Classes
    RabbitMQ compare with redis and MSMQ
  • 原文地址:https://www.cnblogs.com/zhangxin4477/p/6718852.html
Copyright © 2020-2023  润新知