排序算法,顾名思义,就是把一个无序的数据序列通过一定的逻辑进行有序化,或从小到大,或从大到小,所以排序算法在我们日常生活也十分常见,因此其应用也很广泛,还是有必要浅浅了解一下的我觉得。
下面我将大概介绍一下十大经典排序算法的基本思路以及它的稳定性和时间复杂度。这里先来说一下什么是稳定性:在待排序的数据中,如果有值相同的数据,在排序的全程中都不会改变它们的先后顺序,则认为该排序算法是稳定的。当然了,一个好的排序算法时间复杂度也是衡量质量的一个重要标准。
#define LEN 15
#define swap(a,b) {typeof(a) t=(a);(a)=(b);(b)=t;}
1、冒泡排序
数据左右进行比较,把最大的数一直交换到最后,特点是该算法对数据的有序性敏感,在排序过程中发现有序可以立即停止排序,如果待排序的数据基本有序,则冒泡的效率非常高
时间复杂度:最优O(N) 平均:O(N^2) 交换类
稳定的
//冒泡
void maopao(int *arr,int len)
{
for(int i=0;i<len-1;i++)
{
for(int j=i;j<len;j++)
{
if(arr[j]<arr[i])
swap(arr[i],arr[j]);
}
}
}
2、选择排序
选择:假定最开始的位置是最小值并记录下标min,然后与后面的数据比较,如果有比min为下标的数据还要小,则更新min,最后判断如果min的值发生了改变,则交换min位置的数据与最开始位置的数据
虽然选择排序的时间复杂度较高,但是数据交换次数少,因此实际运行速度并不慢
是冒泡排序的变种,但是没有对数据有序性敏感,数据混乱情况下比冒泡快
时间复杂度:O(N^2)
不稳定的 (10 10 1)
注意:算法的时间复杂度并不能代表算法的实际时间,有时候时间复杂度高的反而速度更快 选择类
//选择
void xuanze(int *arr,int len)
{
for(int i=0;i<len;i++)
{
int min=i;
for(int j=i+1;j<len;j++)
{
if(arr[j]<arr[min])
min=j;
}
if(i!=min)
swap(arr[i],arr[min]);
}
}
3、插入排序
把数据看作两个部分,一部分是有序的,把剩余的数据逐个插入进去
时间复杂度:O(N^2) 插入类
稳定的
//插入
void charu(int *arr,int len)
{
for(int i=1;i<len;i++)
{
int temp=arr[i];
int j=i-1;
for(;j>=0 && temp<arr[j];j--)
arr[j+1]=arr[j];
if(j!=i)
arr[j+1]=temp;
}
}
4、希尔排序
是插入排序的增强版,由于插入排序数据移动的速度比较慢,所以在此基础上增加了增量的概念,从而提高排序的速度
时间复杂度:O(N^(1.3~2)) 插入类
不稳定
//希尔
void xier(int *arr,int len)
{
for(int gap=len/2;gap>0;gap/=2)
{
for(int i=gap;i<len;i++)
{
int temp=arr[i];
int j=i;
for(;j>=gap && arr[j-gap]>temp;j-=gap)
arr[j]=arr[j-gap];
if(j!=i)
arr[j]=temp;
}
}
}
5、快速排序
找到一个标杆p,备份标杆p的值val,一面从左找比val大的数据,找到后赋值给p,更新标杆p的位置到左标杆,然后从右边找比val小的数,找到后也赋值给p,同样更新p到右标杆,反复执行直到左右标杆相等停止,最后把val赋值回p的位置,最终会形成p左边的数都比它小,右边的数都比它大;然后再按照同样的方式对左右两边进行快排,最后全部有序
快速排序的综合性能最高,因此叫做快速排序,笔试面试考最多
时间复杂度:O(NlogN) 交换类
不稳定
//快速
int _kuaisu(int *arr,int low,int high)
{
int flag=arr[low];
while(low<high)
{
while(low<high && arr[high]>=flag) high--;
arr[low]=arr[high];
while(low<high && arr[low]<=flag) low++;
arr[high]=arr[low];
}
arr[high]=flag;
return high;
}
void kuaisu(int *arr,int low,int high)
{
if(low<high)
{
int current=_kuaisu(arr,low,high);
kuaisu(arr,0,current-1);
kuaisu(arr,current+1,high);
}
return;
}
void q_sort(int *arr,int len)
{
kuaisu(arr,0,len-1);
}
6、归并排序
先把一组待排序的数据拆分成单独的个体,存放到临时空间中,然后两两比较合并,全部合并完成后再从临时空间中拷贝给原内存
由于使用额外的内存空间避免了数据交换的耗时,是一种典型的以空间换时间的算法
时间复杂度:O(NlogN)
稳定
//归并
//合并
void __guibing_sort(int *arr,int *temp,int l,int p,int r)
{
// l左部分最左,p左部分最右;p+1右部分最左,r右部分最右
//默认认为左右部分各自有序
if(arr[p]<arr[p+1]) return;
int k=l,i=l,j=p+1;
while(i<=p && j<=r)
{
//左右部分从左开始比较,谁小放入temp
if(arr[i]<=arr[j])
temp[k++]=arr[i++];
else
temp[k++]=arr[j++];
}
//比完后,把还没有比较的放入temp末尾
while(i<=p) temp[k++]=arr[i++];
while(j<=r) temp[k++]=arr[j++];
//从临时空间拷会原内存
//while(l<=r) arr[l]=temp[l++];
memcpy(arr+l,temp+l,sizeof(int)*(r-l+1));
}
//拆分
void _guibing_sort(int *arr,int *temp,int l,int r)
{
if(l>=r) return;
int p=(l+r)/2;
_guibing_sort(arr,temp,l,p);
_guibing_sort(arr,temp,p+1,r);
__guibing_sort(arr,temp,l,p,r);
}
void guibing_sort(int *arr,int len)
{
int *temp=malloc(sizeof(int)*len);
_guibing_sort(arr,temp,0,len-1);
free(temp);
}
7、堆排序
把数据当做当做完全二叉树看待,然后把树调整成大顶堆,然后把堆顶数据交换到末尾,然后数量--,然后重新调整回大顶堆,重复操作,直到数量为1时结束,既可以循环实现也可以递归实现(参考heap.c)
时间复杂度:O(NlogN) 选择类
不稳定
//堆排序
//以下是下标从0开始的写法
//循环
void sort_heap(int *arr,int len)
{
for(int i=1;i<=len;i++)
{
int j=i;
while(j>1)
{
if(arr[j/2-1]<arr[j-1])
{
swap(arr[j/2-1],arr[j-1]);
j=j/2;
}
else break;
}
}
while(len>1)
{
swap(arr[0],arr[len-1]);
len--;
int i=1;
while(i-1<len)
{
if(i*2<len)
{
if(arr[i*2]>=arr[i*2-1]&&arr[i*2]>arr[i-1])
{
swap(arr[i*2],arr[i-1]);
i=i*2+1;
}
else if(arr[i*2-1]>arr[i*2]&&arr[i*2-1]>arr[i-1])
{
swap(arr[i*2-1],arr[i-1]);
i=i*2;
}
else break;
}
else if(i*2-1<len)
{
if(arr[i*2-1]>arr[i])
{
swap(arr[i*2-1],arr[i-1]);
i=i*2;
}
else break;
}
else break;
}
}
}
//递归
void _sort_heap_recursion(int *arr,int top,int end)
{
if(top>=end) return;
int max=top;
int l=max*2;
int r=max*2+1;
if(l<=end && arr[l]>arr[max]) max=l;
if(r<=end && arr[r]>arr[max]) max=r;
if(top!=max)
{
swap(arr[max],arr[top]);
_sort_heap_recursion(arr,max,end);
}
}
void sort_heap_recursion(int *arr,int len)
{
for(int i=1;i<=len;i++)
{
int j=i;
while(j>0)
{
if(arr[j/2]<arr[j])
{
swap(arr[j/2],arr[j]);
j=j/2;
}
else break;
}
}
for(int i=len-1;i>0;i--)
{
swap(arr[0],arr[i]);
_sort_heap_recursion(arr,0,i-1);
}
}
8、计数排序
找出数据中的最大值和最小值,并创建哈希表,把 数据-最小值 作为数组的下标访问哈希表并标记数量,标记完后,遍历哈希表,当表中的值大于0,把 下标+最小值 还原数据依次放回数组中,是一种典型的以空间换时间的算法
该排序算法理论上速度非常快,它不是基于比较的算法,在一定范围内整数排序时快于任意的一种比较排序算法,但是有很大的局限性:适合排序整形数据,而且数据的范围差别不宜过大,否则会非常浪费内存反而慢于比较的排序,如果数据越平均、重复数越多,性价比越高
时间复杂度:Ο(N+k)(其中k是整数的范围)
稳定的
//计数
void count_sort(int *arr,int len)
{
int min=arr[0],max=arr[0];
for(int i=0;i<len;i++)
{
if(arr[i]<min) min=arr[i];
if(arr[i]>max) max=arr[i];
}
//创建哈希表
int *temp=calloc(sizeof(int),max-min+1);
//标记哈希表
for(int i=0;i<len;i++)
temp[arr[i]-min]++;
//还原到arr中
for(int i=0,j=0;i<=max-min;i++)
{
while(temp[i]--)
{
arr[j++]=i+min;
}
}
free(temp);
}
9、桶排序
根据数据的值存储到不同的桶中,然后再调用其它的排序算法,对桶中的数据进行排序,然后再从桶中依次拷贝回数组中,从而降低排序的规模以此提高排序的速度,是一种典型的以空间换时间的算法
缺点:如何分桶、桶范围多大,这些都需要对数据有一定的了解
时间复杂度:Ο(N+k)
桶排序的稳定性取决于桶内排序使用的算法
//桶
void _tong_sort(int *arr,int len,int cnt,int range)//cnt桶个数,range桶数值范围
{
//申请桶内存
//bucket指向桶的开头 bucket_end指向末尾 数据加入bucket_end++
int *bucket[cnt],*bucket_end[cnt];
for(int i=0;i<cnt;i++)
{
//数据可能集中在一个桶内,所以只能每个桶开len长度的空间
bucket[i]=malloc(sizeof(int)*len);
//开始时,起始和末尾指针都指向桶开头
bucket_end[i]=bucket[i];
}
//把所有数据按照桶的范围依次放入
for(int i=0;i<len;i++)
{
for(int j=0;j<cnt;j++)
{
if(arr[i]>=range*j && arr[i]<range*(j+1))
{
*(bucket_end[j])=arr[i];
bucket_end[j]++;
}
}
}
//让每个桶中的数据排序
for(int i=0;i<cnt;i++)
{
//计算每个桶中元素数量
int size=bucket_end[i]-bucket[i];
//有元素,才需要使用别的排序算法进行排序
if(size>1) count_sort(bucket[i],size);
//把桶按照先后顺序放入原数组
memcpy(arr,bucket[i],sizeof(int)*size);
arr+=size;
free(bucket[i]);
}
}
void tong_sort(int *arr,int len)
{
_tong_sort(arr,len,4,25);
}
10、基数排序
是桶排序的具体实现,首先创建10个队列(链式队列),然后逆序计算出数据的个、十、百...位数,然后入到对应的队列中,结束后依次从队列中出队回数组中,数据下一位继续入队,依次循环,最大值的位数就是循环次数
缺点:只适合排序正整数数据,又要准备队列
时间复杂度:Ο(N+k)
稳定的
//基数
void jishu_sort(int *arr,int len)
{
ListQueue *queue[10]={};
for(int i=0;i<10;i++)
{
queue[i]=create_queue();
}
//循环次数由最大值的位数决定
int max=arr[0];
for(int i=0;i<len;i++)
{
if(arr[i]>max) max=arr[i];
}
//i是1表示个位,2表示十位,3表示百位
for(int i=1,k=1;max/k>0;k*=10,i++)
{
int mod=pow(10,i);
int div=mod/10;
for(int j=0;j<len;j++)
{
//获取每位数据每位上的值,如果mod过大,数据的index都是0
int index=arr[j]%mod/div;
in_queue(queue[index],arr[j]);
}
int k=0;
for(int j=0;j<10;j++)
{
//把队列中的数据按顺序放回原数组arr
while(queue[j]->cnt!=0)
{
head_queue(queue[j],arr[k++]);
out_queue(queue[j]);
}
}
}
}
以上是十大经典排序算法的基本实现
over