文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何使用C语言实现快速排序

2023-07-05 19:36

关注

本篇内容主要讲解“ 如何使用C语言实现快速排序”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ 如何使用C语言实现快速排序”吧!

快速排序的基本思想是:任取待排序数列中的一个数作为 key 值,通过某种方法使得 key 的左边所有的数都比它小,右边的数都比它大;以 key 为中心,将 key 左边的数列与右边的数列取出,做同样的操作(取 key 值,分割左右区间),直至所有的数都到了正确的位置。

上述所提到的某种方法可以有很多种,例如:hoare法、挖坑法、前后指针法。它们虽然做法不相同,但做的都是同一件事——分割出 key 的左右区间(左边的数比 key 小,右边的数比 key 大)。

1. hoare法

方法与步骤

以数列 6,1,2,7,9,3,4,5,8,10 为例:

取最左边为 key ,分别有 left 和 right 指向数列的最左端与最右端;

right 先走,找到比 key 小的数就停下来;

left 开始走,找到比 key 大的数就停下来;

交换 left 与 right 所在位置的数;

重复上述操作,right 找小,left 找大,进行交换;

right 继续找小;

left 继续找大,若与 right 就停下来;

交换二者相遇位置与 key 处的值;

此时一趟排序就完成了,此时的数列有两个特点:

key 所指向的值(6)已经到了正确的位置;

key 左边的数字都比 key 要小,右边的都比 key 要大;

接下来就是递归的过程了,分别对左右区间进行同样的操作:

代码实现

知道了详解步骤,用代码来实现并不困难,但是有很多很多的细节需要注意。(这里的代码未经优化,当前的代码有几种极端的情况不能适应)

