常用排序算法及动画演示
(一)插入排序
插入排序可形象理解为打扑克时理牌的一个过程,具体分为直接插入、折半插入排序、希尔排序三种;
①直接插入排序
void InsertSort(int a[],int n) //n为数据的个数
{
int i,j,temp; //i作为已排序好的元素的下标,j存放进行比较的元素的下标
for(i=0;i<n-1;i++)
{
temp=a[i+1]; //temp把要排序的数据元素暂时存放
j=i;
while(j>-1&&temp<a[j])
{
a[j+1]=a[j];
j--; //若能一直找到比temp大的数则j一直往前移,直到找到比temp小的数或者已经到了数组的最前端为止
}
a[j+1]=temp; //当没进入循环,j没j--时,a[j+1]=a[i+1]=temp,不移动
//当退出循环,j--时,a[j+1]的位置为temp值须插入的位置
}
}
直接插入算法动画演示:
视频引自bilibili up主 太_傅
注:视频中的开始位置为i=1,上文中的开始位置为i=0,区别如下方所示
void InsertSort(int a[],int n)
{
int i,j,temp;
for(i=1;i<n;i++) //从i=1开始
{
temp=a[i+1];
j=i-1; //j=i-1;
while(j>-1&&temp<a[j])
{
a[j+1]=a[j];
j--;
}
a[j+1]=temp;
}
}
②折半插入排序算法
折半插入排序利用二分法来进行查找插入位置,然后依次向后集体移动数据元素,从而插入待插数据;
void BInsertSort(int a[],int n,int x)//x为待插入元素
{
int mid;
int low=0; //最左边
int high=n-1; //最右边
int i;
//step1.利用二分法查找插入位置
while(high>=low)
{
mid=(high+low)/2;
if(x>=a[mid])
{
low=mid+1; //如果x比a[mid]大,往右边查找
}
else //x<a[mid]
{
high=mid-1; //往左边查找
}
}
//step2.插入待插元素
for(i=n-1;i>=a[high+1];i--) //从后开始挪动
{
a[i+1]=a[i];
}
a[high+1]=x; //插入
}
③希尔排序算法
希尔排序是在分组概念上的直接插入排序
希尔排序算法的核心思想是将需要进行排序的数据按一定规则分成若干个小组,每次对小组左右两边的数进行对比,将较小的数移至前方(升序),移动完后,边缘下标+1,循环完一轮后,将所选的k值除以2再次对数据进行分组,重复上述操作,直至将数据排序完成。
void ShellSort(int a[],int n)
{
int i,j,temp; //i代表右端,j代表左端,temp存放右端数据
int k=n/2;
while(k>0)
{
for(i=k;i<n;i++)
{
temp=a[i];
j=i-k;
//利用直接插入排序进行排序
while(j>=0&&temp<a[j]) //循环条件是原始右端数据小于当前左端数据
{
a[j+k]=a[j]; //交换
j=j-k; //左移
}
a[j+k]=temp; //跳跃式交换
}
k=k/2; //重新分组,继续循环操作
}
}
希尔排序算法的动画演示:
视频引自bilibili up主 太_傅
(二)选择排序
①直接选择排序算法
直接选择排序的基本思想是每次从待排序的数据元素集合中选取一个最小或最大的数据元素放到数据元素集合的最前端
void SelectSort(int a[],int n)
{
int i,j;
int min,temp; //min标记最小数据的下标,temp存储最小数据元素的值
for(i=0;i<n-1;i++)
{
min=i; //初始化
for(j=i;j<n;j++)
{
if(a[j]<a[i])
{
min=j;
}
//交换最小数据元素
if(min!=i)
{
temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
}
}
直接选择排序动画演示:
视频引自bilibili up主 太_傅
②堆排序算法
堆排序算法是基于完全二叉树的一种排序方式
堆的定义:
堆分为最大堆和最小堆(又称为大根堆/大顶堆,小根堆/小顶堆),最大堆是指所有的非叶子结点小于对应的子树的根的堆,最小堆则与此相反。如下图就是一个最大堆

创建堆代码算法:
void CreatHeap(int a[],int n,int h)
{
//调整非叶结点a[h]使之满足最大堆,n为数组a的元素个数
int i,j,flag;
int temp;
i=h;
j=2*i+1;
temp=a[i];
flag=0;
//沿左右孩子中值较大者重复向下筛选
while(j<n&&flag!=1)
{
//寻找左右孩子结点中的较大者,j为其下标
if(j<n-1&&a[j]<a[j+1])
{
j++; //移动到另一个子堆
}
if(temp>a[j]) //a[i]>a[j]
{
flag=1; //标记结束筛选条件
}
else //否则把a[j]上移
{
a[i]=a[j];
i=j;
j=2*i+1;
}
}
//把最初的a[i]赋予最后的a[j];
}
初始化最大堆:
利用上述CreatHeap(a,n,h)函数,初始化创建最大堆的过程就是从第一个非叶结点a[h](h=(n-2)/2)开始,到根结点a[0]为止,循环调用该函数,初始化创建最大堆。
代码如下:
void InitCreatHeap(int a[],int n)
{
//把a[0]-a[n-1]初始化为最大堆
int i;
for(i=(n-2)/2;i>=0;i--) //i是非叶结点的下标;
{
CreatHeap(a,n,i); //创建最大堆;
}
}
【堆排序基本思想】:
①首先把n个元素的数组a初始化创建为一个最大堆
②把最大堆的根a[0]和当前最大堆的最后一个元素交换
③最大堆元素个数减1
④由于第①步后根结点不再满足最大堆的定义,依次需要重新调整一个新的根结点,使之满足最大堆的定义
实现代码如下:
void HeapSort(int a[],int n)
{
int i;
int temp;
InitCreatHeap(a,n);
for(i=n-1;i>0;i--) //从当前最大堆元素个数开始依次递减,直到最大堆变为一个已排序好的数组;
{
//把堆顶a[0]元素和当前最大堆的最后一个元素交换;
temp=a[0];
a[0]=a[i];
a[i]=temp;
CreatHeap(a,n,0); //调整根结点满足最大堆;
//注意:此时子二叉树根结点下标为0,子二叉树结点个数为i;
}
}
堆排序算法动画演示:
视频引自bilibili up主 太_傅
(三)交换排序
利用交换数据元素的位置进行排序的方法称作尉交换排序。常用的交换排序方法有冒泡排序和快速排序。其中,快速排序是一种分区交换排序方法。
①冒泡排序
void BubbleSort(int a[],int n)
{
int i,j,flag;
int temp;
for(i=0;i<n-1&&flag==1;i++)
{
flag=0;
for(j=0;j<n;j++)
{
if(a[j]<a[j+1]) //交换两个元素
{
flag=1; //flag=1表示进行过交换,若flag!=1,说明已经排序好了,可提前结束排序
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
注:上述算法中的flag,用于标记本次交换排序过程是否有交换动作。若本次交换过程没有交换动作,即本次交换排序过程后flag不等于1,则说明数据元素集合已经全部排好序,可提前结束排序过程。
②快速排序算法
快速排序法是冒泡排序法的改进,它的基本思想是在需要排序的数组中选定一个basic值作为基准点,对左右端分别进行扫描,然后将小于基准点的值放在基准点左侧,大于基准点的值放在基准点的右侧。最后对左右端子集合进行递归排序,最终得到排序好的数组。
void QuickSort(int a[],int left,int right)
{
int i=left,j=right;
int basic=a[left]; //取第一个数据为基准点
while(i<j) //进行一次排序的条件是i<j
{
while(i<j&&basic<=a[j]) j--; //当边的数据元素较大时,右端点左移;
if(i<j) //推出while循环时,已经在右端找到了一个小于basic的数据元素,此时交换左右两端的数据元素
{
a[i]=a[j];
i++;
}
while(i<j&&basic>a[i]) i--;
if(i<j) //推出while循环时,已经在右端找到了一个大于basic的数据元素,此时交换左右两端的数据元素
{
a[j]=a[i];
j--;
}
}
a[i]=basic; //此时i=j,此时将basic的值放在此位置
if(left<i) QuickSort(a,left,i-1); //对左端子集合进行递归排序
if(i<right) QuickSort(a,j+1,right); //对右端子集合进行递归排序
}
快速排序算法动画演示:
视频引自bilibili up主 秒懂算法
(四)归并排序
归并排序主要是指二路归并排序
归并排序的基本思想是将数组中的每一个数据元素作为一个单位,然后各单位分别对各自的头元素进行比较,将较小者放入新数组中,然后将剩余元素依次存入新数组。如此重复,直到得到一个长度为n的新的有序数组。
//一次二路归并排序算法如下:
void MSort(int a[],int b[],int i,int m,int n)
{
//将a[i..m]和a[m+1..n]有序归并到b[i..n]
int k,j,l;
for(k=i,j=m+1;i<=m&&j<=n;k++)
{
if(a[k]<a[j])
{
b[k]=a[k++];
}
else
{
b[k]=a[j++];
}
}
if(i<=m)
{
for(l=0;l<=m-i;l++)
{
b[k+l]=a[i+l]; //将剩余的a[i..m]赋值给b[]
}
}
if(j<=n)
{
for(l=0;l<=n-j;j++)
{
b[k+1]=a[j+l]; //将剩余的a[j..n]赋值给b
}
}
}
//二路归并排序算法如下:
void Merge(int SR[],int TR1[],int s,int t )
{
//将SR[]有序归并到TR1[]
int m;
int TR2[MaxSize+1]; //中间变量数组
if(s==t)
TR2[s]=SR[s];
else
{
m=(m+t)/2; //将SR[s..t]分为SR[s..m]到SR[m..t]
Merge(SR,TR2,s,m); //将SR[s..m]有序归并到TR2[s..m]
Merge(SR,TR2,m,t); //将SR[m..t]有序归并到TR2[m..t]
MSort(TR2,TR1,s,m,t); //将TR2[s..t]有序归并到TR1[s..t]
}
}
//对数组l进行归并排序
void MergeSort(int l[])
{
Merge(l,l,1,sizeof(l)/sizeof(int));
}
归并排序算法动画演示:
视频引自bilibili up主 秒懂算法
(五)基数排序算法
基数排序也叫桶排序,其基本思想是以一组数字的个位、十位、百位…为关键字将数字依次存入以队列行是组成的“桶”中,然后依次从第一个“桶”开始逐个桶将数据收回,最后形成一个排序完成的数组。
#include <LQueue.h> //引入队列文件
void RadixSort(int a[],int n,int m,int d)
{
//对数据元素a[0]-a[n-1]进行关键字为m位d进制整型数值的基数排列
//桶采用链式队列
int i,j,k,power;
LQueue *tub;
//把d个队列定义为动态数组
tub=(LQueue *)malloc(sizeof(LQueue )*d);
for(i=0;i<d;i++)
QueueInitate(&tub[i]); //d个队列初始化
//进行m次放和收
for(i=0;i<m;i++)
{
if(i==0) power=1;
else power=power*d;
//将数据元素按关键字第k位的数值放到相应的队列中
for(j=0;j<n;j++)
{
k=a[j]/power-(a[j]/(power*d))*d; //d为进制
}
//顺序回收各队列中的数据元素至数组a中
k=0;
for(j=0;j<d;j++)
{
while(QueueNotEmpty(tub[j])!=0)
{
QueueDelete(&tub[j],&a[k]); //从各队列中回收
k++;
}
}
}
}
基数排序算法动画演示:
‘
视频引自bilibili up主 师布衣
各类排序算法性能比较

目前为止,并没有一种排序算法是最好的,只有根据实际需求去判断哪一种最合适。