【前言】

所有代码段都以升序为例,数组下标从0开始。排序的稳定性即:任意两个相等的数据,排序前后的相对位置不发生变化

【冒泡排序(Bubble Sort)】

它重复地访问过要排序的元素序列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。访问元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经完成排序。

因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

    for(int i=0;i<n-1;i++)for(int j=0;j<n-1-i;j++)if(a[j]>a[j+1]) swap(a[j],a[j+1]);

排序过程如下:

原序列:6   8   1   4   5   3   7   2第一次:6   1   4   5   3   7   2   8第两次:1   4   5   3   6   2   7   8第三次:1   4   3   5   2   6   7   8第四次:1   3   4   2   5   6   7   8第五次:1   3   2   4   5   6   7   8第六次:1   2   3   4   5   6   7   8第七次:1   2   3   4   5   6   7   8

我们会发现,每一次排序之后至少有一个元素会到最终位置上,就是每次排序的元素中的最大元素。

小优化:当在一次排序过程中交换次数为0时,说明已经有序,可提前结束冒泡排序。

冒泡排序是稳定的排序。最好时间复杂度为O(n),最差时间复杂度为O(n^2),平均时间复杂度为O(n^2)。

【快速排序(Quick Sort)】

快速排序的基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序分为数组划分和递归排序两个步骤。

1.数组划分

选取一个基值,将数组分为大于基值以及小于基值两部分,并返回基值所在位置以利用于递归划分。
       对数组a,设需要划分的一段为a[p]~a[r],我们期望得到一个q,其中p<=q<=r,使得a[p]~a[q-1] <=a[q]<= a[q+1]~a[r],这个时候原先的一段数组被分成了三部分。我们可以设基值x为这段数组的第一个元素a[p]。然后令i=p+1,j=r。当a[j]>=x时,j--;当a[j]<x时,我们需要将这个元素放到小于基值的一边,于是开始比较a[i] : 当a[i]<=x时,i++;当a[i]>x时,交换此时a[i]与a[j]的元素。判断直到i==j时结束,交换基准值与a[i]。这一部分算法复杂度为o(n) 。

2.递归排序

在对整个数组进行了划分后,我们将数组分成了两部分,一部分比基值小,一部分比基值大,并且我们知道了基值所在的位置,因此只需对划分出来的两部分进行递归排序即可。

排序过程如下:

原序列:      6     8     1     4     5     3     7     2
以6为基准:3     2     1     4     5     6     7     8  //交换2与8,最后基准值:交换3与6
以3为基准:1     2     3     4     5     6     7     8  //交换3与1
以1为基准:1     2     3     4     5     6     7     8  //无需交换

设基值为这段数组的第一个元素a[p]时进行快速排序:

#include <stdio.h>
#define maxn 105
int a[maxn],n;
void swap(int x,int y)
{int t=a[x];a[x]=a[y];a[y]=t;
}
void quick_sort(int left,int right)
{if(left>right) return;int temp=a[left];   //存入基准数int l=left,r=right;while(l!=r){//寻找右边小于基准数的数跟左边大于基准数的数做交换while(a[r]>=temp&&l<r) r--;while(a[l]<=temp&&l<r) l++;if(l<r) swap(a[l],a[r]);}//将基准数归位a[left]=a[l];a[l]=temp;quick_sort(left,l-1);   //继续处理左边quick_sort(l+1,right);   //继续处理右边
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);quick_sort(1,n);for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",a[i]);return 0;
}

对于分治算法,当每次划分时,若都能分成两个等长的子序列,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。除了取序列的第一个或最后一个元素作为基准,还可以随机选取基准和三数取中选择基准。

小优化:当待排序序列的长度分割到一定大小后,使用插入排序。
原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。

快速排序是不稳定的排序方法。最差时间复杂度为O(n^2),平均时间复杂度为O(nlogn)。

【插入排序(Insert Sort)】

