冒泡排序和快速排序(C语言实现)

冒泡排序

实现思想(升序):将数组最大值一步一步交换到数组末尾,该值完成排序,需排序区间-1,继续找需排序区间最大值交换到末尾,直到需排序区间只有一个数,整个数组就有序了。

动图演示

void Swap(int* a, int* b)//交换函数
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void BubbleSort(int* a, int n)
{
    int i = 0;
    int index = n - 1;//最后一个值的下标
    while (index > 0)
    {

        int flag = 0;//记录该区间是否有值交换
        for (i = 0; i < index; i++)//将需排序区间里面最大的值交换到区间最后一个
        {
            if (a[i] > a[i + 1])//大的往后交换
            {
                Swap(&a[i], &a[i + 1]);
            }
        }

        if (flag == 0)//表示没有值交换,数组已经有序,前面待排序区间也已经有序
        {
            return;
        }
        index--;//冒泡排序进行一次,区间向前缩进1,后面的数已经完成排序。
    }
}

  时间复杂度O(N^2)   空间复杂度O(1)

快速排序

基本思想:任取待排序元素序列中的某元素作为基准值,按照该基准值将待排序列分为两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右序列重复该过程,直到所有元素都排列在相应位置上为止。

按照基准值将待排序列分为两子序列,常见的方式有:
 1、Hoare版本
 2、挖坑法
 3、前后指针法

递归实现(升序)

Hoare版本

单趟的动图演示

Hoare版本的单趟排序基本思想 

如果基准值key在左边,右边先开始找比key小的值,找到了停止,左边才开始找比key大的值,找到后交换,重复此过程,直到左右相遇,将相遇点的值与key的值交换,这样单趟排序完成,key左边的值比key小,key右边的值比key大。

然后我们在将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,排序完成。

int SingleSort1(int* a, int left, int right)
{  
    int keyi = left;
    while (left < right)
    {
        while (left < right && a[right] >= a[keyi])//右边找到小于keyi位置的值停止
        {
            right--;
        }
        while (left < right && a[left] <= a[keyi])//左边找到大于keyi位置的值停止
        {
            left++;
        }
        Swap(&a[left], &a[right]);//交换
    }
    //当left=right时,表示相遇,与keyi位置交换
    Swap(&a[keyi], &a[left]);
    return left;
}

void QuickSort(int* a, int left,int right)
{
    int keyi = SingleSort1(a,left,right);
    if (left >= right)//表示区间只有一个或者没有值,直接返回
    {
        return;
    }
    //[left,keyi-1] keyi [keyi+1,right]
    QuickSort(a, left, keyi - 1);//递归keyi的左区间
    QuickSort(a, keyi + 1, right);//递归keyi的右区间
}

挖坑法

单趟的动图演示

 

 挖坑法单趟排序基本思想 :pit位置存一般存最左边或者最右边的值(区别是pit在左,右边先找,pit在右,左边先找)这里pit在左,右边找比pit小的,找到了,将左边的值填入,此时右边形成新的坑,左边开始找,找到比pit大的,填入到右边的坑,知道左右相遇停止,将pit填入相遇点。

//挖坑法
int SingleSort2(int* a, int left, int right)
{   
    int pit = a[left];
    while (left < right)
    {
        while (left < right && a[right] >= pit)
        {
            right--;
        }
        //找到比pit小的值,填到左边坑位上,此时坑在右边
        a[left] = a[right];
        while (left < right && a[left] <= pit)
        {
            left++;
        }
        //找到比pit大的值,填到右边坑位上,此时坑在左边
        a[right] = a[left];
    }
    a[left] = pit;//将pit填入到相遇点
    return left;
}

void QuickSort(int* a, int left,int right)
{
    int keyi = SingleSort2(a,left,right);
    if (left >= right)
    {
        return;
    }
    QuickSort(a, left, keyi - 1);
    QuickSort(a, keyi + 1, right);
}

前后指针法

单趟的动图演示

 前后指针法单趟排序基本思想:选取一个key(这里在最左边)  前指针prev初始在key位置上,后指针cur初始在key+1位置上,cur开始向后找比key小的值,找到了,prev先++,在交换两个的值(如果++prev=cur表示的是自己跟自己交换,可以不处理),cur继续向后找,直到cur出了数组的范围,交换prev与key的值,单趟排序完成。类比两个指针赶着大的值向后走,小的留在前面。

 int SingleSort3(int* a, int left, int right)
{
    int keyi = left;
    int prev = left;//前指针
    int cur = left + 1;//后指针
    while (cur <= right)
    {
        if (a[cur] < a[keyi] && ++prev != cur)//表示cur找到比key小的值,并且不是自己于自己交换
        {
            Swap(&a[cur], &a[prev]);
        }
        cur++;//cur大于key的值,交换后继续往后找都会执行++操作
    }
    Swap(&a[prev], &a[keyi]);//cur出了数组范围,交换prev与keyi的值
    return prev;
}
void QuickSort(int* a, int left,int right)
{
    int keyi = SingleSort3(a,left,right);
    if (left >= right)
    {
        return;
    }
    QuickSort(a, left, keyi - 1);
    QuickSort(a, keyi + 1, right);
}

快排的优化

三数取中

快速排序的时间复杂度是O(NlogN),是理想情况下计算的结果。每次进行完单趟排序后,key的左序列与右序列的长度都相同:

选取的key不可能都是正中间的那个数,当待排序列本就是一个有序的序列时,我们若是依然每次都选取最左边或是最右边的数作为key,那么快速排序的效率将达到最低: 

此时快速排序的时间复杂度退化为O(N2)。所以,对快速排序效率影响最大的就是选取的key,若选取的key越接近中间位置,则效率越高,所以出现三数取中优化。

三数取中:最左边的数、最右边的数以及中间位置的数。三数取中就是取这三个数当中,值的大小居中的那个数作为该趟排序的key。

//三数取中 左中右返回中间的那个
int MidIndex(int* a, int left, int right)
{
    int mid = left + (right - left)/2;//取到中间位置下标 (left+right)/2 (left+right)要是超过整型最大值会溢出
    if (a[mid] < a[left])
    {
        if (a[mid] > a[right])
        {
            return mid;
        }
        else if (a[right] < a[left])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
    else
    {
        if (a[left] > a[right])
        {
            return left;
        }
        else if (a[mid] > a[right])
        {
            return right;
        }
        else
        {
            return mid;
        }
    }
}
 

当大小居中的数不在序列的最左或是最右端时,我们不是就以居中数的位置作为key的位置,而是将key的值与最左端的值进行交换,这样key就还是位于最左端了,所写代码就无需改变,而只需在单趟排序代码开头加上以下两句代码即可:

    int mid = MidIndex(a, begin, end);//获取大小居中的数的下标
    Swap(&a[begin], &a[mid]);//将该数与序列最左端的数据交换,这样key还是从最左边开始
    //以下代码保持不变...

时间复杂度O(N*log^N)   空间复杂度O(1) 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值