八大排序算法之交换排序

来源:互联网 发布:docker nginx负载均衡 编辑:程序博客网 时间:2024/06/11 19:54
上一篇博文中已经分享了八大排序算法中两种插入排序算法--直接插入排序和希尔排序。本文来继续介绍剩下的排序算法--交换排序,包括冒泡排序和快速排序。其中关于冒泡排序还会介绍其改进后的算法,而对于快速排序则会介绍递归和非递归两种形式。

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

一、冒泡排序


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.

原创粉丝点击