交换排序--冒泡排序与快速排序

关于交换,王道考研数据结构书里是这样说的:

 有一个博主讲的也不错:交换排序 (冒泡排序 && 快速排序)_跳动的bit的博客-CSDN博客

 总之就是比较并移位。并且最经典的交换排序算法是冒泡与快速排序。

一、冒泡排序

顾名思义,就是跟冒泡泡一样,不断地比较交换把最大的值冒到当前的最顶层。

 动图演示--摘抄自:1.1 冒泡排序 | 菜鸟教程

void BubbleSort(int nums[], int n) {//正的冒泡
    //从最底不断往上冒
    for (int i = 0; i < n; ++i) {//用于更新冒出的泡所能到达的最顶层
        for (int j = 1; j < n - i; ++j) {//从j=1开始冒,冒到当前的最顶层n-i
//交换气泡,保证冒到当前的气泡是在当前层最大的            
            if (nums[j - 1] > nums[j])swap(nums[j - 1], nums[j]);
        }
    }
    for (int i = 0; i < n; ++i)cout << nums[i] << endl;
}

当然也可以反着冒泡,嗷不,应该叫沉底排序了

void BubbleReverseSort(int nums[], int n) {//反的冒泡
    for (int i = n - 1; i > 0; --i) {
        for (int j = n - 1; j >= n - i; --j) {//从最上面不停往下沉
            if (nums[j] < nums[j - 1])swap(nums[j - 1], nums[j]);
        }
    }
    for (int i = 0; i < n; ++i)cout << nums[i] << endl;
}

冒泡的复杂度是稳定的。时间复杂度为一个等差数列的前n项和,为n²复杂度。

时间复杂度:O(n²)

空间复杂度:O(1)

二、快速排序

快排是基于分治的,直接理解概念还是挺好理解的,但是处理边界条件倒挺难的,什么时候该放等号不该放等号,如果不亲自跑通一遍挺容易出错的。

下面是动图,摘自:1.6 快速排序 | 菜鸟教程

快排包含一个分割函数跟一个不断调用分割函数的sort函数。

1.分割函数

首先在分割函数中随机选取一个基准pivot(通常是取第一个数为基准),然后用前后双指针两头遍历。保证右边的一定是大于等于基准,左边的一定是小于等于基准的。而双指针相碰的时候就是pivot所在的位置了。

int Partition(int nums[],int l,int r) {
    int pivot = nums[l];//基准的选取,此时l位置保存的数是可覆盖的
    while (l<r) {
        while (nums[r] >= pivot&&r>l) {
            --r;//保证基准右边不小于基准
        }
        nums[l] = nums[r];//将右边比基准小的赋给l,此时r位置的数是可覆盖的
        while (nums[l] <= pivot && r > l) {
            ++l;//保证基准左边不大于基准
        }
        nums[r] = nums[l];//将左边比基准大的赋给r,此时l位置的数是可覆盖的
    }//l==r,两指针相撞,此时代表找到了基准的位置了
    nums[r] = pivot;//将基准放到其对应的位置
    return r;//返回基准所在的位置,此时将数组以基准为分界线分割成了两部分
}

为了避免数组最坏情况,我们可能会设置一个随机位置作为基准以降低最坏情况发送的概率。因此分割函数可以再改进一下:

int Partition(int nums[],int l,int r) {
    swap(nums[rand()%(r-l+1)+l],nums[l]); //随机选取一个数作为基准
    int pivot = nums[l];//基准的选取,此时l位置保存的数是可覆盖的
    while (l<r) {
        while (nums[r] >= pivot&&r>l) {
            --r;//保证基准右边不小于基准
        }
        nums[l] = nums[r];//将右边比基准小的赋给l,此时r位置的数是可覆盖的
        while (nums[l] <= pivot && r > l) {
            ++l;//保证基准左边不大于基准
        }
        nums[r] = nums[l];//将左边比基准大的赋给r,此时l位置的数是可覆盖的
    }//l==r,两指针相撞,此时代表找到了基准的位置了
    nums[r] = pivot;//将基准放到其对应的位置
    return r;//返回基准所在的位置,此时将数组以基准为分界线分割成了两部分
}

2.排序函数

在sort函数中调用分割函数并不断缩小区间(由大到小的过程),直到每一个基准都安排到了自己的固定位置了,那么这个数组便排序好了。

void QuickSort(int nums[], int n) {
    auto dfs = [&](int l,int r,auto&& me)->void {
        if (r <= l)return;//递归终止条件
        int pivot= Partition(nums,l,r);
        me(l, pivot - 1, me);//基准前半部分再排序
        me(pivot+1,r , me);//基准后半部分再排序
    };
    dfs(0, n - 1, dfs);    
    for (int i = 0; i < n; ++i)cout << nums[i] << endl;
}

3.复杂度分析

快速排序是不稳定的排序。

1)最好的情况。即每次都能很好地将区间划分成均等的两部分。这样的话第一次可以扣掉1个数,第二次扣掉两个数...,最后是一个等比数列的前n项和。

此时其时间复杂度为O(nlogn)。

空间复杂度为O(logn)(空间复杂度来自于递归函数的调用深度)

2)最坏的情况。这是一个已经排序好的数列了,每次基准都是在边界上面,这时候是一个等差数列前n项和了。

时间复杂度为O(n)

空间复杂度为O(n²)

3)平均复杂度。这个比较复杂,具体参考:快速排序时间复杂度分析 - 知乎

算法 | 快速排序平均时间复杂度分析 - 知乎

关于空间复杂度可参考:究竟为什么,快速排序的时间复杂度是n*lg(n)? | 经典面试题_58沈剑的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值