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

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

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


一、冒泡算法

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 个。

原文地址:https://www.cnblogs.com/polly333/p/4810719.html