排序(二)-----冒泡排序、快速排序三种递归实现and快排非递归

冒泡排序

动图演示:
这里写图片描述

void BubbleSort(int* arr, int n)//升序
{

    for (int i = 0; i < n-1; ++i)//n个数需要冒泡冒n-1趟
    {
        int flag = 0;//每趟开始之前flag置零
        for (int j = 0; j < n-1-i; j++)
        {
            if (arr[j]>arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                flag = 1;
            }
        }
        if (flag == 0)//数组有序,无需再冒泡。
            break;
    }
}

这里写图片描述
测试段

#include"sort.h"

void Print(int*a,int n)
{
    for (int i = 0; i < n; i++)
        printf("%d ", a[i]);
    printf("\n");
}
void TestSort()
{
    int arr[] = { 98, 88, 22, 999, 456, 12, 33, 77, 123 };
    int n = sizeof(arr) / sizeof(int);
    BubbleSort(arr, n);
    Print(arr, n);
}
int main()
{
    TestSort();
    system("pause");
    return 0;
}

快速排序

动图演示:
这里写图片描述

方法一——前找大,后找小:
对于 98, 88, 22, 999, 456, 12, 33, 77, 123这个数组,我们可以定义两个变量begin从头开始找比key大的数end从未开始找比key小的数
这里写图片描述
当arr[begin]比key大,arr[end]比key小时,交换arr[begin]和arr[end]。
这样下来相遇的位置以前的数都比key小,相遇点之后的数都比 key要大。
这里写图片描述

int Partion1(int*arr,int left,int right)//前begin找大,后end找小
{
    if (right - left>2)
        MidThree(arr, left, right);
    int key = arr[right];
    int begin = left, end = right;
    while (begin < end)
    {
        while (begin < end&&arr[begin] <= key)
            ++begin;
        while (begin<end&&arr[end]>=key)
            --end;
        //arr[begin]一定是比key大的数
        //arr[end]一定是比key小的数
        if (begin<end)
            Swap(&arr[begin], &arr[end]);//小数放前边,大数放后边
    }
    //到此begin==end,而且arr[begin]>key;
    Swap(&arr[begin], &arr[right]);
    return begin;
}

方法二——前后指针法:cur找小与++prev换

0——prev位置 比key值小
prev——cur 比key值大
cur 找比key小的数,找到之后与++prev交换

起始位置,prev在cur前一个位置,cur遇到不比key小的数继续往后走,停下位置数值一定比key值要小,prev!=cur时候,prev位置处于cur走过的路上,肯定是不小于key的数,交换cur与prev的值。将大数换到后面,小数换到前边。

此时prev位置的数比key小,如此保证prev以前的数值都比key值要小,当cur再次找到比key小的数时候,++prev位置肯定是比key值大的数,再交换。

int Partion2(int* arr, int left, int right)//前后指针法
{
    if (right-left>2)
        MidThree(arr, left, right);
    int key = arr[right];
    int cur = left, prev =  left - 1;
    while (cur < right)
    {
        if (arr[cur] < key&&++prev != cur)//cur找小与prev换
            Swap(&arr[cur], &arr[prev]);
        ++cur;
    }
    Swap(&arr[++prev], &arr[right]);
    return prev;
}

方法三——挖坑法:
因为确定基数key之后,key这个位置就可以加以利用,我始终保持这个key所在的坑位是在数组最右端,找到一个大数与key位置交换后,数组长度减一,仍然再把坑位换回最右端,以保证每次换大数到最右端。

int Partion3(int*arr,int left,int right)
{
    if (right - left>2)
        MidThree(arr, left, right);
    int key = arr[right];
    //找大来填坑
    int start = left;
    int hole = right;//三数取中法是置坑位在最右端
    while (start < hole)
    {
        while (start < hole&&arr[start] <= key)
            ++start;//start找到比key大的数
        if (start < hole)
        {
            Swap(&arr[start], &arr[hole]);//大数入坑
            --hole;
        }
        if (start<hole)
        Swap(&arr[start], &arr[hole]);//仍然将坑位置于最右端
    }
    return hole;
}

是不是有点像搜索二叉树的特性,key相当于根节点。
这个key很关键,他最好能是这个数组的中位数。

这里优化的方法称之为三数取中法,key取下标为left、right、或者数组中间值mid三数中处于中间的的那个数。

//快速排序
void MidThree(int*arr,int left,int right)//三数取中位数放在数组最有端
{
    int mid = (right + left)>>1;

    if (arr[left] > arr[right])
        Swap(&arr[left], &arr[right]);//先确保arr[left]<arr[right]

    if (arr[mid] < arr[left])//arr[left]是中位数
        Swap(&arr[left], &arr[right]);
    else if (arr[mid] < arr[right])//arr[mid]是中位数
        Swap(&arr[mid], &arr[right]);
}


void QuickSort(int* arr,int left,int right)//快速排序
{
    if (left < right)
    {
        int div = Partion3(arr, left, right);
    //这里可以分别调用Partion1、partion2、partion3测试
        QuickSort(arr, 0, div-1);
        QuickSort(arr,div, right);
    }
}

这里写图片描述

重难点:

快排非递归:
需用用到的栈的相关实现在这里:
栈和队列的实现:
https://blog.csdn.net/vickers_xiaowei/article/details/80016979

//非递归快排
void QuickSort(int* arr, int left, int right)
{
    Stack s;
    StackInit(&s, 20);

    if (right - left > 2)
        MidThree(arr, left, right);
    int key = arr[right];
    int keyindex = right;
    int begin = left, end = right;


    while (begin < end || !StackEmpty(&s))
    {
        int mright = end;//这里记录下在进行移动的数组的end和begin
        //在产生相遇点,产生新的取件后,判断新的两段区间是否还需要入栈时候用
        int mleft = begin;

        while (begin < end)
        {
            while (begin < end&&arr[begin] <= key)
                ++begin;
            while (begin < end&&arr[end] >= key)
                --end;
            //arr[begin]一定是比key大的数
            //arr[end]一定是比key小的数
            if (begin<end)
                Swap(&arr[begin], &arr[end]);//小数放前边,大数放后边
            //到此begin==end,而且arr[begin]>key;    
        }
        if (begin != keyindex)
            Swap(&arr[begin], &arr[keyindex]);


        if (begin-mleft>1)//前半部分入栈条件是前半部分不少于1
        {
            StackPush(&s, 0);
            StackPush(&s, begin - 1);
        }
        if (keyindex - begin>1)//后半部分入栈的条件是后半部分不少于1
        {
            StackPush(&s, begin);
            StackPush(&s, mright);
        }
        if (!StackEmpty(&s))//如果栈不为空取两个数,作为新的区间
        {
            end = StackTop(&s);
            StackPop(&s);
            begin = StackTop(&s);
            StackPop(&s);
        }

        if (end - begin > 2)
            MidThree(arr, begin, end);
        key = arr[end];//更新关键码key
        keyindex = end;
    }
}

有关其它排序:
排序一——直接插入排序and希尔排序
https://blog.csdn.net/vickers_xiaowei/article/details/80613544
排序二——冒泡排序、快速排序三种递归实现and快排非递归
https://blog.csdn.net/vickers_xiaowei/article/details/80646873
排序三——选择排序and堆排序
https://blog.csdn.net/vickers_xiaowei/article/details/80646990
排序四——归并排序和排序结:
https://blog.csdn.net/vickers_xiaowei/article/details/80646992

猜你喜欢

转载自blog.csdn.net/Vickers_xiaowei/article/details/80646873