用C语言实现各种排序

算法分类

十种常见排序算法可以分为两大类:

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

算法复杂度

相关概念

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机

内执行时所需存储空间的度量,它也是数据规模n的函数。

1、冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1.1 算法描述

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

1.2 动图演示

1.3 代码实现

void BubbleSort(int* a, int n)
{for (int j = 0; j < n - 1; j++){for (int i = 0; i < n - 1-j; i++){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);}}}}

2、选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

2.1 算法描述

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1…n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

2.2 动图演示

2.3 代码实现

void SelectSort(int* a, int n)
{int begin = 0;int end = n - 1;int more = 0;int less = 0;while (begin < end){for (int cur = begin; cur <= end; cur++){if (a[less] > a[cur]){less = cur;}if (a[cur] > a[more]){more = cur;}}Swap(&a[begin], &a[less]);if (begin == more){more = less;}Swap(&a[end], &a[more]);begin++;end--;}
}

2.4 算法分析

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。

3、插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

3.1 算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

3.2 动图演示

3.2 代码实现

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){a[end + 1] = a[end];end--;}elsebreak;}a[end + 1] = tmp;}}

3.4 算法分析

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

4、希尔排序(Shell Sort)

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

4.1 算法描述

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

4.2 动图演示

4.3 代码实现

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end-=gap;}elsebreak;}a[end + gap] = tmp;}gap--;}
}

4.4 算法分析

希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。

5、归并排序(Merge Sort)

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

5.1 算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

5.2 动图演示

5.3 代码实现

//递归实现
void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;_MergeSort(a, left, mid, tmp);_MergeSort(a, mid+1, right, tmp);int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}for (int j = left; j <= right; j++){a[j] = tmp[j];}
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}_MergeSort(a, 0, n-1, tmp);free(tmp);tmp = NULL;
}//非递归实现
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}int gap = 1;while (gap < n){for (int i = 0; i < n; i += gap * 2){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;if (end1 >= n ||  begin1 >= n)break;if (end2 >= n)end2 = n - 1;int index = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}for (int j = i; j <= end2; j++){a[j] = tmp[j];}}gap *= 2;}
}

5.4 算法分析

归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

6、快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

6.1 算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2 动图演示

6.3 代码实现

//三数取中
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[right]){if (a[mid] < a[left]){return left;}else if(a[mid] > a[right]){return right;}else{return mid;}}else//a[left]>=a[rigth]{if (a[mid] > a[left]){return left;}else if (a[mid] < a[right]){return right;}else{return mid;}}
}//horne法
int PartSort1(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);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]);return left;
}
//挖坑法
int PartSort2(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int key = a[left];int pit = left;while (left < right){while (left < right && a[right] >= key){right--;}a[pit] = a[right];pit = right;while (left < right && a[left] <= key){left++;}a[pit] = a[left];pit = left;}a[pit] = key;return left;
}
//前后指针法
int PartSort3(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int cur = left + 1;int prev = left;int key = left;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}int QuickSort(int* a, int left, int right)
{if (left >= right)return;int key = PartSort3(a, left, right);QuickSort(a, left, key-1); QuickSort(a, key+1, right);
}
//快排非递归 用栈辅助实现
void QuickSortNonR(int* a, int left, int right)
{ST st;StackInit(&st);StackPush(&st, left);StackPush(&st, right);while (!StackEmpty(&st)){int end = StackTop(&st);StackPop(&st);int begin = StackTop(&st);StackPop(&st);int key = PartSort3(a, begin, end);if (key + 1 < end){StackPush(&st, key + 1);StackPush(&st, end);}if (begin < key-1){StackPush(&st, begin);StackPush(&st, key-1);}}StackDestroy(&st);
}

7、堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

7.1 算法描述

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

7.2 动图演示

7.3 代码实现

void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void AdjustUp(int* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[parent] < a[child]){Swap(&a[parent], &a[child]);child = parent;int parent = (child - 1) / 2;}elsebreak;}}void AdjustDown(int *a, int n, int root)
{int parent = root;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}void HeapSort(int* a, int n)
{for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}for (int end = n - 1; end > 0; end--){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);}
}