插入排序的基本操作就是将一个元素插入到已经排好序的有序序列中,从而得到一个新的、个数加一的有序序列。类似打扑克时的按大小整理扑克的步骤,每步将一个待排序的元素按其关键值的大小插入前面的有序序列中的适当的位置上(有序序列中,该适当位置的后面位置的元素,都往后移动一位,腾出该适当位置),直到全部插入完为止。

    for(int i=1;i<n;i++){int j=i-1,t=a[i];while(a[j]>t&&j>=0) //大于位置i的元素往后移动一位{a[j+1]=a[j];j--;}a[j+1]=t;}

排序过程如下:

原序列:6   8   1   4   5   3   7   2第一次:6   8   1   4   5   3   7   2第两次:1   6   8   4   5   3   7   2第三次:1   4   6   8   5   3   7   2第四次:1   4   5   6   8   3   7   2第五次:1   3   4   5   6   8   7   2第六次:1   3   4   5   6   7   8   2第七次:1   2   3   4   5   6   7   8

插入排序是稳定的排序。最好时间复杂度为O(n),最差时间复杂度为O(n^2),平均时间复杂度为O(n^2)。

【希尔排序(Shell Sort)】

希尔排序是插入排序的一种,又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版。

希尔排序是把整个数组按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个分组恰被分成一组,算法便终止。

    for(int k=n/2;k>0;k/=2){for(int i=k;i<n;i++){int j=i;if(j-k>=0&&a[j]<a[j-k])swap(a[j],a[j-k]),j-=k;}}

排序过程如下:

原序列:6   8   1   4   5   3   7   2第一次:5   3   1   2   6   8   7   4   //增量为4第两次:1   2   5   3   6   4   7   8   //增量为2第三次:1   2   3   5   4   6   7   8   //增量为1

希尔排序是非稳定的排序。对希尔排序的时间复杂度分析很困难,在特定情况下可以准确的估算排序码的比较次数和元素移动的次数,但要想弄清楚排序码比较次数和元素移动次数与增量选择之间的依赖关系,并给出完整的数学分析,还没有人能够做到。

【选择排序(Selection Sort)】

选择排序是一种简单直观的排序算法,它的工作原理是每一次从待排序的元素中选出最小(或最大)的一个元素,与已排序序列的后一个位置进行交换,使得已排序的序列长度+1,重复操作直到没有元素待排序。

    for(int i=0;i<n-1;i++){int t=i;for(int j=i+1;j<n;j++)if(a[j]<a[t]) t=j;swap(a[i],a[t]);}

排序过程如下:

原序列:6     8     1     4    5     3     7     2
第1次: 1     8     6     4     5     3     7     2
第2次: 1     2     6     4     5     3     7     8
第3次: 1     2     3     4     5     6     7     8
第4次: 1     2     3     4     5     6     7     8
第5次: 1     2     3     4     5     6     7     8
第6次: 1     2     3     4     5     6     7     8
第7次: 1     2     3     4     5     6     7     8

选择排序是不稳定的排序方法。最差时间复杂度为O(n^2),平均时间复杂度为O(n^2)。

【堆排序(Heap Sort)】

堆排序的算法是选择排序的优化版本。堆排序在寻找最小值(或最大值)的过程中使用了堆这种数据结构,提高了效率。堆是一个近似二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。

堆是什么?如何建堆?堆如何插入删除和调整?传送门:二叉树和堆(理论)

那么我们要如何实现堆排序呢?下面以升序排序为例讲解。

    算法一:将需要排序的序列建堆,调整成最小堆。每次删除并输出堆顶结点的值即最小值,再将最后一个数放置于堆顶结点的位置,最小堆的大小-1,向下调整成最小堆。重复上述步骤直到堆为空。这样得到的输出序列即是升序。
    算法二:将需要排序的序列建堆,调整成最大堆。每次把堆顶结点的值即最大值与最后一个结点交换位置,最大堆的大小-1,向下调整成最大堆。重复上述步骤直到结束。最后层序遍历输出堆排序后的堆,即为升序序列。

下面为算法二的代码,以升序堆排序为例:

#include <stdio.h>
#define maxn 105
int h[maxn];
int n;
//交换函数
void swap(int x,int y)
{int t=h[x];h[x]=h[y];h[y]=t;
}
//向下比较调整成最大堆
void siftdown(int pos,int num)
{int t,flag=0; //flag用来标记是否需要继续向下调整while(!flag){int t=pos; //用t记录父结点和左右儿子中值较大的结点编号if(pos*2<=num&&h[t]<h[pos*2]) t=pos*2;if(pos*2+1<=num&&h[t]<h[pos*2+1]) t=pos*2+1;//如果最大的结点不是父结点if(t!=pos){swap(t,pos);pos=t;}else flag=1;}
}
void create()
{//从最后一个非叶结点到第1个结点依次进行向下调整for(int i=n/2;i>=1;i--)siftdown(i,n);
}
//堆排序(升序)
void heapSort()
{create(); //建堆int num=n; for(int i=n;i>1;i--){swap(1,i); //交换最大值与最后一个数num--;siftdown(1,num); //前num个数调整成最大堆}
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&h[i]);heapSort();for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);return 0;
}

堆排序是不稳定的排序。最差时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)。

【归并排序(Merge Sort)】

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。

首先考虑如何将二个有序数列合并。这个非常简单,只要比较两个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果其中一个数列为空,那直接将另一个数列的数据依次取出即可。

