希尔排序
希尔排序的基本思想:
如果一个序列基本有序,那么插入排序会很高效
将待排序列划分为若干组,在每一组内进行插入排序,使得子序基本有序
再对整个序列进行插入排序
希尔排序示例:
例如:将n个数据元素分成d个子序列:
{ R[1] , R[1+d] , R[1+2d] , … , R[1+kd]}
{ R[2] , R[2+d] , R[2+2d] , … , R[2+kd]}
{ R[d] , R[2d] , R[3d] , … , R[kd] , R[(k+1)d] }
其中,d称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后—次排序减为1。
![](https://i-blog.csdnimg.cn/blog_migrate/14bd426cec983af55caaba276d4aa7a3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/39baa29d4de6219c9c4de5e64a563bd0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/aa6b186e2d14a2211b3e1f739fcc04a4.png)
/* 希尔排序 O(n3/2)
不稳定排序
不是标准定义上的希尔排序,只对每次分组的第一组进行了排序
*/
template < typename T >
static void Shell(T arr[],int len,bool ASC = true)
{
int d = len;
do
{
d = d/3 + 1; //实践证明/3是最快的,记住即可
for(int i=d;i<len;i+=d)//分组进行插入排序
{
int spe = i;
T e = arr[i];
for(int j=i-d;(j>=0)&&(ASC ? (e < arr[j]) : (e > arr[j]) );j-=d)
{
arr[j+d] = arr[j];
spe = j;
}
if(spe != i)
{
arr[spe] = e;
}
}
}while(d > 1);
}
归并排序
归并排序的基本思想:
将两个或两个以上的有序序列合并成—个新的有序序列
有序序列V[0]…V[m]和V[m+1]…V[n-1] ⇢ V[0]…V[n-1]
这种归并方法称为归并(2个有序序列为2路归并,3个为3路归并)
![](https://i-blog.csdnimg.cn/blog_migrate/448df393aed723a6eb7845804e25cc3f.png)
/* 二路归并
归并排序:将两个以上的有序序列合并成一个新的有序序列
*/
template < typename T >
static void Merga(T arr[],T helper[],int begin,int mid,int end,bool ASC = true)
{
int i = begin;
int j = mid + 1;
int k = begin;
while( (i <= mid)&&(j <= end) )
{
if(ASC ? (arr[i]<arr[j]):(arr[i]>arr[j]) )
{
helper[k++] = arr[i++];
}
else
{
helper[k++] = arr[j++];
}
}
while(i <= mid)//一路全部进去,将另一路遗留的也录入
{
helper[k++] = arr[i++];
}
while(j <= end)
{
helper[k++] = arr[j++];
}
for(i=begin;i<=end;i++)//排好序后的序列替代原序列
{
arr[i] = helper[i];
}
}
// 归并 的 递归分解
template < typename T >
static void Merga(T arr[],T helper[],int begin,int end,bool ASC = true)
{
if(begin < end)//只有一个元素时,为有序序列
{
int mid = (begin + end) / 2;
Merga(arr,helper,begin,mid,ASC);//左路分解
Merga(arr,helper,mid+1,end,ASC);//右路分解
Merga(arr,helper,begin,mid,end,ASC);//将两个有序序列归并
//上式不用mid-1,因为begin 0 end 1时,求出的mid仍为0,右路永远分解不到头
}
}
/* 归并排序 O(nlogn)
稳定排序,需要辅助空间
利用递归调用将序列分解为有序序列,再进行二路归并
*/
template < typename T >
static void Merga(T arr[],int len,bool ASC = true)
{
T* helper = new T[len];
if(helper)
{
Merga(arr,helper,0,len-1,ASC);
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to creat helper arr ...");
}
delete[] helper;
}
快速排序
快速排序的基本思想 :
任取序列中的某个数据元素作为基准将整个序列划分为左右两个子序列
左侧子序列中所有元素都小于或等于基准元素
右侧子序列中所有元素都大于基准元素
基准元素排在这两个子序列中间
分别对这两个子序列重复进行划分,直到所有的数据元素都排在相应位置上为止
![](https://i-blog.csdnimg.cn/blog_migrate/cbf252ef44e0f4b60fce77215e899c24.png)
//返回基准在数组中的下标(只遍历一次)
template < typename T >
static int Partition(T arr[],int begin,int end,bool ASC = true)
{
T pv = arr[begin];
while(begin < end)
{
while( (begin < end)&&(ASC ? pv < arr[end] : pv > arr[end]) )
{
end--;
}
swap(arr[begin],arr[end]);
while( (begin < end)&&(ASC ? pv >= arr[begin] : pv <= arr[begin]) )
{
begin++;
}
swap(arr[begin],arr[end]);
}
arr[begin] = pv;
return begin;
}
// 快速排序 的 递归分解
template < typename T >
static void Quick(T arr[],int begin,int end,bool ASC = true)
{
if(begin < end)
{
int pivot = Partition(arr,begin,end,ASC);//找基准
Quick(arr,begin,pivot-1,ASC);//左分路递归找基准
Quick(arr,pivot+1,end,ASC);//右分路递归找基准
}
}
/* 快速排序 O(nlogn)
不稳定排序
利用递归调用将序列依据基准分开
*/
template < typename T >
static void Quick(T arr[],int len,bool ASC = true)
{
Quick(arr,0,len-1,ASC);
}
小结
名称 | 稳定性 | 时间复杂度 | 空间复杂度 | 补充 |
---|---|---|---|---|
选择排序 | 不稳定 | O(N2) | O(1) | |
冒泡排序 | 稳定 | O(N2) | O(1) | |
插入排序 | 稳定 | O(N2) | O(1) | |
希尔排序 | 不稳定 | O(N3/2) | O(1) | |
归并排序 | 稳定 | O(NlogN) | O(N) | 需要辅助空间O(N) |
快速排序 | 不稳定 | O(NlogN) | O(1) | 最坏情况下为O(N2) |