• 浅谈数据结构-交换排序(冒泡、快速)


    交换排序:两两比较待排序的关键字,并交换不满足次序要求的那对数,直到整个表都满足次序要求为止。

    冒泡和快速排序是交换排序算法,冒泡排序给人直观感觉是从前开始遍历,将小的元素,慢慢交换到数组前面。快速排序直观感觉是从数组两边开始,以某一值作为分组标准,从顶端和尾端开始慢慢交换。


    一、冒泡算法

    1、“加火”-冒泡思想

    冒泡算法思想:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。从含义讲轻气泡不能在重气泡之下的原则,从下往上扫描数组s:凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。

    从算法思想分析,冒泡算法实现比较简单,但从冒泡实现的细节区分,可以初级冒泡排序、一般化冒泡排序、升级版冒泡排序。

    2、初级冒泡排序

    //低级冒泡排序
    void SortAlgorithm::PrimaryBubbleSort(pSqlList plist)
    {
        int i,j;
        
        for (i =0;i<plist->length;i++)
        {
            //数组选择一个,与其他数据比较,如果有小的,就放在i位置,就是获取余下数组中最小的
            for(j = i+1;j<plist->length;j++)
            {
                if (plist->SqlArray[i] > plist->SqlArray[j])
                {
                    swap(plist,i,j);
                }
            }
        }
    }
    PrimaryBubbleSort

    上述代码不是严格意义的冒泡排序算法,冒泡算法是循环比较邻接元素大小,如果不符合规则,进行交换。这个算法就是,先从数组选取一个元素,以它为标准,找出最小值。获取最小值后,在获取此最小值。

    以上图为了,第一次进行排序后,2竟然在最后面了,这是不高效的。这其实一般人排序算法的理解,没学过排序算法的人,估计就会这一种了。

    3 、一般冒泡排序

     1 //冒泡排序
     2 void SortAlgorithm::bubble_sort(pSqlList pList)
     3 {
     4     int i,j;
     5 
     6     for (i =0;i<pList->length;i++)
     7     {
     8         //数组从后面开始遍历,前后两个数比较,将小的往前送
     9         //注意数组开始条件,和结束条件
    10         for(j = pList->length-1;j>=i;j--)
    11         {
    12             //注意数组坐标,千万别溢出
    13             if (pList->SqlArray[j-1] > pList->SqlArray[j])
    14             {
    15                 swap(pList,j-1,j);
    16             }
    17         }
    18     }
    19 
    20 }
    bubble_sort

    注意算法是从后向前循环,如果从前向后,起始条件和终止条件要改变下。

    算法步骤:

    1、比较第1个和第2个数,将小数放前,大数放后。

    2、比较第2个和第3个数,将小数放前,大数放后,如此继续,直到比较最后两个数,将小数放前,大数放后。

    至此,第一趟结束,将最大的数放在了最后。

    3、继续循环操作1、2步骤。其实很简单的操作,改进在于比较前后两个值,遵循一个原则,实现数据有序排列

    4、升级版冒泡排序

    如果待排序的序列式{2, 1, 3, 4, 5, 6, 7, 8, 9},也就是说,除了第一和第二的关键字需要交换外,别的都已经是正常的顺序了.

    //高级冒泡排序
    void SortAlgorithm::SeniorBubbleSort(pSqlList pList)
    {
        int i,j;
        int flag = true;
        printf("开始验证冒泡排序");
        for (i =0;i<pList->length&&flag;i++)
        {
            //大部分操作和冒泡排序一致,就是添加了判断条件,就是当有数据交换时才执行,没有数据交换,说明数组有序
            flag = false;
            for(j = pList->length-1;j>=i;j--)
            {
                //注意数组坐标,千万别溢出
                if (pList->SqlArray[j-1] > pList->SqlArray[j])
                {
                    flag = true;
                    swap(pList,j-1,j);
                }
            }
            PrintSqlList(pList);
        }
    }
    SeniorBubbleSort

    代码分析:

    1、从尾部9开始冒泡排序,进行第一次冒泡后,序列已经为有序序列{1,2,3,4,5,6,7,8,9}

    2、进入第二个循环,flag = false,然后执行内部冒泡排序,发现序列有序,此时flag还是false,所以循环直接跳出。


    二、快速排序算法

    它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数

    然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    1、算法分析

    ①分解:
    在S[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间S[low..pivotpos-1)和S[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
    注意:
        划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=S[pivotpos]):
        S[low..pivotpos-1].keys≤S[pivotpos].key≤S[pivotpos+1..high].keys
                      其中low≤pivotpos≤high。
    ②求解:
    通过递归调用快速排序对左、右子区间S[low..pivotpos-1]和S[pivotpos+1..high]快速排序。
    ③组合:
    因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。

    在当前无序区中选取划分的基准关键字是决定算法性能的关键。所以后续研究人员对快速的提升基本就是如何选取基准关键词。

    imageimage

    初始状态为一组无序的数组:50,10,90,30,70,40,80,60,20。

    1、数组首位放置基准位,程序中默认选取spList[1] = 50。

    2、从数组两边开始排序,大于50的,放在右边,小于50 ,放在左边。

    3、经过以上操作步骤后,完成了第一次的排序,得到新的数组:20,10,40,30,50,70,80,60,90,。

    新的数组中,以20为分割点,在前5个元素中继续排序,同时以70在后面数组中排序。

    2 、核心代码

     1 //快速排序
     2 void SortAlgorithm::QuickSort(pSqlList pList)
     3 {
     4     printf("开始验证快速排序");
     5     QuickSort(pList,1,pList->length-1);
     6 }
     7 //快速排序
     8 inline void SortAlgorithm::QuickSort(pSqlList pList,int left,int right)
     9 {
    10     //快速排序原理是从不停迭代,类似二叉树,分组排序,注意跳出条件
    11     //这里是if,如果是while就进入死循环
    12     if(left<right)
    13     {
    14         //数组分组,将组内数组进行交换,让其小的在基数左边,大的基数右边
    15         int baseposition = Division(pList,left,right);
    16         //继续迭代,将分好的左边小数数组,内部再进行快速排序
    17         QuickSort(pList,left,baseposition-1);
    18         //继续迭代,将分好的右边大数数组,内部再进行快速排序
    19         QuickSort(pList,baseposition+1,right);
    20     }
    21     printf("快速排序组外排序
    ");
    22     PrintSqlList(pList);
    23 }
    24 
    25 inline int SortAlgorithm::Division(pSqlList pList,int left,int right)
    26 {
    27     printf("快速排序组内数组展示");
    28     //以左边数为基准
    29     int base = pList->SqlArray[left];
    30     while(left<right)
    31     {
    32         //判断右边的是否大于基准数,如果右边数小于,放在左边,否则继续向左,遍历,直到找到大于基数的.
    33         while(left<right&&pList->SqlArray[right] >= base)
    34         {
    35             right--;
    36         }
    37         //找到比base小的数后,将这个元素放在坐标数组中
    38         pList->SqlArray[left] = pList->SqlArray[right];
    39         // 从序列左端开始,向右遍历,直到找到大于base的数
    40         while(left< right && pList->SqlArray[left] <= base)
    41         {
    42             left++;
    43         }
    44         // 从序列左端开始,向右遍历,直到找到大于base的数
    45         pList->SqlArray[right] = pList->SqlArray[left];
    46     }
    47 
    48     // /最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
    49         // 而left位置的右侧数值应该都比left大。
    50     pList->    SqlArray[left] = base;
    51     PrintSqlList(pList);
    52     return left;
    53 }
    QuickSort

     

    三、交换排序性能分析

      1、冒泡排序复杂度

    排序方法 时间复杂度

    平均情况

    最坏情况

    最好情况

    空间复杂度 稳定性 复杂性
    冒泡排序

    O(N2)

    O(N2)

    O(N)

    O(1) 稳定 简单
    快速排序

    O(Nlog2N)

    O(N2)

    O(Nlog2N)

    O(Nlog2N)O(2) 不稳定 较复杂

    1、冒泡复杂度

    若文件的初始状态是正序的,一趟扫描即可完成排序。比较次数为n-1,冒泡排序最好时间复杂度为O(N)。
    若初始文件是反序的,需要进行 N -1 趟排序。每趟排序要进行 N - i 次关键字的比较(1 ≤ i ≤ N - 1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:1+2+3+….+n-1 = n(n-1)/2,所以最坏时间复杂度为O(N2)。
    因此,冒泡排序的平均时间复杂度为O(N2)。
    总结起来,其实就是一句话:当数据越接近正序时,冒泡排序性能越好

    2、快速排序复杂度

    时间复杂度

    当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。

    而当数据随机分布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数接近相等,此时执行效率最好。

    所以,数据越随机分布时,快速排序性能越好;数据越接近有序,快速排序性能越差

    空间复杂度

    快速排序在每次分割的过程中,需要 1 个空间存储基准值。而快速排序的大概需要 Nlog2N次 的分割处理,所以占用空间也是 Nlog2N 个。

  • 相关阅读:
    Java的代码风格
    哪些你容易忽略的C语言基础知识
    Java基础学习笔记第二章
    Java代码性能优化总结
    Java并发编程(2):线程中断(含代码)
    C语言代码训练(一)
    数控G代码编程详解大全
    PLC编程算法
    博客转移到新地址
    一些吐槽
  • 原文地址:https://www.cnblogs.com/polly333/p/4810719.html
Copyright © 2020-2023  润新知