八大排序算法之交换排序

本文详细介绍了交换排序中的冒泡排序和快速排序,包括基本思想、稳定性分析、时间复杂度及不同实现方式,如左右指针法、挖坑法、前后指针法等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在之前的两篇博客中,我们分别说了插入排序和选择排序,有兴趣的同学还可以戳链接去看看八大排序算法之选择排序八大排序算法之插入排序

交换排序主要说得是冒泡排序和快速排序,思想就和名字一样是用交换来实现的。

1.冒泡排序

基本思想:在要排序的数组中,对当前还没排好序的范围内的全部数,进行依次比较,以升序为例,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

稳定性:因为是冒泡,所以不会改变相同元素的相对位置。

时间复杂度:O(N^2)

这里写图片描述

冒泡排序比较简单,这里就不画图描述了,直接上代码。

void BubbleSort(int *a, int size)
{
    assert(a);
    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size - i-1; j++)
        {
            if (a[j]>a[j + 1])
                swap(a[j], a[j + 1]);
        }
    }
}

2.快速排序

快速排序是我们最常用的排序,这里我们说三种快排的方法:左右指针法、挖坑法、前后指针法。
<1>左右指针法

基本思想:以当前的某个数为基准,然后找出第一个比它大的,第一个比它小的,然后进行交换。两个指针向中间靠近,当左右指针相等的时候,这次循环就结束了。然后不断递归缩小区间,直到所有的区间都有序。

这里写图片描述

这里写图片描述

//以一个数为基准,然后找比它大的和小的,然后交换,直到这两个指针相遇
//和中间的那个数进行交换,以这个数为界,左边的都小,右边的都大
int PartSort1(int *a, int left, int right)  //左右指针法
{      
    //优化:三数取中法
    //int mid = getMid(a, left, right);
    //swap(a[mid], a[right]);

    int key = a[right];
    int begin = left;
    int end = right;
    while (begin < end)
    {
        while (begin < end && a[begin] <= key)
            ++begin;
        while (begin < end && a[end] >= key)
            --end;
        if (begin < end)
            swap(a[begin], a[end]);
    }
    swap(a[begin], a[right]);
    return begin;
}

如果当前数组是个和我们排序相反的有序数组,这么这种情况是快排的最差情况,此时我们可以考虑在选择基准的时候进行优化下。在这里,最简单的优化就是三数取中法。我们在left和right找到中间的位置,然后根据这个mid进行比较,最后在按照我们上面的继续执行。

//当数组有序的时候,快排的情况最差,这个时候可以采用三数取中法
int getMid(int *a, int left, int right)
{
    int mid = left + (right - left) >> 2;
    if (a[left] < a[mid])   //left  mid
    {
        if (a[mid] < a[right])   //left  mid  right
            return mid;
        else if (a[left]>a[right])   //right  left  mid  
            return left;
        else
            return right;
    }
    else   //mid left
    {
        if (a[mid] > a[right])
            return mid;
        else if (a[left] < a[right])
            return left;
        else
            return right;
    }
}

<2>挖坑法

基本思想:以最后一个数为基准,将这个基准保存在key中,从左找大于key的,找到了就和end交换;然后从右找小于key的,找到了就交换;最后把比key大/小的都放在了合适的位置上

这里写图片描述

一趟排序的过程如上,如果你还是不明白。就直接跳过去看代码就好了。

int PartSort2(int *a, int begin, int end)   //挖坑法
{
    int key = a[end];
    while (begin < end)
    {
        while (begin < end && a[begin] <= key)
            ++begin;
        a[end] = a[begin];
        while (begin < end && a[end] >= key)
            --end;
        a[begin] = a[end];
    }
    a[begin] = key;
    return begin;
}

<3>前后指针法

基本思想:也是在找比key大的或者小的,但这里它是从一个方向开始找的。用两个指针分别保存大于和小于,然后找到就进行交换。

这里就不画图了,直接用代码来说明思路。

int PartSort3(int *a, int begin, int end)  //前后指针法
{
    int prev = begin - 1;
    int cur = begin;
    while (cur < end)
    {
        if (a[cur] >= a[end])
            cur++;
        if (cur != prev && a[cur] < a[end])
        {
            prev++;
            swap(a[cur], a[prev]);
            cur++;
        }
    }
    swap(a[++prev], a[end]);
    return prev;
}

可以看出,上面三种排序的思路都是一样的,先找出一个基准,然后通过查找比基准大的和小的来进行比较,通过比较,完成排序,然后不断缩小区间去排序。

void QuickSort(int *a, int left,int right)
{
    assert(a);
    if (left >= right)
        return;

    //int mid = PartSort1(a, left, right);
    //int mid = PartSort2(a, left, right);
    int mid = PartSort3(a, left, right);
    QuickSort(a, left, mid - 1);
    QuickSort(a, mid + 1, right);
}

<4>快速排序的非递归实现

有时候进行笔试或者面试的时候,可能要求你写个快排但是不能使用递归。这个时候千万不能慌,一定要想清除快排的根本是什么,然后借助合适的数据结构来完成。

在上面实现的快排中,我们可以看出递归的部分是不断缩小区间的这个范围,假设现在我们是在操作系统内部,那么每次递归的时候就是创建一个新的栈帧的时候,所以可以联想到栈来实现。也就是说,我们可以利用栈来保存每次更改的区间。其他部分的思路还是一样的,想到这里是不是有种豁然开朗的感觉?话不多说,代码奉上。

void QuickSortNonR(int *a, int left, int right)
{
    stack<int> s;
    s.push(right);
    s.push(left);
    while (!s.empty())
    {
        //每次排序的区间
        int begin = s.top();  
        s.pop();
        int end = s.top();
        s.pop();

        int mid = PartSort3(a, begin,end);

        //类似递归的部分,将每次的区间进行压栈
        if (begin < mid - 1)   
        {
            s.push(mid - 1);
            s.push(begin);
        }

        if (mid + 1 < right)
        {
            s.push(end);
            s.push(mid + 1);
        }
    }
}

对快速排序来说,两个指针分别查找相较于基准值大或者小的,所以相同元素的相对位置可能会发生改变,即快排是不稳定的。

到现在为止,常见的排序我们已经说了6种,下篇博客将会说最后的两种,并且对所有的排序算法做个总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值