@

1. 插入排序

插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按其关键字大小插入前面已排好序的子序列,直到全部记录插入完全。

1.1 直接插入排序

直接插入排序运行动态图如下:



参考代码如下:

  1. #include <stdio.h>
  2. void InsertSort(int[],int);
  3. // 无哨兵的插入排序
  4. void PrintData(int[],int);
  5. void InsertSort2(int[],int);
  6. // 有哨兵的插入排序,数组下标为0处不存储数据元素
  7. void PrintData2(int[],int);
  8. int main(){
  9. int A1[] = {49,38,65,97,76,13,27,49};
  10. InsertSort(A1,8);
  11. PrintData(A1,8);
  12. printf("\n");
  13. int A2[] = {0,49,38,65,97,76,13,27,49};
  14. InsertSort2(A2,8);
  15. PrintData2(A2,8);
  16. return 0;
  17. }
  18. void InsertSort(int A[],int n){
  19. int i,j,temp;
  20. for(i=1;i<n;i++)
  21. if(A[i]<A[i-1]){
  22. temp = A[i];
  23. for(j=i-1;j>=0&&temp<A[j];--j)
  24. A[j+1] = A[j];
  25. A[j+1] = temp;
  26. }
  27. }
  28. void InsertSort2(int A[],int n){
  29. int i,j;
  30. for(i=2;i<=n;i++)
  31. if(A[i]<A[i-1]){
  32. A[0] = A[i];
  33. for(j=i-1;A[0]<A[j];--j)
  34. A[j+1] = A[j];
  35. A[j+1] = A[0];
  36. }
  37. }
  38. void PrintData(int A[],int n){
  39. for(int i=0;i<n;i++)
  40. printf("%d\t",A[i]);
  41. }
  42. void PrintData2(int A[],int n){
  43. for(int i=1;i<=n;i++)
  44. printf("%d\t",A[i]);
  45. }

运行结果如下:

  • 算法的空间复杂度:O(1)
  • 算法的时间复杂度:O(n^2)
  • 稳定性:由于每次插入元素时总是从后向前比较再移动,所以不会出现相同元素相对位置发生变化的情况,即直接插入是一种稳定的排序算法。

1.2 折半插入排序

参考代码如下:

  1. void InsertSort3(int A[],int n){
  2. int i,j,low,high,mid;
  3. for(i=2;i<=n;i++){
  4. A[0] = A[i];
  5. low = 1,high = i-1;
  6. while(low<=high){
  7. mid = (low+high)/2;
  8. if(A[mid] > A[0])
  9. high = mid - 1;
  10. else
  11. low = mid + 1;
  12. }
  13. for(j=i-1;j>=high+1;--j)
  14. A[j+1] = A[j];
  15. A[high+1] = A[0];
  16. }
  17. }
  • 算法时间复杂度:折半插入排序的时间复杂度为O(n^2),但对于数据量不是很大的排序表,折半插入排序往往能表现出很好的性能。

1.3 希尔排序(Shell Sort)

希尔排序的基本思想是:先将待排序表分成L[i,i+d,i+2d,..,i+kd]的“特殊”子表,即把相隔某个“增量”的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。

参考代码如下:

  1. void ShellSort(int A[],int n){
  2. // 希尔排序
  3. int i,j,dk;
  4. for(dk=n/2;dk>=1;dk/=2){
  5. // 步长变化
  6. for(i=dk+1;i<=n;i++){
  7. if(A[i] < A[i-dk]){
  8. A[0] = A[i];
  9. // A[0]元素只是暂存元素,不是哨兵
  10. for(j=i-dk;j>0&&A[j]>A[0];j-=dk)
  11. A[j+dk] = A[j]; // 记录后移,查找插入的位置
  12. A[j+dk] = A[0]; // 插入
  13. }
  14. }
  15. }
  16. }
  • 算法的空间复杂度:O(1)
  • 算法的时间复杂度:希尔排序的时间复杂度约为O(n^1.3),在最坏情况下希尔排序的时间复杂度为O(n^2).
  • 稳定性:当相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,因此希尔排序是一种不稳定的排序方法。

2.交换排序

2.1 冒泡排序

基本思想:从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列比较完毕!

下图为一个冒泡排序的过程(从后往前两两比较相邻元素的值【即先把小的元素往前排】)。