而归并排序的基本思路就是将数组依次分成2组A、B,如果这2组组内的数据都是有序的,那么就可以将这2组数据进行如上操作排序。那么如何让这2组数据有序呢?可以将A、B组各自再分成2组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组已经达到了有序,然后再合并相邻的2个小组就可以了。这样通过先递归分解数列,再合并数列就完成了归并排序。

#include <stdio.h>
#define maxn 105
void solve(int *a,int l,int mid,int r) //治:哪边小先取哪边的元素
{int b[maxn];int i=l,j=mid+1,k=l;while(i<=mid&&j<=r){if(a[i]<a[j]) b[k++]=a[i++];else b[k++]=a[j++];}while(i<=mid) b[k++]=a[i++];while(j<=r) b[k++]=a[j++];for(i=l;i<=r;i++) a[i]=b[i];
}
void Merge_Sort(int *a,int l,int r) //分:递归分解再合并
{if(l<r){int mid=(l+r)/2;Merge_Sort(a,l,mid);Merge_Sort(a,mid+1,r);solve(a,l,mid,r);}
}
main()
{int n,a[maxn]; scanf("%d",&n);for(int i=0;i<n;i++) scanf("%d",&a[i]);Merge_Sort(a,0,n-1);for(int i=0;i<n;i++) printf(i==n-1?"%d\n":"%d ",a[i]);
}

归并排序是稳定的排序。最差时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)。

【基数排序(Radix Sort)】

要讲基数排序,首先我们先来讲讲桶排序(Bucket Sort)。

    假设我们有 N 个学生,他们的成绩是0到100之间的整数(于是最多有 M = 101 个不同的成绩),如何在线性时间内将学生的成绩排序输出?我们可以用一个大小为101的数组a,a[i]存放成绩为i的学生人数,最后遍历输出即可。时间复杂度T(N, M) = O( M+N )。

那么当M>>N时,桶排序显然不再适用,那么基数排序就出现了。

基数排序属于"分配式排序"(distribution sort),又称"桶子法",顾名思义,它是通过元素的部分关键码,将要排序的元素分配至某些"桶"中,藉以达到排序的作用。

基数排序分为两类:第一类是最低位优先法,简称LSD法,即先从最低位开始排序,再对次低位进行排序,直到对最高位排序后得到一个有序序列。第二类是最高位优先法,简称MSD法,先从最高位开始排序,再对次高位进行排序,直到对最低位排序后得到一个有序序列。

当对给定序列进行次位优先(LSD)排序时,过程如下:

原序列:  64,8,216,512,27,729,0,1,343,125桶子:      0     1      2      3      4       5       6      7      8       9按个位:  0,1,512,343,64,125,216,27,8,729按十位:  0,512,125        343              641,216,  27,8              729按百位:  0    125    216   343          512          729182764最终序列:0 1 8 27 64 125 216 343 512 729

基数排序是稳定的排序方法。P为最大位数,N为数的数目,B为桶子的大小,最差时间复杂度为O(P(N+B)),平均时间复杂度为O(P(N+B))。

参考博客:八大排序:冒泡排序、插入排序、希尔排序、选择排序、堆排序、归并排序、快速排序、基数排序_芋圆西米露的博客-CSDN博客_八大排序算法

