排序算法:快速排序和归并排序

快速排序(升序排序):快速排序的核心就是找到一个基准值(我们这里采用最后一个元素作为基准值),然后在剩下的区间里从左往右找到一个大于基准值的数,再从右往左找到一个小于基准值的数然后将二者交换,直到二者相遇一趟排序结束。然后在继续下一趟排序。
这里写图片描述
这里写图片描述

////////////////////////////////////
//快速排序的递归版本
////////////////////////////////////
//交换法
int Partion(int arr[],int beg,int end)
{
    if(end - beg <= 1)
    {
        return beg;
    }
    int left = beg;
    int right = end-1;
    //取数组的最后一个元素作为基准值
    int key = arr[right];
    while(left < right)
    {
        //从左往右找到一个大于基准值的元素
        while(left < right && arr[left] <= key)
        {
            left++;
        }
        //从右往左找到一个小于基准值的元素
        while(left < right && arr[right] >= key)
        {
            right--;
        }
        if(left < right)
        {
            swap(&arr[left],&arr[right]);
        }
    }
    //最后把left指向的位置和基准值的位置进行交换
    swap(&arr[left],&arr[end-1]);
    return left;
}
//挖坑法
int Partion2(int arr[],int beg,int end)
{
    if(end -beg <= 1)
    {
        return beg;
    }
    int left = beg;
    int right = end-1;
    int key = arr[right];
    while(left < right)
    {
        while(left < right && arr[left] <= key)
        {
            left++;
        }
        //循环退出,意味着left就指向了一个大于基准值的位元素
        //就可以把这个值填到刚才right指向的坑里
        //一旦赋值操作完成,left自身也就成为了一个坑
        if(left < right)
        {
            arr[right--] = arr[left];
        }
        while(left < right && arr[right] >= key)
        {
            right--;
        }
        //循环退出,意味着right指向了一个小于基准值的元素
        //就可以把这个值填到刚才left指向的坑里
        //一旦赋值操作完成,right自身就又变成了一个坑
        if(left < right)
        {
            arr[left++] = arr[right];
        }
    }
    //一旦left和right相遇,那么说明整个区间就整理完毕了
    //还剩下一个坑需要填,此时就把基准值填进去就可以了
    arr[left] = key;
    return left;
}
void _QuickSort(int arr[],int beg,int end)
{
    if(end - beg <= 1)
    {
        //要么没有元素要么就只有一个元素
        return;
    }
    //[beg,mid)左区间
    //[mid+1,end)右区间
    //左区间的所有元素一定都小于等于右区间的所有元素
    int mid = 0;
    mid = Partion(arr,beg,end);
    _QuickSort(arr,beg,mid);
    _QuickSort(arr,mid+1,end);
}
//快速排序
void QuickSort(int arr[],int len)
{
    if(len <= 1)
    {
        //不需要排序
        return;
    }
    //[0,len)
    _QuickSort(arr,0,len);
}

///////////////////////////////////
//非递归版本
///////////////////////////////////
void QuickSortByLoop(int arr[],int len)
{
    if(len <= 1)
    {
        //不需要排序
        return;
    }
    Stack stack;
    StackInit(&stack);
    int beg = 0;
    int end = len;
    //需要注意的是我们这里每次入栈总是入栈两次
    //即我们入栈了一对儿值。而这一对值就是一个区间
    //[beg,end)
    StackPush(&stack,beg);
    StackPush(&stack,end);
    while(1)
    {
        //取栈顶元素
        //由于入栈时的一对值是一个区间
        //所以后入展的一定是该区间的右边范围值
        //即该区间的结束位置
        int ret = StackTop(&stack,&end);
        if(ret == 0)
        {
            //栈为空,说明快速排序就结束了
            break;
        }
        //走到这里说明有区间待排序
        //所以将该栈顶元素弹出
        StackPop(&stack);
        //再取栈顶元素就取到该区间的左边结束位置的值
        StackTop(&stack,&beg);
        //[beg,end)相当于是即将要进行快速排序
        //进行整理的区间
        if(end-beg <= 1)
        {
            //此时该区间内有吗有一个元素要么一个元素都没有
            //因此不需要排序,直接进行下一趟循环
            continue;
        }
        //整理该区间
        int mid = Partion(arr,beg,end);
        //在继续将区间压栈,然后进行下一趟循环
        //[beg,mid),[mid+1,end]
        StackPush(&stack,beg);
        StackPush(&stack,mid);
        StackPush(&stack,mid+1);
        StackPush(&stack,end);
    }
}
//测试一下
void TestQuickSort()
{
    Test_Header;
    int arr[] = {2,5,1,9,6,23,12,0,45,31,44,25};
    int len = sizeof(arr)/sizeof(arr[0]);
    //QuickSort(arr,len);
    QuickSortByLoop(arr,len);
    printf("expect:0,1,2,5,6,9,12,23,25,31,44,45\n");
    printf("actual:");
    int i = 0;
    for(;i < len;i++)
    {
        if(i != len-1)
            printf("%d,",arr[i]);
        else
            printf("%d\n",arr[i]);
    }
}

测试结果:
这里写图片描述