用C语言实现各种排序(附动图)相关推荐

  1. 红黑树详解(二)红黑树的插入(附动图和案例)

    红黑树详解(二)红黑树的插入(附动图和案例) 摘要: 在很多源码涉及到大量数据处理的时候,通常都是用红黑树这一数据结构.红黑树是一种自平衡的二叉查找树,它能在进行插入和删除操作时通过特定操作保持二叉查 ...

  2. 算法 - 十大经典排序算法(动图演示)

    [TOC] 算法 - 十大经典排序算法(动图演示) ​ 在计算机科学与数学中,一个排序算法(英语:Sorting algorithm)是一种能将一串资料依照特定排序方式进行排列的一种算法.最常用到的排 ...

  3. Java经典排序算法:选择排序,动图演示排序过程

    Java经典排序算法:选择排序,动图演示排序过程 示意动图: public class Main {public static void main(String[] args) {new Main() ...

  4. 【数据结构】八大排序(超详解+附动图+源码)

    目录 前言 常见排序算法的实现 1.插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 6.1 hoare版本 6.2挖坑法 6.3前后指针法 6.4快速排序优化 6.5快速 ...

  5. 十大经典排序算法Python版实现(附动图演示)

    来源:大数据DT 本文约5200字,建议阅读10分钟 排序算法是<数据结构与算法>中最基本的算法之一.本文介绍10种常见的内部排序算法,及如何用Python实现. 排序算法可以分为内部排序 ...

  6. 一文读懂Python版的十大经典排序算法(附动图演示)

    来源:大数据DT 本文约5200字,建议阅读10分钟 排序算法是<数据结构与算法>中最基本的算法之一.本文介绍10种常见的内部排序算法,及如何用Python实现. 排序算法可以分为内部排序 ...

  7. 十大经典排序算法动图图解

    转载:http://web.jobbole.com/87968/ 0.排序算法说明0.1 排序的定义 对一序列对象根据某个关键字进行排序. 0.2 术语说明 稳定:如果a原本在b前面,而a=b,排序之 ...

  8. 经典排序算法动图图解

    目录 1.冒泡排序(Bubble Sort) 2.选择排序(Selection Sort) 3.插入排序(Insertion Sort) 4.希尔排序(Shell Sort) 5.归并排序(Merge ...

  9. 经典四大排序(动图实现)

    代码出处: 动图出处 插入排序 直接插入排序: 步骤: 动图: 代码: 折半插入排序: 注意点: 流程: 代码: 希尔排序: 过程: 交换排序: 冒泡排序: 性能分析 : 动图: 代码: 进阶版: 快 ...

最新文章

  1. C#的变迁史 - C# 2.0篇
  2. 不允许对系统目录进行即席更新_还不懂Docker?一个故事安排的明明白白!
  3. 第6节 三个败家子(6)——很黄很暴力的刘禅
  4. python web scraping
  5. CentOs基础操作指令(网络配置,RPM包管理)
  6. 数字滤波器(一)--IIR与FIR的基本结构与MATLAB实现
  7. R语言及参考答案(4)
  8. cisco查看模块是单模多模
  9. Mac Mysql 基本操作命令
  10. emlog插件,emlog采集插件,emlog伪原创发布插件
  11. 文件或目录损坏,详细教您文件或目录损坏且无法读取怎么办
  12. 身边的逻辑学——简单的真理不简单(2) 无论如何,清晰思考利多于弊
  13. 区间调度问题(最大利润作业调度问题)
  14. Exp3 免杀原理与实践 20164302 王一帆
  15. XUPT 寒假算法集训第三周
  16. 1024程序员节 技术对抗赛 算法与安全答题 标准答案
  17. android 老人机模式,如何将智能手机切换成老人机模式
  18. CString类详细介绍
  19. Android学习|控件——Notification通知
  20. 三年级计算机的组成教学设计,三年级计算机教学设计.docx

热门文章

  1. BUUCTF:[HBNIS2018]caesar
  2. JavaScript的函数与构造函数
  3. stratascratch 4 Finding User Purchases
  4. MongoDB之多表关联查询
  5. Linux查看防火墙日志
  6. 基于51单片机家庭用暖气节能控制器的设计
  7. Cloud Foundry 快速入门 (cf工具)
  8. 决策树-C4.5例题
  9. uniapp如何引入阿里云矢量图标库
  10. Redis面试知识点,必问10条