常见的排序算法——交换排序

交换排序

利用交换元素的位置进行排序的方法称作交换排序。
常见的交换排序的方法:冒泡排序和快速排序。

冒泡排序
基本思想
  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素就是是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
    -持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码如下
void BubbleSort(int array[], int size)
{
    for (int i = 0; i < size; ++i)
    {
        //j会取到j+1,因此应该小于size-i-1
        for (int j = 0; j < size - i - 1; ++j)
        {
            if (array[j + 1] < array[j])
            {
                Swop(&array[j + 1], &array[j]);
            }
        }
    }
}
总结
  • 冒泡排序最好情况时间复杂度O(n),冒泡排序最坏情况下时间复杂度O(n^2)
  • 冒泡排序空间复杂度O(1)
  • 冒泡排序是一种稳定的排序算法
快速排序
基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

代码实现

交换函数

void Swop(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

a. 递归方式

void _QuickSort(int array[], int begin, int end)
{
    if (end - begin <= 1)
        return;

    //取最后一个元素为基准值
    int mid = Partion_3(array, begin, end);

    //排序左子区间
    _QuickSort(array, begin, mid);

    //排序右子区间
    //mid+1是因为我们用的是左闭右开的区间
    _QuickSort(array, mid + 1, end);
}

void QuickSort(int array[], int size)
{
    _QuickSort(array, 0, size);
}

b. 非递归


void QuickSortNor(int array[], int size)
{
    if (size < 1)
        return;
    Stack s;
    StackInit(&s);

    //Partion函数用的都是左闭右开区间
    int begin = 0;
    int end = size;

    StackPush(&s, end);
    StackPush(&s, begin);

    while (!StackEmpty(&s))
    {
        begin = StackTop(&s);
        StackPop(&s);
        end = StackTop(&s);
        StackPop(&s);

        int mid = Partion(array, begin, end);

        //先入栈左区间
        StackPush(&s, begin);
        StackPush(&s, mid);

        //入栈右区间
        StackPush(&s, mid + 1);
        StackPush(&s, end);
    }
}

Partion函数的三种实现方式

  • 交换法


int Partion(int array[], int begin, int end)
{
    //左闭右开
    int left = begin;
    int right = end - 1;

    int key = array[end-1];

    //因为是左闭右开的区间,所以没有等号
    while (left < right)
    {
        //从左边开始找,找到一个大于基准值的值
        while (left < right && array[left] <= key)
        {
            ++left;
        }
        //从右边开始找,找到一个小于基准值的值
        while (left < right && array[right] >= key)
        {
            --right;
        }
        //找到后,交换
        if (left < right)
        {
            Swop(&array[left], &array[right]);
        }
    }
    //当循环终止后,交换下标为left和end-1的元素
    //这样基准值的左边都小于基准值,右边都大于基准值
    Swop(&array[left], &array[end - 1]);

    return left;
}
  • 挖坑法
int Partion_2(int array[], int begin, int end)
{
    //左闭右开区间
    int left = begin;
    int right = end - 1;
    //确定基准值,并且左右边的元素已经被key保存,
    //此时出现了一个坑
    int key = array[right];

    while (left < right)
    {
        while (left < right && array[left] <= key)
        {
            ++left;
        }
        if (left < right)
        {
            //填右边坑,并且右边出现新的坑
            array[right--] = array[left];
        }
        while (left < right && array[right] >= key)
        {
            --right;
        }
        if (left < right)
        {
            //填左边的坑,左边出现新的坑
            array[left++] = array[right];
        }
    }

    //最后只剩一个坑,left位置的坑。
    //将key填入
    array[left] = key;


    return left;
}
  • 前后指针法
int Partion_3(int array[], int begin, int end)
{
    int pre = begin - 1;
    int cur = begin;
    int key = array[end - 1];

    while (cur < end)
    {
        if (array[cur] < key)
        {
            //当pre和cur相等时,说明前边元素都小于基准值
            pre++;
            //如果pre不等于cur,说明下标为pre的值大于基准值
            //cur继续往后走,当cur找到一个小于基准值的值时,
            //交换下表为pre,cur的值,
            if (pre != cur)
                Swop(&array[cur], &array[pre]);
        }
        cur++;
    }

    //这时cur已经走到end,如果pre不等于cur,pre指向的是小于基准值的值
    //说明下标大于pre的的值都大于基准值,
    //++pre交换,将基准值放在这个位置
    if (++pre != end)
        Swop(&array[pre], &array[end - 1]);

    return pre;
}
快速排序的优化
  • 三只取中确定基准值
    与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
      (1) 首先,它使得最坏情况发生的几率减小了。
      (2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。
  • 当区间比较小的时候,可以使用插入排序,直接对这个区间进行排序,从而减小递归次数。
  • 当递归深度达到一定的程度时,使用堆排序对待排序区间进行排序。

    这里未进行优化,读者可自己进行实践

总结
  • 快速排序的时间复杂度为O(N^2),最好为O(NlgN),最差为O(N^2)。
  • 快速排序的空间复杂度为O(lgN)
  • 当待排序的关键字是随机分布时,快速排序的平均时间最短。

猜你喜欢

转载自blog.csdn.net/lyjwonderful/article/details/80498398