八大排序算法之交换排序

上一篇博文中已经分享了八大排序算法中两种插入排序算法--直接插入排序和希尔排序。本文来继续介绍剩下的排序算法--交换排序,包括冒泡排序和快速排序。其中关于冒泡排序还会介绍其改进后的算法,而对于快速排序则会介绍递归和非递归两种形式。

说明:排序还是以从小到大为例。

一、冒泡排序


1.基本思想
冒泡排序算法是比较常用而且特别容易理解的排序算法。每一趟冒泡过程挑选出一个最大或最小的数,N个数只需要N-1趟就可以排好序。下面直接介绍它的排序过程,相信大家一看就懂。
2.排序过程
每一趟排序过程中,相邻两个元素之间相互比较,如果前面的元素比后面的元素值要大,则交换两者位置,直到交换到最后一个元素,此时最大的元素就放在了最后一个位置。具体过程如下:

冒泡排序过程

3.代码实现
#include <stdio.h>

void BubbleSort(int *pArray, int iLen);//冒泡排序
void PrintArray(int *pArray, int iLen);
void Swap(int *lhs, int *rhs);

int main(void)
{
    int iArray[] = {9, 7, 6, 5, 3 };
    int iLen = sizeof(iArray) / sizeof(*iArray);    //计算数组元素个数
    PrintArray(iArray, iLen);
    ReQuickSorck(iArray, iLen);
    PrintArray(iArray, iLen);
    return 0;
}

void Swap(int *lhs, int *rhs)
{
    int iTemp = *lhs;
    *lhs = *rhs;
    *rhs = iTemp;
}

void PrintArray(int *pArray, int iLen)
{
    for (int iIndex = 0; iIndex < iLen; ++iIndex)
    {
        printf("%5d", pArray[iIndex]);
    }
    printf("\n");
}

void BubbleSort(int *pArray, int iLen)
{
    int iCount;
    int iIndex;

    //控制循环趟数,N个数需要N-1for (iCount = 0; iCount < iLen - 1; ++iCount)
    {
        //每次从第0个开始比较,这里的iLen-iCount-1是因为将上一个已经排好序的元素排除在本次排序过程之外,减少比较次数
        for (iIndex = 0; iIndex < iLen - iCount - 1; ++iIndex)
        {
            if (pArray[iIndex] > pArray[iIndex + 1])
            {
                Swap(&pArray[iIndex], &pArray[iIndex + 1]);
            }
        }
    }
}
4.算法分析(时间复杂度、空间复杂度和稳定性)
(1)时间复杂度
从上面的代码中可以看出,嵌套了两层循环,而且循环次数为(N-1)+(N-2)+.......+1=(N^2-3N+2)/2,所以时间复杂度为O(N^2)。
(2)空间复杂度
在交换两个相邻元素的位置的Swap函数里面有一个临时变量iTemp,所以空间复杂度为O(1)。
(3)稳定性
稳定。
5.改进后的冒泡排序
以上程序有一个不好的地方,那就是对于最好的情况(数据原始就有序)的时间复杂度也为O(N^2)。对于改善这种情况,从上面的代码可以知道,出现这种情况的原因在于即使原始数据有序,但是每趟排序还是进行了两两比较。我们发现如果有一趟排序过程中,没有任何相邻的两个元素发生交换,则说明数据已经有序,没有必要再进行下一趟排序了。这是我们可以使用一个标志bFlag来标识这种状态,代码如下:
void BubbleSort_(int *pArray, int iLen)//改进后的冒泡排序
{
    int iCount;
    int iIndex;
    bool bFlag = true;    //初始化标识
    for (iCount = 0; iCount < iLen - 1  && bFlag; ++iCount)
    {
        bFlag = false;    //置为false,如果下面的循环中没有交换元素,则说明数据已经有序
        for (iIndex = 0; iIndex < iLen - 1 - iCount; ++iIndex)
        {
            if (pArray[iIndex] > pArray[iIndex + 1])
            {
                bFlag = true;
                Swap(&pArray[iIndex], &pArray[iIndex + 1]);
            }
        }
    }
}

二、快速排序


