交换函数
void Swap(int *a,int *b)
{
int tmp=*a;
*a=*b;
*b=tmp;
}
希尔排序,
希尔排序也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或直接插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2时分成两个子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序之前第二个子序列中的8在前面,现在对两个子序列进行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,两个8的相对次序发生了改变。
//////////////////////
//希尔排序
//最右时间复杂度:O(N),最差时间复杂度:O(NllogN)
//空间复杂度:O(1) 不稳定排序
void ShellSort(int arr[ ],int size)
{
if( size<=1)
{
return ;
}
//定义一个步长,
int gap=size;
//分组同时进行排序,当gap=1时,就是直接插入排序
while( gap>1)
{
//gap/3是普遍常用的求gap值
//加1是为了gap最后可以等于1(进行直接插入排序)
gap=gap/3+1;
int i;
for(i=0;i<size-gap;++i)
{
int end=i;
int tmp=arr[end+gap];
while( end>=0&&arr[end]>tmp)
{
arr[end+gap]=arr[end];
end-=gap;
}
arr[end+gap]=tmp;
}
}
return ;
}
归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
归并宏观图
程序中分组思想
/时间复杂度:O(NlogN)
//空间复杂度:O(N)
//稳定排序
//适用:对链表,数组高效的排序
void _MargeArray(int arr[ ],int beg,int mid,int end,int *tmp)
{
int cur1=beg;
int cur2=mid;
int tmp_index=beg;
while(cur1<mid && cur2<end)
{
if(arr[cur1]<arr[ cur2])
{
tmp[tmp_index++]=arr[cur1++];
}
else
{
tmp[tmp_index++]=arr[cur2++];
}
}
while( cur1<mid)
{
tmp[tmp_index++]=arr[cur1++];
}
while( cur2<end)
{
tmp[tmp_index++]=arr[cur2++];
}
//把tmp中的元素拷贝到arr中
//进行归并的时候处理的区间是arr[beg,end]
//对应的会把这部分区间元素填充到tmp[beg,end];
//此时,最后一把拷贝回去时,要保证结果放在正确的位置上
memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
return ;
}
//[beg,end]就是我们当前要处理的子数组
void _MargeSort(int arr[ ],int beg,int end,int *tmp)
{
if( end-beg<=1)
{
//要么一个元素,要么两个元素,要么非法
return ;
}
//找到中间位置,划分区间
int mid=beg+(end-beg)/2;
//递归划分左区间
_MargeSort(arr,beg,mid,tmp);
//递归划分右区间
_MargeSort(arr,mid,end,tmp);
_MargeArray(arr,beg,mid,end,tmp);
return ;
}
//递归版本的归并排序
void MargeSort(int arr[ ],int size)
{
if(size<=1)
{
return ;
}
int *tmp=(int *)malloc(sizeof(int)*size);
_MargeSort(arr,0,size,tmp);
free(tmp);
}
//非递归版本的归并排序
void MargeSortByLoop(int arr[ ],int size)
{
if(size<=1)
{
return ;
}
int *tmp=( int *)malloc(sizeof(int)*size);
//定义一个步长,初始为1,相当于每次合并两个
//长度为gap的有序区间
int gap=1;
for( ;gap<size;gap*=2)
{
//在当前gap下,适用i辅助我们完成所有
//长度为gap的合并
int i=0;
for( ;i<size;i+=2*gap)
{
int beg=i;
int mid=i+gap;
if( mid>size)
{
mid=size;
}
int end=i+gap*2;
if( end>size)
{
end=size;
}
_MargeArray(arr,beg,mid,end,tmp);
}
}
free(tmp);
}
快速排序
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
从序列中挑出一个元素,作为”基准”(pivot).
把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。
比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序
//////////////////////////////
//快速排序
//时间复杂度:O(NlogN)
//空间复杂度:O(logN) 不稳定排序
//////////////////////////////
//挖坑法
int Partion2(int arr[ ],size_t beg,size_t end)
{
if( end-beg<=1)
{
return beg;
}
size_t left=beg;
size_t right=end-1;
size_t key=arr[right];
while( left<right)
{
while( left<right&&arr[left]<=key)
{
++left;
}
//循环退出,意味着left就指向一个大于基准值的元素
//就可以把这个值赋值给刚才right指向的坑里
//一旦赋值成功,left自身也就成了一个坑
if(left<right)
{
arr[right--]=arr[left];
}
while(left<right&&arr[right]>=key)
{
--right;
}
//循环退出,意味着right就指向一个大于基准值的元素
//就可以把这个值赋值给刚才left指向的坑里
//一旦赋值成功,right自身也就成了一个坑
if(left<right)
{
arr[left++]=arr[right];
}
}
//一旦left和right重合了,那么说明整个区间都已经整理完了
//还剩下一个坑需要填,把基准值填到left指向的坑里就行了
arr[left]=key;
return left;
}
//经典算法
int Partion(int arr[ ],size_t beg,size_t end)
{
if(end-beg<=1)
{
return 0;
}
int left=beg;
int right=end-1;
//定义一个key变量,来保存基准值
//其中基准值一般设置为数组最后一个元素
int key=arr[right];
while(left<right)
{
//从左往右进行比较,循环结束,说明当前值比key大,则需要交换
while(left<right&& arr[left]<=key)
{
++left;
}
//从右向左比较,循环结束,说明当前值比key小,则需要交换
while(left<right&& arr[right]>=key)
{
--right;
}
if(left<right)
{
Swap(&arr[left],&arr[right]);
}
}
Swap(&arr[left],&arr[end-1]);
return left;
}
//排序区间整理函数
void _QuickSort(int arr[ ],size_t beg,size_t end)
{
if(end-beg<=1)
{
return ;
}
int mid=Partion2(arr,beg,end);
_QuickSort(arr,beg,mid);
_QuickSort(arr,mid+1,end);
return ;
}
//排序主函数
void QuickSort(int arr[ ],size_t size)
{
if( size<=1)
{
return ;
}
_QuickSort(arr,0,size);
return ;
}
//利用栈实现快速排序
void QuickSortByLoop(int arr[ ],int size)
{
if(size<=1)
{
return ;
}
SeqStack seqstack;
SeqStackInit(&seqstack);
int beg=0;
int end=size;
SeqStackPush(&seqstack,beg);
SeqStackPush(&seqstack,end);
while( 1)
{
int ret=SeqStackTop(&seqstack,&end);
if( ret==0)
{
//此时栈为空,说明排序快结束le
return ;
}
SeqStackPop(&seqstack);
SeqStackTop(&seqstack,&beg);
//[ beg,end]相当云即将进行整理的区间
if(end-beg)
{
continue;
}
int mid=Partion(arr,beg,end);
//[beg,mid)
//[mid+1,end]
SeqStackPush(&seqstack,beg);
SeqStackPush(&seqstack,mid);
SeqStackPush(&seqstack,mid+1);
SeqStackPush(&seqstack,end);
}
}