以前上学,排序就记下了一种——冒泡——简单好记
在找工作的过程中也有笔试题会问排序,都是写冒泡
现在找了些资料,把7种排序写一下,也不知道以后还能记得几种
在我理解,那些时间复杂度是O(nlogn)的,都是以复杂性来换取效率的。还有就是,我注释里的稳定性啊,最好、最坏情况啊,都是凭感觉写的,应该会跟其它资料有出入。大家自己甄别
#include"stdio.h"
void printArray(int* a,int n){
for(int i=0;i<n;i++){
printf("%d,",a[i]);
}
printf("\n");
}
/*
【直接插入排序】
把数组分成有序序列、无序序列
每次取无序序列第一个,插入到有序序列中
插入的时候循环有序序列,确定位置后,循环把元素往后移。把之前的元素插入所确定位置
*它在待排序数据基本有序并且数量较少的时候威力还是很大的
【key】这个是稳定的,时间O(n^2),空间O(1)。最好O(n),最坏O(n^2)
*/
void InsertSort(int* a,int size){
int i,j,k,tmp=0;
for(i=1;i<size;i++){//从无序序列中取第一个
tmp=a[i];
for(j=0;j<i;j++){//循环有序序列,确定新元素要插入的位置
if(tmp<a[j]){//把小的数放前面
for(k=i-1;k>=j;k--){//把j...i-1的元素都向后移一位,为tmp挪位置
a[k+1]=a[k];
}
a[j]=tmp;
break;
}
}
}
}
//在这个写法中能比较明显地看出:
//在数组几乎有序时,排序的时间复杂度是O(n)
void InsertSort2(int* array,int length){
int j,k,temp=0;
for(j=1;j<length;j++){//单独一次的插入排序
if(array[j]<array[j-1]){
temp=array[j];//哨兵
k=j-1;
//这种写法,检查有序序列的时候是从最后一个开始检查
//之前的写法是写个循环先确定位置,再写个循环挪位置
//这种写法,是直接挪位置,挪不动了就是所确定位置
while(k>=0 && array[k]>temp){
array[k+1]=array[k];
k--;
}
array[k+1] = temp;
}
}
}
/*
【二分插入排序】
就是插入排序的改进
数组左边是有序序列,所以确定插入位置的时候可以用二分来确定
(这种肯定是要比插入排序快的,但改善的不明显)
【key】这个是稳定的,时间O(n^2),空间O(1)。最好,最坏
它的最好最坏是一样的,因为都要二分找下去
*/
void BinaryInsertSort(int* a,int size){
int i,k,tmp=0;
int low,high,mid=0;
for(i=1;i<size;i++){
tmp=a[i];
low=0;
high=i-1;
while(low<=high){//确定插入位置(low)
mid=(low+high)/2;
if(tmp<a[mid])
high=mid-1;
else
low=mid+1;
}//这个while循环最后肯定是low=high,该位置就是新元素插入位置
for(k=i-1;k>=low;k--){
a[k+1]=a[k];
}
a[low]=tmp;
}
}
/*
【冒泡排序】
这个很简单
之前的2个插入排序比这个好在:每次交换就要有3个操作,而插入排序没有用交换
【key】稳定,时间O(n^2),空间O(1)。最好O(n),最坏O(n^2)
最好是:加了变量isSwap,第一次内层循环,如果一次都没有交换过,就排序结束
*/
void BubbleSort(int* a,int size){
int i,j,tmp=0;
for(i=0;i<size;i++){
for(j=i+1;j<size;j++){
if(a[i]>a[j]){
tmp=a[j];
a[j]=a[i];
a[i]=tmp;
}
}
}
}
/*
【简单选择排序】
循环无序序列,选出最小的,放在无序序列的最头一个位置
执行n-1次,就完成了排序
c里面球数组长度其实不好求,所以直接给出
【key】??,时间O(n^2),空间O(1)。最好(n^2),最坏(n^2)
稳定与否要看是怎么选择的,这个是稳定的
*/
void SelectSort(int* a,int n){
int last=n-1;
for(int cur=0;cur<last;cur++){//cur是该次循环要替换的位置
int index=cur;
for(int i=cur;i<n;i++){//循环一次,取出无序序列里的最小值
if(a[index]>a[i])
index=i;
}
if(index!=cur){//把最小值交换到无线序列开头
int tmp=a[cur];
a[cur]=a[index];
a[index]=tmp;
}
}
}
/*
【希尔排序】
发明这个排序的人叫希尔(所以这个算法跟shell这个词是没关系的)
它其实是插入排序的一种高效率的改进
改进的思路是:插入排序在序列基本有序的时候效率几乎是线性的。所以就尽量弄些有序的序列出来
代码:1.给数组分组,对各个分组进行插入排序;2.把分组变少,重复1直到只有一个分组
(一开始分组多,一个分组小,排序快;后来分组变大,但基本有序,也快)
还有一种写法是只用3个for循环的(现在这种等于是用了4个),它有一个for是把分组和插入混在一起。不提倡,现在这种思路更清晰
【key】不稳定,时间O(nlogn),空间O(1)。最好O(nlogn),最坏O(nlogn)
不稳定理由同下
时间:外层的分组一直是/2,所以外层次数是logn
*/
void ShellSort(int* a,int size){
int i,j,k,key,gap=0;
for(gap=size/2;gap>0;gap=gap/2){
for(i=0;i<gap;i++){//头2个for是分组
for(j=i+gap;j<size;j=j+gap){//从这里开始是插入排序
//这个写法看着有点烦,其实它只是把对应元素跳着捡出来而已
key=a[j];
k=j-gap;
if(key<a[k]){
while(k>=0&&key<a[k]){
a[k+gap]=a[k];
k=k-gap;
}
a[k+gap]=key;
}
}
}
}
}
/*
【快速排序】
一种说法是快排是冒泡的改进,因为快排也用到交换。并不认同这种说法
快排也是一种增量减小的排序。
思路:取序列头元素做标准,把序列从左到右分成<=>,3个组。这个分组用的就是交换;然后再对<>2个组做快排(递归),直到分组数是1
(这种排序感觉怪怪的,莫名其妙就出现了。公认是比较好的排序方法。有一种说法:排序可以尽量用希尔排序,无论数量,如果有性能问题,再改成快排)
【key】不稳定,时间(nlogn),空间O(logn)。最好O(nlogn),最坏O(n^2)
不稳定是因为:它不是依次循环,而是从两头向中间地循环,所以...
空间这个:猜测外层循环左右2个数组进行快排,有点像二分,所以是logn次;
*/
void QuickSort(int* a,int low,int high){
if(low>=high) return;//这一句是python的思想,尽量少缩进
int i=low,j=high,key=a[low],tmp=0;
while(i<j){
//2个while不能颠倒,因为整个序列里只有a[low]是闲值(有备份),其它都是实际值.如果先给a[J]赋值,就会有值被抹去
//感觉这个写法还是很巧妙,有人叫"挖坑填数"
while(i<j&&a[j]>key) j--;
if(i<j) a[i++]=a[j];//赋值后a[j]就成了闲值(有备份)
while(i<j&&a[i]<key) i++;
if(i<j) a[j--]=a[i];
}
//执行完while后i=j
a[i]=key;
QuickSort(a,low,i-1);
QuickSort(a,i+1,high);
}
/*
【堆排序】
最大堆的特点:对于随意某个结点,该结点的值大于左孩子、右孩子的值,可是左右孩子的值没有要求。——所以它只要对前一半的元素建堆就好
感觉堆排序的2个函数都很经典
【key】不稳定,时间O(nlogn),空间O(1)。最好O(nlogn),最坏O(nlogn)
不稳定是因为它建堆or堆调整的时候是跳着进行的,有可能把相同的元素放到前面了
时间:HeapAdjust是logn,外层是n。所以...
*/
//堆调整函数-在i以及i的2个孩子中,选出最大的放在i的位置
void HeapAdjust(int* a,int i,int size){
if(i<0||i>=size) return;
int tmp,child=0;
for(;2*i+1<size;i=child){//要保证有孩子节点,才可以比较&交换
child=2*i+1;
if((child+1)<size && a[child]<a[child+1])
child++;//挑选出孩子里面比较大的那个节点
if(a[child]>a[i]){//有过交换的要继续在循环里检查它的子孩子
tmp=a[i];
a[i]=a[child];
a[child]=tmp;
}
else{
break;
}
}
}
//先对数组前半部分建堆,然后循环-把数组第一个元素与最后一个元素(递减)交换后,每次交换要调整堆以保证第一个元素是最大
void HeapSort(int* a,int size){
int i,tmp=0;
//建堆
for(i=size/2-1;i>=0;i--)
HeapAdjust(a,i,size);
//单次交换,并调整堆
for(i=size-1;i>0;i--){
tmp=a[0];
a[0]=a[i];
a[i]=tmp;
HeapAdjust(a,0,i);
}
}
/*
【归并排序】
思想:把2个有序的序列合并成1个有序的序列。
一开始数组里有n个有序组,按顺序两两合并,直到只有一个有序组。
关键就是那个for循环,和怎么两两合并
(感觉不太有意思,就不写了)
【key】稳定;时间O(nlogn);空间O(n)。最好O(nlogn),最坏O(nlogn)
*/
void main(){
//c的特点之一:变量都在开头申明,后面慢慢用
int size=10;
int zz[10]={4,5,7,9,11,1,2,9,0,-1};
//插入排序
//InsertSort(zz,size);
//InsertSort2(zz,size);
//二分插入排序
//BinaryInsertSort(zz,size);
//冒泡排序
//BubbleSort(zz,size);
//选择排序
//SelectSort(zz,size);
//shell排序
//ShellSort(zz,size);
//快速排序
//QuickSort(zz,0,size-1);
//归并排序
//堆排序
HeapSort(zz,size);
printArray(zz,size);
}
当时没写MergeSort(因为感觉没用),后来因为学习java(里面有用到这个排序)。去看了下,感觉还是挺有意思。
这个链接里讲的已经很好——https://www.cnblogs.com/eudiwffe/p/6254394.html
归并排序的思想是分治,代码里面,主要是递归思想、还有就是合并的那个写法(这个排序没有排序,只有合并。最基本的是2个大小为1的有序数组合并,然后2个大小为2的有序数组合并......)。
除了基本思想外,它数组和链表的写法也很有意思。(链表写法的快慢指针可以看下)