八大排序:冒泡排序、插入排序、希尔排序、选择排序、堆排序、归并排序、快速排序、基数排序相关推荐

  1. java版排序算法简介及冒泡排序以及优化,选择排序,直接插入排序,希尔排序,堆排序,快速排序及其优化前言 2 分类 2 稳定性 3 时间复杂度 4 Java实现版本 5 1、冒泡排序 6 2、选择排序

    好吧 ~~csdn太难用了....尼玛...写了半天的也无法弄进去...lz正在找更好地博客,or放在github上打算.. 下边是lz自己的有道云分享,大概内容是 http://note.youda ...

  2. 对以下6种常用的内部排序算法进行比较:起泡排序、直接插入排序、简单选择排序、快速排序、希尔排序、堆排序。

    题目要求: (1)对以下6种常用的内部排序算法进行比较:起泡排序.直接插入排序.简单选择排序.快速排序.希尔排序.堆排序. (2)待排序表的表长不小于100:其中的数据要用伪随机数产生程序产生:至少要 ...

  3. 【简单排序算法】:简单选择排序、直接插入排序和冒泡排序

    [简单排序算法]:简单选择排序.直接插入排序和冒泡排序 简单选择排序: 原理:设所排序序列的记录个数为n.i取1,2,-,n-1,每次从所有n-i+1个记录(Ri,Ri+1,-,Rn)中找出最小的记录 ...

  4. 各种排序算法以及扩展(选择排序,冒泡排序,插入排序,归并排序,最小和问题,堆排序,荷兰国旗问题,快速排序)

    文章目录 基础算法一二课 选择排序 冒泡排序 插入排序 判断数据是否在数组中 找满足>=value的最左位置 ^ 异或符号的多用 一.进行交换 二.数组中有一种数出现了奇数次,其他数都出现了偶数 ...

  5. Java冒泡排序原理速记,选择排序原理速记

    Java冒泡排序原理速记,选择排序原理速记 冒泡排序原理分析: 冒泡排序从左往右两两比较:保护右边的排序好的元素(比较直接交换元素). 选择排序原理: 选择排序从左往右依次比较:保护左边排序好的元素( ...

  6. [ 数据结构 -- 手撕排序算法第四篇 ] 选择排序

    手撕排序算法系列之第四篇:选择排序. 从本篇文章开始,我会介绍并分析常见的几种排序,大致包括直接插入排序,冒泡排序,希尔排序,选择排序,堆排序,快速排序,归并排序等. 大家可以点击此链接阅读其他排序算 ...

  7. 选择排序java代码_JAVA简单选择排序算法原理及实现

    简单选择排序:(选出最小值,放在第一位,然后第一位向后推移,如此循环)第一位与后面每一个逐个比较,每次都使最小的置顶,第一位向后推进(即刚选定的第一位是最小值,不再参与比较,比较次数减1) 复杂度: ...

  8. 【排序算法】图解简单选择排序(图解堪比Debug显示每次循环结果)

    [排序算法]图解简单选择排序(图解堪比Debug分析每次循环结果) 写在前面: 本文主要介绍简单选择排序算法,通过图片一步步解释每一趟每一次的后移.代码通过C#实现,并输出每一次交换的情况和比较次数, ...

  9. C语言排序算法(一)——选择排序实现

    C语言排序算法(一)--选择排序实现 编写程序,实现从键盘输入10个数,并用选择法从小到大排序. 简单选择排序的基本思想:第1趟,在待排序记录r[1]r[n]中选出最小的记录,将它与r[1]交换:第2 ...

  10. 加标志量的选择排序算法c语言,置换选择排序算法详解(C语言实现)

    上一节介绍了增加 k-路归并排序中的 k 值来提高外部排序效率的方法,而除此之外,还有另外一条路可走,即减少初始归并段的个数,也就是本章第一节中提到的减小 m 的值. m 的求值方法为:m=⌈n/l⌉ ...

最新文章

  1. 传感器的未来: 10年后我们将会生活在一个极端透明的世界
  2. python学精通要多久-python多久能精通
  3. boot数据加解密 spring_springboot项目使用druid对数据库密码的加解密
  4. Matlab 使用GPU加速 转载
  5. 跳槽9招让你“空降”任何企业都能成功
  6. sin查找表 matlab,利用Xilinx中的ROM构造查找表来计算sin和cos的方法探讨
  7. tensorrt基础知识+torch版lenet转c++ trt
  8. linux unix域socket_python3从零学习-5.8.1、socket—底层网络接口
  9. vim 高亮显示php代码
  10. django-模型类的修改-添加与删除字段
  11. [转载] C#面向对象设计模式纵横谈——1. 面向对象设计模式与原则
  12. win10任务栏怎么还原到下面_如何让你的Win10桌面变得更好看?
  13. watir 基础知识
  14. 四旋翼无人机PID调节(无数次实验总结经验和理论支持)
  15. ubuntu16.04 禁用Guest用户
  16. 千锋python培训班怎么样
  17. java里什么是注释,全面解析Java中的注解与注释
  18. 广州市车联网先导区LTE-V2X 车载直连通讯设备技术规范
  19. java案例之制作系统
  20. 软件开发模型/原型法/瀑布模型/螺旋模型

热门文章

  1. 在Lumia 950 XL上运行Windows 10 ARM64,是种什么体验?
  2. 云盒子发布全新一键安装包,50+功能更新
  3. 虚拟服务器死机了怎么重启,虚拟主机宕机怎么办
  4. 【SDL游戏编程入门第十六卷】输出中文SDL_ttf以及设置中文标题
  5. 计算机在小学英语中的应用,信息技术在英语教学中的应用
  6. 如何有效阻止u盘中毒(没联网未安装杀毒软件情况下)
  7. 老百姓计算机系,《老百姓装机宝典》,最新万元以内电脑组装配置,免费收藏!...
  8. VScode 5 显示文件大小插件(filesize)
  9. oracle 发票剩余金额,发票余额为零, 无法结帐
  10. 国际网页短信软件平台搭建定制接口说明|移讯云短信系统