C语言数据结构——快速排序和归并排序

一、快速排序

1.1 基本思想

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

1.2 将数组分成两个部分的方法

1.2.1 hoare版本

实现思路:取left为tmp则从right开始,取right为tmp则从left开始,这里我们取left为tmp, right先走,遇到比tmp小的值停下,然后left开始走遇到比tmp大的值停止,然后left和right所指向的数据交换位置,然后继续重复,直到right和left相遇,相遇位置的数据和tmp交换此时以tmp为中心,数组被分成了两个部分然后将两个部分看成新数组,进行递归排序,当数组只有一个值的时候排序结束
//1.quare法
int partsort1(int* a, int begin, int end)
{
	int left = begin, right = end;
	int tmp = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[tmp])
		{
			right--;
		}
		while (left < right && a[left] <= a[tmp])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[tmp], &a[right]);
	tmp = left;
	return tmp;
}

1.2.2 挖坑法

基本思路:挖坑法顾名思义就是一个挖坑填坑的过程,tmp在那边,坑就从那边开始, 如图left为tmp,则坑就在left,然后right开始找小,找到后将其值赋给坑,此时right变新坑,然后left找大,找到后将其值赋给坑,然后left变新坑,当left和right相遇时,将tmp放到坑里,单次循环结束,然后数组以tmp为中心被分成两个部分每个部分看成一个新数组,进行递归排序,当数组只剩下一个值的时候,排序完成。
//2.挖坑法
int partsort2(int* a, int begin, int end)
{
    int left = begin, right = end;
    int tmp = a[left];
    int pit = left;
    while (left < right)
    {
        while (left < right && a[right] >= tmp)
        {
            right--;
        }
        a[pit] = a[right];
        pit = right;
        while (left < right && a[left] <= tmp)
        {
            left++;
        }
        a[pit] = a[left];
        pit = left;
    }
    a[pit] = tmp;
    return pit;
}

1.2.3 前后指针版本

基本思想: 设立前后指针prev,cur,取左边为tmp,然后cur找小,找到大时cur跳过,当找到小的时候,此时prev+1要么和cur重合,要么prev+1指向的值大于tmp,所以prev+1和cur指向的值交换,当cur越界的时候,prev和tmp交换,此时以tmp为中心,分成两个部分每个部分看成一个新的数组进行递归排序。
//3.双指针法
int partsort3(int* a, int begin, int end)
{
    int prev = begin;
    int cur = begin + 1;
    int tmp = begin;
    while (cur<=end)
    {
        if (a[cur] < a[tmp])
        {
            prev++;
            Swap(&a[prev], &a[cur]);
        }
        cur++;
    }
    Swap(&a[tmp], &a[prev]);
    tmp = prev;
    return tmp;
}

1.3 快排的递归实现及非递归实现

1.3.1 递归实现

基本思路:快排的递归实现主要是着力于 快排基本框架和二叉树的前序遍历类似,所以用利用二叉树的前序遍历递归写出即可。

代码后续会贴出。

1.3.2 非递归实现

基本思路:非递归快排我们用 栈的的后进先出来实现(又是c语言库里没有栈,所以需要自己写,不太了解的老铁可以看我之前的文章,有写如何实现栈), 因为快排每次排序都将数组分成两个部分,所以我们先将最开始的数组的左右下标压入栈中,然后在出栈,对其进行排序,然后把新的到的两个数组各自的左右下标压入栈中,当栈为空的时候,排序完成

1.4 快排的问题及优化

1.4.1 快排的问题

1.tmp是最大或者最小的一些特殊情况下,时间复杂度为0(N^2)
2.当数比较多的时候,递归所需要开辟的栈过多,容易造成栈溢出

1.4.2 快排的优化

1.三数取中法