到了第五趟结束后没有发生交换,说明表已经有序。

下图为另外一个冒泡排序的过程(从前往后两两比较相邻元素的值【即先把大的元素往后排】)



参考代码如下:

  1. // 冒泡排序
  2. void BubbleSort(int A[],int n){
  3. int i,j,temp;
  4. bool flag;
  5. for(i=0;i<n-1;i++){
  6. flag = false;
  7. for(j=n-1;j>i;j--){
  8. if(A[j-1]>A[j]){
  9. temp = A[j-1];
  10. A[j-1] = A[j];
  11. A[j] = temp;
  12. flag = true;
  13. }
  14. }
  15. if(!flag)
  16. return;
  17. // 某一趟遍历后没有发生交换,说明表已经有序
  18. }
  19. }
  • 算法的空间复杂度:O(1)
  • 算法的时间复杂度:O(n^2)
  • 算法的稳定性:由于i>j且A[i]=A[j]时,不会发生交换,因此冒泡排序是一种稳定的排序方法。
  • 比较次数:n(n-1)/2
  • 移动次数:3n(n-1)/2

2.2 快速排序

基本思想:在待排序表L[1,n]中任取一个元素pivot作为枢轴(或基准,通常取首元素),通过一趟排序将待排序表划分为独立的两部分L[1,k-1]和L[k+1,n],使得L[1,k-1]中的所有元素小于pivot,L[k+1,n]中的所有元素大于等于pivot,则pivot放在了最终位置L(k)上,这个过程称为一趟快速排序。然后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素为止,即所有元素放在了其最终的位置上。

快速排序过程如下:



参考代码如下:

  1. int Partition(int A[],int low,int high){
  2. int pivot = A[low];
  3. while(low<high){
  4. while(low<high&&A[high]>=pivot)
  5. high--;
  6. A[low] = A[high];
  7. while(low<high&&A[low]<=pivot)
  8. low++;
  9. A[high] = A[low];
  10. }
  11. A[low] = pivot;
  12. return low;
  13. }
  14. void QuickSort(int A[],int low,int high){
  15. if(low<high){
  16. int pivotpos = Partition(A,low,high);
  17. QuickSort(A,low,pivotpos-1);
  18. QuickSort(A,pivotpos+1,high);
  19. }
  20. }
  • 空间复杂度:最好情况下为O(logn),最坏情况下为O(n)
  • 时间复杂度:最好为O(nlogn),最坏为O(n^2),平均情况下为O(nlogn)
  • 稳定性:在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左侧区间后,它们的相对位置发生了变化,即快速排序是一种不稳定的排序方法。
  • 快速排序是所有排序内部排序算法中平均性能最优的排序算法。

【注】:在快速排序算法中,并不产生有序子序列,但每趟排序后会将枢轴(基准)元素放到其最终的位置上。

3. 选择排序