快速排序:
时间复杂度:平均为O(N*logN),最坏O(N^2)
空间复杂度:最坏O(N)
稳定性:不稳定排序

归并排序(升序排序):将待排序的的区间分成两个长度相等的小区间,对每一个小区间进行排序,所有的小区间排序完毕在将小区间合并成一个区间。
这里写图片描述
这里写图片描述

//////////////////////////////////////////
//递归版本
//////////////////////////////////////////

//合并区间函数(这与我们之前写的合并两个有序链表的思路相同)
void _MergeArr(int arr[],int beg,int mid,int end,int *tmp)
{
    int cur1 = beg;
    int cur2 = mid;
    int tmp_index = beg;
    while(cur1 < mid && cur2 < end)
    {
        //此处我们需要升序排序
        //比较后将较小的一个元素放到新的区间中去
        if(arr[cur1] < arr[cur2])
        {
            //此时cur1对应的元素比较小
            //就将cur1对应的元素放到新区间中去
            tmp[tmp_index++] = arr[cur1++];
            //并且将cur1和tmp_index往后移动一步
            //用于下一次的元素比较和存放
        }
        else
        {
            //此时cur2对应的元素比较小
            //就将cur2对应的元素放到新区间中去
            tmp[tmp_index++] = arr[cur2++];
            //并且将cur2和tmp_index往后移动一步
            //用于下一次的元素比较和存放
        }
    }
    //走到这说明是某一个区间遍历完了
    //此时我们需要把剩下的没有遍历完的区间
    //中剩下的所有元素依次放入新的空间(tmp)中去
    while(cur1 < mid)
    {
        //此时是cur2走完了,cur1还剩下有元素没有处理
        //所以将cur1往后剩下的元素放到新的空间中去
        tmp[tmp_index++] = arr[cur1++];
    }
    while(cur2 < end)
    {
        //此时是cur1走完了,cur2还剩下有元素没有处理
        //所以将cur2往后剩下的元素放到新的空间中去
        tmp[tmp_index++] = arr[cur2++];
    }
    //最后把tmp中的内容拷贝到数组中去即可完成排序
    //进行归并的时候,处理的区间是[beg,end)
    //对应的会把这部分区间的元素填充到tmp[beg,end)区间上
    //此时这里的拷贝会arr的动作,要保证结果放到正确的区间上
    memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
    return;
}
//[beg,end)就是我们当前要处理的子数组
void _MergeSort(int arr[],int beg,int end,int *tmp)
{
    if(end - beg <= 1)
    {
        //递归出口
        //要么只有一个元素,要么没有元素
        return;
    }
    int mid = beg+(end-beg)/2;
    //此时我们就有两个区间
    //[beg,mid),和[mid,end)
    //然后对于这两个区间都调用该函数
    //对于两个区间进行递归的处理
    //此处的处理就是划分区间
    //直到最后只剩下一个元素不能再划分为止
    _MergeSort(arr,beg,mid,tmp);
    _MergeSort(arr,mid,end,tmp);
    //合并区间,合并后的区间内的元素是有序的
    _MergeArr(arr,beg,mid,end,tmp);
    return;
}
void MergeSort(int arr[],int len)
{
    if(len <= 1)
    {
        //不需要排序
        return;
    }
    //此时我们创建一个临时空间用来合并元素
    int *tmp = (int*)malloc(sizeof(int)*len);
    _MergeSort(arr,0,len,tmp);
    free(tmp);
}

//////////////////////////////////////////
//非递归版本
//////////////////////////////////////////
void MergeSortByLoop(int arr[],int len)
{
    if(len <= 1)
    {
        //不需要排序
        return;
    }
    //创建一个临时空间用来合并元素
    int *tmp = (int*)malloc(sizeof(int)*len);
    //定义一个步长gap,初始为1,
    //相当于每次合并两个长度为gap的有序区间
    int gap = 1;
    for(;gap < len;gap*=2)
    {
        //在当前gap下用i辅助完成所有长度为gap的区间的合并
        int i = 0;
        for(;i < len;i+=2*gap)
        {
            //[beg,mid),[mid,end)
            int beg = i;
            int mid = i+gap;
            int end = i+2*gap;
            if(mid > len)
            {
                mid = len;
            }
            if(end > len)
            {
                end = len;
            }
            //排序
            _MergeArr(arr,beg,mid,end,tmp);
        }
    }
    free(tmp);
}
//测试一下:
void TestMergeSort()
{
    Test_Header;
    int arr[] = {2,5,1,9,6,23,12,0,45,31,44,25};
    int len = sizeof(arr)/sizeof(arr[0]);
    //MergeSort(arr,len);
    MergeSortByLoop(arr,len);
    printf("expect:0,1,2,5,6,9,12,23,25,31,44,45\n");
    printf("actual:");
    int i = 0;
    for(;i < len;i++)
    {
        if(i != len-1)
            printf("%d,",arr[i]);
        else
            printf("%d\n",arr[i]);
    }
}

测试结果:
这里写图片描述

归并排序:
时间复杂度:最差和平均时间复杂度都是O(n*logn)
空间复杂度:O(n)
稳定性:稳定排序

猜你喜欢

转载自blog.csdn.net/qq_40927789/article/details/80554576