C语言实现八大排序算法(一)
本文主要介绍数据结构中常见的八大排序算法,冒泡排序、快速排序、直接插入排序、希尔排序、简单选择排序、堆排序、归并排序和基数排序。
注:下面的排序代码有些我没编译跑过,可能有问题,但思想肯定是正确的,我用python重新编写并验证了一遍,代码可参考这 python实现十大排序算法(详解)
排序相描述
注:算法是否稳定并不能衡量一个算法的优劣,排序算法主要包含2种操作:比较和移动,但并不是所有的排序都基于比较操作,比如基数排序。
下图为排序算法体系结构图:
各大排序算法的时间复杂度、空间复杂度及稳定性见下表:
直接插入排序(Insertion Sort)
基本思想
将待排序的无序数列看成是一个仅含有一个元素的有序数列和一个无序数列,将无序数列中的元素逐次插入到有序数列中,从而获得最终的有序数列。
算法流程
详细描述如下:
从第一个元素开始,该元素可以认为已经被排序
取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置中
重复步骤2
复杂度分析:
第1个元素,需要进行 0 次比较;
第2个元素,需要进行 1 次比较;
第3个元素,需要进行 2 次比较;
第n个元素,需要进行n-1次比较;
动态实例
算法实现
//直接插入法一
void InsertSort1(int a[], int n)
{
int i, j;
for(i=1; i<n; i++)
if(a[i] < a[i-1])
{
int temp = a[i]; //保存要比较的值
for(j=i-1; j>=0 && a[j]>temp; j--) //从后向前查找待插入位置
a[j+1] = a[j]; //挪位
a[j+1] = temp; //复制到插入位置
}
}
//直接插入法二:用数据交换代替法一的数据后移(比较对象只考虑两个元素)
void InsertSort2(int a[], int n)
{
for(int i=1; i<n; i++)
for(int j=i-1; j>=0 && a[j]>a[j+1]; j--)
Swap(a[j], a[j+1]);
}
void Swap(int a,int b){
int temp;
temp=a;
a=b;
b=temp;
}
拓展(折半插入排序)
仅适用于顺序存储的线性表
直接插入排序是边比较,边移动元素;
折半插入排序是先折半查找到插入位置,再统一移动元素
void ZhebanInsertSort(int a[],int n){
int i,j,low,high,mid;
for(i=1;i<n;i++){
temp=a[i];
low=1,high=i; //设置折半查找范围
while(low<=high){
mid=(low+high)/2;
if(a[mid]>temp)
high=mid-1; //查找左半部分
else
low=mid+1; //查找右半部分
}
for(j=i;j>=high+1;--j)
a[j+1]=a[j]; //统一后移元素,空出插入位置
a[high+1]=temp; //插入
}
}
动态实例
算法实现
//希尔排序法一
void shellSort1(int a[],int n){
int i,j,gap,temp;
for(gap = n/2;gap>0;gap/=2){
for(i=gap;i<n;i+=gap){
temp = a[i];
for(j = i-gap;j>=0&&a[j]>temp;j-=gap){
a[j+gap] = a[j];
}
a[j+gap] = temp;
}
}
}
//希尔排序法二,和上面的直接插入排序类似,用数据交换代替法一的数据后移(比较对象只考虑两个元素)
void ShellSort2(int a[], int n)
{
int i, j, gap;
//分组
for(gap=n/2; gap>0; gap/=2)
//直接插入排序
for(i=gap; i<n; i++)
for(j=i-gap; j>=0 && a[j]>a[j+gap]; j-=gap)
Swap(a[j], a[j+gap]);
}
冒泡排序(Bubble Sort)
基本思想
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。每一趟排序后的效果都是讲没有沉下去的元素给沉下去。
算法流程 (递增为例)
比较相邻的元素。如果第一个比第二个大,就交换他们两个
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数
针对所有的元素重复以上的步骤,除了最后一个
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
动态实例
算法实现
//冒泡排序
void BubbleSort(int a[], int n)
{
int i, j;
for(i=0; i<n; i++){
bool flag=false; //表示本趟冒泡是否发生交换的标志
for(j=1; j<n-i; j++){ //j的起始位置为1,终止位置为n-i
if(a[j]<a[j-1]){
Swap(a[j-1], a[j]);
flag=true;
}
}
if(flag==false) //未交换,说明已经有序,停止排序
return;
}
}
拓展 (冒泡排序的改进 ---- 鸡尾酒排序)
鸡尾酒排序与冒泡排序的不同处在于排序时是以首尾双向在序列中进行排序。
先对数组从左到右进行升序的冒泡排序;
再对数组进行从右到左的降序的冒泡排序;
以此类推,持续的、依次的改变冒泡的方向,并不断缩小没有排序的数组范围;
鸡尾酒排序的优点是能够在特定条件下(如集合中大部分元素有序),减少排序的回合数。
实现鸡尾酒排序需要分别定义一个从最左边开始的index_left和从最右边开始的index_reight,当两个index相等的时候循环结束。
//鸡尾酒排序
void CocktailSort(int a[], int n){
int left = 0, right=n-1;
while(left<right){
for(int i=left;i<right-1;i++){ //从前往后排
if(a[i]>a[i+1]){
Swap(a[i],a[i+1]);
}
right-=1;
for(int j=right;j>left;j--){ //从后往前排
if(a[j]<a[j-1]){
Swap(a[j],a[j-1]);
}
}
left+=1;
}
}
}
快速排序(Quick Sort)
快速排序是目前所有内部排序算法中平均性能最优的排序算法。
基本思想
快速排序算法的基本思想为分治思想。
先从数列中取出一个数作轴值(基准数)pivot;
根据基准数将数列进行分区,小于基准数的放左边,大于基准数的放右边;
重复分区操作,知道各区间只有一个数为止。
算法流程(递归+挖坑填数)
i = L , j = R i=L,j=R
下面是一个我手写的详细分析实例:
动态实例
算法实现
//快速排序
void QuickSort(int a[],int left,int right){
if(left<right){
int i=left,j=right;
int base=a[left]; //基准
while(i<j){
while(i<j&&a[j]>=base) //从右往左找小于base的元素
j--;
if(i<j)
a[i++]=a[j];
while(i<j&&a[i]<base) //从左往右找大于base的值
i++;
if(i<j)
a[j--]=a[i];
}
a[i]=base; //将基准base填入最后的坑中
QuickSort(a,left,i-1); //递归调用,分治
QuickSort(a,i+1,right);
}
}
注意:如果基准不是选第一个数,可以用Swap()函数将基准调整至第一个位置,再执行上述程序。
总结:本文主要介绍了2大类排序算法:插入排序(直接插入、希尔排序)和交换排序(冒泡排序、快速排序)。