[排序/C++]3.交换排序(冒泡排序、快速排序)

3. 交换排序

  1. 基本思想:
    两两比较,如果发生逆序则交换,直到所有记录都排好序为止。

  2. 常见的交换排序方法:

    • 冒泡排序 O(n^2)
    • 快速排序 O(nlogn)

3.1 冒泡排序——基于简单的交换思想

3.1.1 算法分析和思路

  • 每趟不断将记录两两比较,并按**“前小后大”**规则排序。

  • Q:为什么称之为冒泡?
    A:把数据元素竖着放,观察排序的整个过程就会发现,“轻”的元素往上浮,“重”的元素往下沉。就像气泡,轻的向上冒,重的向下沉。

  • 每一趟增加一个有序元素,最大的元素到了最后面,前面还是无序状态,(n-1)趟后,剩下的一个元素一定有序,即最小的元素,此时所有元素都有序。

3.1.2 算法示例

冒泡排序示例图1
冒泡排序示例图2
冒泡排序示例图3

  • 总结:
    • 元素/记录总个数:n个;
    • 趟数:(n-1)趟;
    • 每趟比较次数:第 i 趟需要两两比较(n-i)次。

3.1.3 算法代码

//冒泡排序
/*
    元素/记录总个数:n个
    趟数:(n-1)趟
    每趟比较次数:第 i 趟需要两两比较(n-i)次
*/
void BubbleSort1(SqList &L){
    for(int i = 1; i <= L.length-1; i++){//n-1趟;有哨兵位,索引从1开始
        for(int j = 1; j <= L.length-i; j++){//第 i 趟需要两两比较(n-i)次
            //发生逆序,两两交换
            if(L.nums[j].key > L.nums[j+1].key){
                Swap(L, j, j+1);
            }
        }
    }
}

排序算法详细代码

  • 冒泡排序优化:
    某一趟比较时记录不发生交换,说明序列全部有序,即可提前结束算法。
//冒泡排序优化:某一趟未发生交换,即停止算法,后面几趟可以省略
/*
    元素/记录总个数:n个
    趟数:(n-1)趟
    每趟比较次数:第 i 趟需要两两比较(n-i)次
*/
void BubbleSort2(SqList &L){
    int flag = 1;//flag作为是否发生交换的标记。1:发生交换;0:未发生交换
    for(int i = 1; (i <= L.length-1) && (flag == 1); i++){//n-1趟;有哨兵位,索引从1开始
        flag = 0;
        for(int j = 1; j <= L.length-i; j++){//第 i 趟需要两两比较(n-i)次
            //发生逆序,两两交换
            if(L.nums[j].key > L.nums[j+1].key){
                Swap(L, j, j+1);
                flag = 1;//发生交换,flag置为1;若本趟未发生交换,flag保持为0
            }
        }
    }
}

排序算法详细代码

3.1.4 算法性能分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是一种稳定的排序方法

3.2 快速排序——改进的交换排序

3.2.1 算法分析和思路

  1. 基本思想:

    1. 任取一个元素(如:第一个元素)为中心
    2. 比中心元素的一律放前面,比它的一律后放面,将待排序序列分割成左右两个子表
    3. 对各个子表重新选择中心元素,并依此规则分别进行以上排序;(递归思想)
    4. 直到每个子表的元素只剩一个,整个序列有序。
  2. 具体实现:

  • 选定一个中心点作为参考,通过一趟排序,所有元素与之比较,小的调整到左边,大的调整到右边。
  • 中心点/中间数/枢轴:第一个元素、最后一个元素、最中间一个元素、任选一个元素等。
  • 如何调整?
    1. 开辟一个存放n个元素的空间。比中心点小的元素,从左往右放;反之,从右往左放。
      缺点:需要额外的空间,当元素个数比较多时,较浪费空间。
    2. 只需要一个额外位置,将中心元素复制到index=0的位置,即哨兵位
      low和high双指针。双指针重合,结束循环。
      每一趟的左右子表的形成是采用从两头向中间交替式逼近法。
      由于每趟中对各子表的操作都相似,可采用递归算法
      当子表只剩一个元素时,即双指针重合,无需排序。
      (详细细节可参考代码理解)

3.2.2 算法示例

快速排序示意图

3.2.3 算法代码

//快速排序算法
void QSort(SqList &L, int low, int high){
    int centerIndex;
    if(low < high){//当子表只剩一个元素时,即low和high重合,无需排序
        centerIndex = FindCenter(L, low, high);
        //将序列一分为二,center为中心点排好序的位置
        QSort(L, low, centerIndex-1);//对左子表递归排序
        QSort(L, centerIndex+1, high);//对右子表递归排序
    }
}

//获得一趟排序后中心点位置索引值
int FindCenter(SqList &L, int low, int high){
    L.nums[0] = L.nums[low];//子列第一个元素(即index=low)为中心点,中心元素复制到index=0的位置
    int centerValue = L.nums[low].key;//中心点元素值
    while(low < high){//low和high重合,即结束循环,重合位置即为所求的中心点位置索引值
        while((low < high) && (L.nums[high].key >= centerValue))    high--;
        L.nums[low] = L.nums[high];
        while((low < high) && (L.nums[low].key <= centerValue))     low++;
        L.nums[high] = L.nums[low];
    }
    L.nums[low] = L.nums[0];
    return low;
}

排序算法详细代码

3.2.4 算法性能分析

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

    • 实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。
    • 划分元素的选取是影响时间性能的关键。
  • 空间复杂度: O ( l o g n ) O(logn) O(logn)

    • 快速排序不是原地排序
      原地排序不需要额外的辅助空间,辅助空间和元素个数无关
    • 快速排序需要递归。如果要递归,必须要用到系统栈,系统栈会记录下递归时每次调用的现场、参数值、返回值等,栈的长度取决于递归调用的深度。
      • 不用递归,用自己设计的栈来实现,同样需要自己设计的用户栈。
  • 是一种不稳定的排序方法

  • 快速排序不适于对原本有序或基本有序的记录序列进行排序。
    越乱越好,越有序反而越慢了,快速排序不是自然排序方法。(自然排序:基本有序,更快)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值