选择排序的基本思想是:每一趟(第i趟)在后面的n-i+1(i=1,2,,...,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n-1趟做完,待排序元素只剩下一个,就不用再选了。

3.1 简单选择排序

简单选择排序算法思想:假设排序表为L[1,n],第i趟排序即从L[i,n]中选取关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可以使得整个排序表有序



参考代码如下:

  1. void SelectSort(int A[],int n){
  2. int i,j,min_index,temp;
  3. for(i=0;i<n-1;i++){
  4. min_index = i;
  5. for(j=i+1;j<n;j++){
  6. if(A[j]<A[min_index]){
  7. min_index = j;
  8. }
  9. }
  10. if(min_index!=i){
  11. temp = A[i];
  12. A[i] = A[min_index];
  13. A[min_index] = temp;
  14. }
  15. }
  16. }
  • 空间复杂度:O(1)
  • 时间复杂度:O(n^2)
  • 稳定性:简单选择排序是一种不稳定的排序方法
  • 使用性:既可以用于顺序表,也可以用于链表
  • 比较次数:n(n-1)/2

3.2 堆排序

堆定义如下:n个关键字序列L[1,n]称为堆,当且仅当该序列满足:

(1)L[i]>L[2i]且L[i]>L[2i+1]或

(2)L[i]<L[2i]且L[i]<L[2i+1]

可以将该一维数组称为一棵完全二叉树,满足条件(1)的堆称为大根堆(大顶堆),大根堆的最大元素存放在根节点,且其任一非根节点的值小于等于其双亲节点值。满足条件(2)的堆称为小根堆(小顶堆),根节点是最小元素。

  • 利用大根堆进行排序得到的序列为升序序列
  • 利用小根堆进行排序得到的序列为降序序列

3.2.1 大根堆

建立大根堆的算法如下:

  1. void BuildMaxHeap(int A[],int len);
  2. int main(){
  3. int A[] = {0,53,17,78,9,45,65,87,32};
  4. printf("初始序列:\n");
  5. for(int i=1;i<=8;i++){
  6. printf("%d\t",A[i]);
  7. }
  8. printf("\n");
  9. BuildMaxHeap(A,8);
  10. printf("大根堆:\n");
  11. for(int i=1;i<=8;i++){
  12. printf("%d\t",A[i]);
  13. }
  14. printf("\n");
  15. return 0;
  16. }
  17. void HeadAdjust(int A[],int k,int len){
  18. A[0] = A[k]; // A[0]暂存子树的根节点
  19. int i;
  20. for(i=2*k;i<=len;i*=2){
  21. if(i<len&&A[i]<A[i+1])
  22. i++;
  23. // 取key较大的子节点的下标
  24. if(A[0]>=A[i]) break;
  25. else{
  26. A[k] = A[i]; // 将A[i]调整到双亲节点上
  27. k = i; // 修改k值,以便继续向下筛选
  28. }
  29. }
  30. A[k] = A[0];
  31. }
  32. void BuildMaxHeap(int A[],int len){
  33. for(int i=len/2;i>0;i--){
  34. HeadAdjust(A,i,len);
  35. }
  36. }

运行结果:



推排序算法如下:

  1. void HeapSort(int A[],int len){
  2. BuildMaxHeap(A,len);
  3. int i,temp;
  4. for(i=len;i>1;i--){
  5. temp = A[1];
  6. A[1] = A[i];
  7. A[i] = temp;
  8. HeadAdjust(A,1,i-1);
  9. }
  10. }

运行结果:

3.2.2 小根堆

建立小根堆的算法如下:

  1. void HeadAdjust2(int A[],int k,int len){
  2. A[0] = A[k];
  3. int i;
  4. for(i=2*k;i<=len;i*=2){
  5. if(i<len&&A[i]>A[i+1])
  6. i++;
  7. // 取key较小的子节点下标
  8. if(A[0]<=A[i])
  9. break;
  10. else{
  11. A[k] = A[i];
  12. k = i;
  13. }
  14. }
  15. A[k] = A[0];
  16. }
  17. void BuildMinHeap(int A[],int len){
  18. for(int i=len/2;i>0;i--){
  19. HeadAdjust2(A,i,len);
  20. }
  21. }

运行结果:



堆排序算法如下:

  1. void HeapSort2(int A[],int len){
  2. BuildMinHeap(A,len);
  3. int i,temp;
  4. for(i=len;i>1;i--){
  5. temp = A[1];
  6. A[1] = A[i];
  7. A[i] = temp;
  8. HeadAdjust2(A,1,i-1);
  9. }
  10. }

运行结果:

  • 空间复杂度:O(1)
  • 时间复杂度:O(nlogn)
  • 稳定性:堆排序算法是一种不稳定排序方法

数据结构(C语言版)严蔚敏->排序的更多相关文章

  1. 《数据结构-C语言版》(严蔚敏,吴伟民版)课本源码+习题集解析使用说明

    <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明 先附上文档归类目录: 课本源码合辑  链接☛☛☛ <数据结构>课本源码合辑 习题集全解析  链接☛☛☛  ...

  2. 数据结构C语言版 表插入排序 静态表

    数据结构C语言版 表插入排序.txt两个人吵架,先说对不起的人,并不是认输了,并不是原谅了.他只是比对方更珍惜这份感情./*  数据结构C语言版 表插入排序  算法10.3 P267-P270  编译 ...

  3. c++学习书籍推荐《清华大学计算机系列教材:数据结构(C++语言版)(第3版)》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <清华大学计算机系列教材:数据结构(C++语言版)(第3版)>习题解析涵盖验证型.拓展型.反思型.实践型和研究型习题,总计290余道大题.525道 ...

  4. 数据结构C语言版 有向图的十字链表存储表示和实现

    /*1wangxiaobo@163.com 数据结构C语言版 有向图的十字链表存储表示和实现 P165 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h> ...

  5. 数据结构C语言版 弗洛伊德算法实现

    /* 数据结构C语言版 弗洛伊德算法  P191 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h>#include <limits.h> # ...

  6. 【数据结构(C语言版)系列二】 栈

    栈和队列是两种重要的线性结构.从数据结构角度看,栈和队列也是线性表,但它们是操作受限的线性表,因此,可称为限定性的数据结构.但从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据类型. 栈的定 ...

  7. 【数据结构(C语言版)系列三】 队列

    队列的定义 队列是一种先进先出的线性表,它只允许在表的一端进行插入,而在另一端删除元素.这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开.在队列中,允许插入的一端叫做队尾(rear),允许 ...

  8. 深入浅出数据结构C语言版(17)——有关排序算法的分析

    这一篇博文我们将讨论一些与排序算法有关的定理,这些定理将解释插入排序博文中提出的疑问(为什么冒泡排序与插入排序总是执行同样数量的交换操作,而选择排序不一定),同时为讲述高级排序算法做铺垫(高级排序为什 ...

  9. 深入浅出数据结构C语言版(17)——希尔排序

    在上一篇博文中我们提到:要令排序算法的时间复杂度低于O(n2),必须令算法执行"远距离的元素交换",使得平均每次交换减少不止1逆序数. 而希尔排序就是"简单地" ...

随机推荐

  1. APP应用前端开发

    1.开发手机APP前端要重视meta标签的编写: 2.注意HTML5标签在前端开发中的使用: 3.前端制作要舍弃CSS float属性(可flex布局),用绝对定位不利于页面布局的扩展: 4.APP前 ...

  2. JZ009乘积小于k的子数组

    title: 乘积小于k的子数组 题目描述 题目链接:乘积小于k的子数组.剑指offer009 解题思路 注意: 一开始的乘积k值就是小的,随着右边窗口移动才会不断增大 怎么样的条件才能更新左窗口:当 ...

  3. kNN-识别手写数字

    最后,我们要进行手写数字分类任务,但是现在我们是用kNN算法,可能会比较慢 首先,完整地看完2.3.1和2.3.2的内容,然后找到trainingDigits和testDigits文件夹,大致浏览下 ...

  4. 项目完成小结 - Django3.x版本 - 开发部署小结 (2)

    前言 好久没更新博客了,最近依然是在做之前博客说的这个项目:项目完成 - 基于Django3.x版本 - 开发部署小结 这项目因为前期工作出了问题,需求没确定好,导致了现在要做很多麻烦的工作,搞得大家 ...

  5. re模块,正则表达式起别名和分组机制,collections模块,time与datetime模块,random模块

    re模块和正则表达式别名和分组机制 命名分组 (1)分组--可以让我们从文本内容中提取指定模式的部分内容,用()来表示要提取的分组,需要注意的是分组 是在整个文本符合指定的正则表达式前提下进行的进一步 ...

  6. sqlserver limit

    select Loaction.Lat,Loaction.Long from Company order by CompanyId OFFSET 4 ROWS FETCH NEXT 2 ROWS ON ...

  7. WTF表单验证

    WTF表单验证可分为3个步骤: ①导入wtf扩展提供的表单验证器.(from wtforms.validators import DataRequired,EqualTo) ②定义表单类 # 定义表单 ...

  8. Fail2ban 简介

    Fail2ban是一个基于日志的IP自动屏蔽工具.可以通过它来防止暴力破解攻击. Fail2ban通过扫描日志文件(例如/var/log/apache/error_log),并禁止恶意IP(太多的密码 ...

  9. git实战-多分支开发-2022新项目

    现在开发中大多数公司中都在使用Git这个代码版本管理工具,几乎可以说是已经成为标配,刚入职不久的这家新公司也不例外. 去公司没多久,开始搭建项目,然后创建开发分支,有多少个后端人员就创建多少个开发分支 ...

  10. iphone苹果手机拼健康码行程码教程

    因为疫情原因,不管是上班,还是上学,各公司和学校都要求提供全家人的健康码和行程码,并弄成一张拼图,这样方便统计!这就苦了广大用苹果手机的朋友们了,因为苹果手机没有自带的拼图软件. 下面我就教大家一个非 ...