1.递归版快速排序
(1)基本思想
快速排序的过程分为两步,一是先取一个基准元素进行划分,二是根据找到基准元素的位置放入。一般取第一个元素为基准元素。具体过程是从右向左找第一个比基准小的元素,然后将该元素放入基准的位置;然后从左向右找第一个比基准大的元素,将该元素放入上一个找到的比基准小元素的位置;以此左右交替直到找到基准元素该放入的位置。
(2)排序过程
下面的图显示了一趟快速排序过程,当low和high相遇时说明一趟排序过程完成,然后将基准元素放入两者相遇的位置,进行下一趟排序。

这里写图片描述

(3)代码实现及运行结果
#include <stdio.h>
#include <stdlib.h>

void Swap(int *lhs, int *rhs);
void PrintArray(int *pArray, int iLen);
void QuickSort(int *pArray, int iLen);
void Quick(int *pArray, int low, int high);
int  Partition(int *pArray, int low, int high);

int main(void)
{
    int iArray[] = { 9, 7 , 6,5, 3 };
    int iLen = sizeof(iArray) / sizeof(*iArray);    //计算数组元素个数
    PrintArray(iArray, iLen);
    QuickSort(iArray, iLen);
    PrintArray(iArray, iLen);
    return 0;
}

void QuickSort(int *pArray, int iLen)
{
    Quick(pArray, 0, iLen - 1);
}

//递归排序
void Quick(int *pArray, int low, int high)
{
    int pivot = Partition(pArray, low, high);
    if (pivot > low + 1)
    {
        Quick(pArray, low, pivot - 1);
    }

    if (pivot < high - 1)
    {
        Quick(pArray, pivot + 1, high);
    }
}

int  Partition(int *pArray, int low, int high)
{
    int iTemp = pArray[low];
    while (low < high)
    {
    //从右向左找第一个比基准元素小的元素
        while (low < high && pArray[high] >= iTemp)
        {
            --high;
        }
        if (low == high)  //说明一次划分完成
        {
            break;
        }
        else
        {
            pArray[low] = pArray[high];
        }
        //从左向右找第一个比基准大的元素
        while (low < high && pArray[low] <= iTemp)
        {
            ++low;
        }
        if (low == high)  //说明一次划分完成
        {
            break;
        }
        else
        {
            pArray[high] = pArray[low];
        }
    }
    //将基准元素放入low和high相遇的位置
    pArray[low] = iTemp; 
    return low;//返回基准元素的位置,便于进行下一次划分
}

void Swap(int *lhs, int *rhs)
{
    int iTemp = *lhs;
    *lhs = *rhs;
    *rhs = iTemp;
}

void PrintArray(int *pArray, int iLen)
{
    for (int iIndex = 0; iIndex < iLen; ++iIndex)
    {
        printf("%5d", pArray[iIndex]);
    }
    printf("\n");
}
(4)算法分析(时间复杂度、空间复杂度和稳定性)
a)时间复杂度
时间花费的时间主要用于划分过程,从Partition过程来看,整个函数执行完时,正好是把整个序列遍历了一遍。虽然是双层循环,但其复杂度为O(N)。因为这是一个递归的过程,而且每一次划分完以后将原序列一分为二(基准元素左边和基准元素右边),有点二叉树的感觉,所以会递归O(logN)次。将两者结合起来,整个算法的时间复杂度为O(NlogN)。
b)空间复杂度
如果单看一趟划分过程,则从上面代码可以看出来空间复杂度为O(1)。但是因为这是一个递归的过程,所以在上一次函数的临时变量没有释放时又进行了下一次划分。所以空间复杂度为O(logN)。
c)稳定性
不稳定。
2.非递归版快速排序
    任何递归程序都可以借助栈进行非递归实现。直接上代码:
void NCQuickSort(int *pArray, int iLen)
{
    int iLow = 0;
    int iHigh = iLen - 1;
    int *pStack = (int*)malloc(sizeof(int)*(2 * iLen - 1));
    int iIndex = 0;
    pStack[iIndex++] = iLow; //注意这里是
    pStack[iIndex++] = iHigh;
    while (iIndex > 0)
    {
        iHigh = pStack[--iIndex];
        iLow = pStack[--iIndex];

        int pivot = Partition(pArray, iLow, iHigh);

        if (pivot > iLow + 1)
        {
            pStack[iIndex++] = iLow;
            pStack[iIndex++] = pivot - 1;
        }

        if (pivot < iHigh - 1)
        {
            pStack[iIndex++] = pivot + 1;
            pStack[iIndex++] = iHigh;
        }
    }
}

需要注意的一点是,栈的特点是先进后出,如果是先把low入栈,则先出栈的时high.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值