三数取中法就是,取left,right以及数组中间的值,比较三者的大小,取中间值作为tmp,这样就避免了tmp是最大或者最小值的情况。
//三数取中法优化
int GetMidIndex(int* a, int begin, int end)
{
    int min = (begin + end) / 2+1;
    if (a[begin] < a[min])
    {
        if (a[min] < a[end])
        {
            return min;
        }
        else if (a[begin] > a[end])
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else
    {
        if (a[begin] < a[end])
        {
            return begin;
        }
        else if (a[min] > a[end])
        {
            return min;
        }
        else
        {
            return end;
        }
    }
}

2.小区间优化法

当数组长度较小时,也就是递归展开的最后几层,所需要的栈占总需求的大部分,此时如果用插入排序就可以有效避免栈溢出的现象。

代码后续贴出。

1.5 快排的总体特性

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定,如下图所示

1.6 代码

//1.quare法
int partsort1(int* a, int begin, int end)
{
    int left = begin, right = end;
    int tmp = left;
    while (left < right)
    {
        while (left < right && a[right] >= a[tmp])
        {
            right--;
        }
        while (left < right && a[left] <= a[tmp])
        {
            left++;
        }

        Swap(&a[left], &a[right]);
    }
    Swap(&a[tmp], &a[right]);
    tmp = left;
    return tmp;
}
//2.挖坑法
int partsort2(int* a, int begin, int end)
{
    int left = begin, right = end;
    int tmp = a[left];
    int pit = left;
    while (left < right)
    {
        while (left < right && a[right] >= tmp)
        {
            right--;
        }
        a[pit] = a[right];
        pit = right;
        while (left < right && a[left] <= tmp)
        {
            left++;
        }
        a[pit] = a[left];
        pit = left;
    }
    a[pit] = tmp;
    return pit;
}
//3.双指针法
int partsort3(int* a, int begin, int end)
{
    int prev = begin;
    int cur = begin + 1;
    int tmp = begin;
    while (cur<=end)
    {
        if (a[cur] < a[tmp])
        {
            prev++;
            Swap(&a[prev], &a[cur]);
        }
        cur++;
    }
    Swap(&a[tmp], &a[prev]);
    tmp = prev;
    return tmp;
}
//三数取中法优化
int GetMidIndex(int* a, int begin, int end)
{
    int min = (begin + end) / 2+1;
    if (a[begin] < a[min])
    {
        if (a[min] < a[end])
        {
            return min;
        }
        else if (a[begin] > a[end])
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else
    {
        if (a[begin] < a[end])
        {
            return begin;
        }
        else if (a[min] > a[end])
        {
            return min;
        }
        else
        {
            return end;
        }
    }
}
//快排
void QuickSort(int* a, int begin, int end)
{
    if (begin >= end)
    {
        return;
    }
    int tmp = GetMidIndex(a, begin, end);
    //小区间优化
    if (end - begin < 15)
    {
        InsertSort(a + begin, end - begin+1);
    }
    else
    {
        tmp = partsort2(a, begin, end);
        QuickSort(a, begin, tmp - 1);
        QuickSort(a, tmp + 1, end);
    }
    
}
//非递归快排
void QuickSortNonR(int* a, int begin, int end)
{
    Stack st;
    StackInit(&st);
    StackPush(&st, begin);
    StackPush(&st, end);
    while (StackEmpty(&st))
    {
        //后进先出,右边后进的所以先取右边
        int right = StackTop(&st);
        StackPop(&st);
        int left = StackTop(&st);
        StackPop(&st);
        int tmp = partsort1(a, left, right);
        //先排左边,就要先入右边
        if (tmp + 1 < right)
        {
            StackPush(&st, tmp + 1);
            StackPush(&st, right);
        }
        if (left < tmp - 1)
        {
            StackPush(&st, left);
            StackPush(&st, tmp-1);
        }
    }
    StackDestroy(&st);
}

二、归并排序

2.1 基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

2.2 递归实现

基本思路:因为归并排序的展开图和二叉树类似,而且归并排序的核心思想是先让子序段有序,在合并,所以可以用 二叉树的后序遍历的思想来实现
单次排序的思路;单次排序类似于将 两个有序数组合并成一个有序数组的过程,先创建一个长度和原数组长度相等的数组tmp,然后将两个有序的子序数组取小尾插进tmp里,全部插入后,再将tmp的数据memcpy进原数组
//归并排序单次排序
void _MergeSort(int* a, int begin, int end, int* tmp)
{
    if (begin >= end)
    {
        return;
    }
    int mid = (begin + end) / 2;
    //分解
    _MergeSort(a, begin, mid, tmp);
    _MergeSort(a, mid+1, end, tmp);
    //合并
    int begin1 = begin,begin2=mid+1;
    int i = begin;
    while (begin1 <= mid && begin2 <= end)
    {
        if (a[begin1] <= a[begin2])
        {
            tmp[i++] = a[begin1++];
        }
        else
        {
            tmp[i++] = a[begin2++];
        }
    }
    while (begin1 <= mid)
    {
        tmp[i++] = a[begin1++];
    }
    while (begin2 <= end)
    {
        tmp[i++] = a[begin2++];

    }
    memcpy(a+begin, tmp+begin, sizeof(int)*(end - begin + 1));

}
//归并排序递归
void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        perror("malloc");
        exit(-1);
    }
    _MergeSort(a, 0, n - 1, tmp);
    free(tmp);
    tmp = NULL;
}

2.3 非递归实现

基本思路:设置一个参数rangeN来模拟实现归并排序,当rangeN=1时模拟的是递归的最后一层的实现,为2则是倒数第二层的实现,每次rangeN*2,当rangeN大于数组长度时,排序完成。
//归并排序非递归
void MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        perror("malloc");
        exit(-1);
    }
    int rangeN = 1;
    while (rangeN < n)
    {
        for (int i = 0; i < n; i += rangeN * 2)
        {
            int begin1 = i, end1 = i + rangeN - 1;
            int begin2 = end1 + 1, end2 = begin2 + rangeN - 1;
            int j = i;
            //判断越界情况
            //end1,begin2,end2都越界
            if (end1 >= n)
            {
                break;
            }
            //begin2,end2越界
            else if (begin2 >= n)
            {
                break;
            }
            //end2越界
            else if (end2 >= n)
            {
                end2 = n - 1;
            }
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (a[begin1] <= a[begin2])
                {
                    tmp[j++] = a[begin1++];
                }
                else
                {
                    tmp[j++] = a[begin2++];
                }
            }
            while (begin1 <= end1)
            {
                tmp[j++] = a[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[j++] = a[begin2++];

            }
            memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
        }
        rangeN *= 2;
    }
    


    free(tmp);
    tmp = NULL;
}

注意:写代码时需要注意一下数组越界的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值