void Swap(int* p, int* q){int tmp = *p;*p = *q;*q = tmp;} void QuickSort(int* a, int begin, int end){    //数列只有一个数,或无数列则返回if (begin >= end){return;} int left = begin;int right = end; int keyi = left; while (left < right){        //右边先走while (left < right && a[right] >= a[keyi]){right--;} while (left < right && a[left] <= a[keyi]){left++;} Swap(&a[left], &a[right]);} Swap(&a[keyi], &a[left]); QuickSort(a, begin, left - 1);QuickSort(a, left + 1, end);}

2. 挖坑法

挖坑法相比于hoare法,思路上更为简单易懂。

方法与步骤

还是以同样的数列 6,1,2,7,9,3,4,5,8,10 为例:

先将第一个数存放到 key 中,形成一个坑位:分别有 left 和 right 指向数列的最左端与最右端; 

  right 先走,找到比 key 小的数,将该数丢到坑里;同时又形成了一个新的坑;

left 开始走,找到比 key 大的数,将该数丢到坑里;同时形成一个新的坑;

 4. right继续找小,进行重复的操作;

left 找大;

right 找小;

left 找大;

若二者相遇就停下来;将 key 值放入坑;

至此,一趟排序已经完成,我们发现此时的数列与hoare具有相同的特点:

key 所指向的值(6)已经到了正确的位置;

key 左边的数字都比 key 要小,右边的都比 key 要大;

挖坑法、hoare、前后指针法完成一趟排序后都具有相同的特点,所以不同版本的快速排序不一样的只有单趟排序的实现,总体思路都是相同的。

代码实现

void QuickSort(int* a, int begin, int end){if (begin >= end){return;} int left = begin;int right = end; int key = a[left];int hole = left;//坑位 while (left < right){while (left < right && a[right] >= key){right--;}a[hole] = a[right];hole = right; while (left < right && a[left] <= key){left++;} a[hole] = a[left];hole = left;} a[hole] = key; QuickSort(a, begin, hole - 1);QuickSort(a, hole + 1, end);}

3. 前后指针法

方法与步骤

以同样的数列为例:

取第一个值为 key ;有 prev 和 cur 分别指向数列开头和 prev 的下一个数;

cur 先走,找到比 key 小的数就停下来;

++prev ,交换 prev 与 cur 位置的数;(前两次无需交换,因为自己与自己换没有意义)

重复此步骤;

直到 cur 走完整个数列,交换 prev 与 key 处的值;

至此,第一趟排序就结束了,又是与前两种方法相同的结果;

代码实现

void QuickSort(int* a, int begin, int end){if (begin >= end){return;} int prev = begin;int cur = prev + 1; int keyi = begin; while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;} Swap(&a[keyi], &a[prev]);keyi = prev; QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}

4. 快速排序的缺点与优化

1.快速排序的缺点

我们用三种方式实现了快速排序,其实这三种方式并无明显的优劣之分。但是我们前面设计的快速排序其实是有两个缺点的:

在最坏情况下它的的效率极慢;

在数据量太大时会造成栈溢出。

那么什么情况是最坏情况呢?答案是,当数据本身就是有序的时候(无论是逆序还是顺序)。在最坏情况下,每次我们的 key 值都是最大或者最小,这样就会使 key 与数列的每个数都比较一次,它的时间复杂度为 O(n^2);

为什么会发生栈溢出呢?因为我们的快速排序是利用递归实现的,有递归调用,就要建立函数栈帧,并且随着递归的深度越深所要建立的函数栈帧的消耗就越大 。如这幅图所示:

2.快速排序的优化

① 三数取中法选 key

为了应对最坏情况会出现时间复杂度为 O(N^2) 的情况,有人提出了三数取中的方法。

旧方法中,我们每次选 key 都是数列的第一个元素。三数取中的做法是,分别取数列的第一个元素、最后一个元素和最中间的元素,选出三个数中不是最大也不是最小的那个数当作 key 值。

有了三数取中,之前的最坏情况立马变成了最好情况。

代码实现

由于hoare法、挖坑法、前后指针法最终的效果都相同且效率差异很小,所以就任意选取一个为例,其余两者都类似。

//三数取中的函数int GetMidIndex(int* a, int begin, int end){int mid = (begin + end) / 2; if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}}//hoare法void QuickSort(int* a, int begin, int end){if (begin >= end){return;} int mid = GetMidIndex(a, begin, end);Swap(&a[mid], &a[begin]); int left = begin;int right = end;int keyi = left; while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;} Swap(&a[left], &a[right]);} Swap(&a[keyi], &a[left]); QuickSort(a, begin, left - 1);QuickSort(a, left + 1, end);}
② 小区间优化

随着递归的调用越深入,此时有个很大的缺点就是函数栈帧的消耗很大。但是同时又有一个好处,就是越往下,数列就越接近有序,且此时每个小区间的数据个数特别少。

那么有什么办法可以取其长处避其短处呢?不知道你是否还记得插入排序的特点&mdash;&mdash;数据越接近有序,效率就越高。并且,在数据量极少的情况下,时间复杂度为 O(N^2) 的插入排序与时间复杂度为 O(N*log N) 的快速排序基本没有什么区别。所以,我们干脆就在排序数据量少的数列时,采用插入排序代替。

代码实现
//三数取中的函数int GetMidIndex(int* a, int begin, int end){int mid = (begin + end) / 2; if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}} //插入排序void InsertSort(int* a, int n){for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp)         //大于tmp,往后挪一个{a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;          //把tmp插入空隙}} //hoare法void QuickSort(int* a, int begin, int end){if (begin >= end){return;} if ((end - begin + 1) < 15){// 小区间用直接插入替代,减少递归调用次数InsertSort(a+begin, end - begin + 1);}else{int mid = GetMidIndex(a, begin, end);Swap(&a[mid], &a[begin]); int left = begin;int right = end;int keyi = left; while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;} Swap(&a[left], &a[right]);} Swap(&a[keyi], &a[left]); QuickSort(a, begin, left - 1);QuickSort(a, left + 1, end);}}

两外两种方法的代码实现已打包完成,可在文末直接取用。

5. 快速排序的非递归实现

快速排序的非递归思路与递归相差无几,唯一不同的是,非递归用栈或队列模拟函数递归建立栈帧的过程。

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 keyi = PartSort1(a, left, right);//三种方法任选其一//int keyi = PartSort2(a, left, right);//int keyi = PartSort3(a, left, right); if (keyi + 1 < right){StackPush(&st, keyi + 1);StackPush(&st, right);} if (left < keyi - 1){StackPush(&st, left);StackPush(&st, keyi - 1);}} StackDestroy(&st);}

附录﹡完整源码

快速排序递归实现

//交换函数void Swap(int* p, int* q){int tmp = *p;*p = *q;*q = tmp;} //三数取中int GetMidIndex(int* a, int begin, int end){int mid = (begin + end) / 2; if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}} //插入排序void InsertSort(int* a, int n){for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp)         //大于tmp,往后挪一个{a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;          //把tmp插入空隙}} // Hoare法int PartSort1(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int left = begin, right = end;int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){--right;} while (left < right && a[left] <= a[keyi]){++left;} Swap(&a[left], &a[right]);} Swap(&a[left], &a[keyi]);keyi = left; return keyi;} // 挖坑法int PartSort2(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int left = begin, right = end;int key = a[left];int hole = left;while (left < right){while (left < right && a[right] >= key){--right;} a[hole] = a[right];hole = right; while (left < right && a[left] <= key){++left;} a[hole] = a[left];hole = left;} a[hole] = key;return hole;} //前后指针法int PartSort3(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]); ++cur;} Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;} //快速排序(递归)void QuickSort(int* a, int begin, int end){if (begin >= end){return;} if ((end - begin + 1) < 15){// 小区间用直接插入替代,减少递归调用次数InsertSort(a + begin, end - begin + 1);}else{int keyi = PartSort1(a, begin, end);//int keyi = PartSort2(a, begin, end);//int keyi = PartSort3(a, begin, end); QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}}

快速排序非递归实现

#include<stdio.h>#include<stdlib.h>#include<assert.h>#include<stdbool.h> typedef int STDataType; typedef struct Stack{STDataType* a;  //动态开辟数组int capacity; //记录栈的容量大小int top; //记录栈顶的位置}Stack; //栈的初始化void StackInit(Stack* ps);//释放动态开辟的内存void StackDestroy(Stack* ps);//压栈void StackPush(Stack* ps, STDataType data);//出栈void StackPop(Stack* ps);//读取栈顶的元素STDataType StackTop(Stack* ps);//判断栈是否为空bool StackEmpty(Stack* ps);  // Hoare法int PartSort1(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int left = begin, right = end;int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){--right;} while (left < right && a[left] <= a[keyi]){++left;} Swap(&a[left], &a[right]);} Swap(&a[left], &a[keyi]);keyi = left; return keyi;} // 挖坑法int PartSort2(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int left = begin, right = end;int key = a[left];int hole = left;while (left < right){while (left < right && a[right] >= key){--right;} a[hole] = a[right];hole = right; while (left < right && a[left] <= key){++left;} a[hole] = a[left];hole = left;} a[hole] = key;return hole;} int PartSort3(int* a, int begin, int end){int mid = GetMidIndex(a, begin, end);Swap(&a[begin], &a[mid]); int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]); ++cur;} Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;}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 keyi = PartSort1(a, left, right);//三种方法任选其一//int keyi = PartSort2(a, left, right);//int keyi = PartSort3(a, left, right); if (keyi + 1 < right){StackPush(&st, keyi + 1);StackPush(&st, right);} if (left < keyi - 1){StackPush(&st, left);StackPush(&st, keyi - 1);}} StackDestroy(&st);} //栈的实现_函数定义 void StackInit(Stack* ps){assert(ps);//初始化时,可附初值,也可置空ps->a = NULL;ps->capacity = 0;ps->top = 0;} void StackDestroy(Stack* ps){assert(ps);//若并未对ps->a申请内存,则无需释放if (ps->capacity == 0)return;//释放free(ps->a);ps->a = NULL;ps->capacity = ps->top = 0;} void StackPush(Stack* ps,STDataType data){assert(ps);//若容量大小等于数据个数,则说明栈已满,需扩容if (ps->capacity == ps->top){//若为第一次扩容,则大小为4,否则每次扩大2倍int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);} ps->a = tmp;ps->capacity = newCapacity;}//压栈ps->a[ps->top] = data;ps->top++;} void StackPop(Stack* ps){assert(ps);assert(!StackEmpty(ps));//出栈ps->top--;} STDataType StackTop(Stack* ps){assert(ps);assert(!StackEmpty(ps));//返回栈顶的数据return ps->a[ps->top - 1];} bool StackEmpty(Stack* ps){assert(ps);//返回topreturn ps->top == 0;}

到此,相信大家对“ 如何使用C语言实现快速排序”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