题目描述
给你一个整数数组 nums,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]
提示:
1 <= nums.length <= 50000
-50000 <= nums[i] <= 50000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-an-array
为了练习自己对排序算法的熟练程度,这道题我写了7种算法,5种过了。
【冒泡排序】倒在用例10上,时间超限,因此我猜测时间复杂度为O(n^2)的算法都过不了。
所以再没试着实现此类排序算法,至于快排在最坏情况【数组已有序】时间复杂度也为n平方,为了避免快排也死在路上,因此我采用【三者取中法】去保证复杂度不为最坏。
成功的算法有【希尔排序】【堆排】【归并排序】【三者取中+挖坑式快排】【三者取中+挖坑式快排串联直接插入】,算法实现如下(偷懒,不讲原理,注释也很详细了):
1.希尔排序
void ShellInsert(int*a,int dk,int numsSize)
{
int tmp=0,j=0;
for(int i = dk;i<numsSize;i++){//从dk开始
if(a[i]<a[i-dk]){//后数比前数小
tmp = a[i];//记录要插入的目标值
for(j=i-dk;j>=0&&tmp<a[j];j-=dk)//向前寻找插入位置
a[j+dk]=a[j];//向后覆盖
a[j+dk]=tmp;//目标值插入
}
}
}
int* sortArray(int* nums, int numsSize, int* returnSize){
*returnSize = numsSize;
int delta = numsSize;
while(delta/=2)//增量计算
ShellInsert(nums,delta,numsSize);
ret
提交结果
2.堆排
void AdjustDown(int *a,int n,int root)
{
int k = 2 * root + 1;//root的左孩子
int tmp = a[root];//保存root数据
while (k < n) {//在有效范围内
if (k + 1 < n && a[k + 1] > a[k])//有效范围内找最小的孩子
k += 1;
if (tmp < a[k]) {//父节点数据比孩子节点数据小
a[root] = a[k];//交换数据
root = k;//向深度前进
k = 2 * root + 1;//左孩子
}
else
break;
}
a[root] = tmp;//root数据放回
}
int* sortArray(int* nums, int numsSize, int* returnSize){
*returnSize = numsSize;
int curpos = numsSize / 2 - 1;
while (curpos >= 0) { //AdjustDown里的逻辑决定此处建的大根堆
AdjustDown(nums, numsSize, curpos);
curpos--;
}//大根堆决定是升序排列
int k = numsSize - 1;//堆尾索引
while (k) {
int tmp = nums[0];
nums[0] = nums[k];
nums[k] = tmp;;
AdjustDown(nums, k, 0);
k--;
}
return nums;
}
提交结果
3.归并排序
void MergeSort(int* a, int left, int right, int* tmp){
if(left>=right)
return;
int mid = (left+right)>>1;
MergeSort(a,left,mid,tmp);//左路
MergeSort(a,mid+1,right,tmp);//右路
int begin1,begin2,end1,end2;
begin1 = left,end1 = mid;//左路索引范围
begin2 = mid+1,end2 = right;//右路索引范围
int k = left;//临时空间索引
while(begin1<=end1&&begin2<=end2){//分路升序合并
if(a[begin1]<=a[begin2])
tmp[k++] = a[begin1++];
else
tmp[k++] = a[begin2++];
}
while(begin1<=end1)//此两while处理剩下的数字
tmp[k++] = a[begin1++];
while(begin2<=end2)
tmp[k++] = a[begin2++];
memcpy(a+left,tmp+left,sizeof(int)*(right+1-left));//将有序区回归原空间
}
int* sortArray(int* nums, int numsSize, int* returnSize){
*returnSize = numsSize;
int* tmp = (int*)malloc(sizeof(int) * numsSize);
assert(tmp);
MergeSort(nums,0,numsSize-1,tmp);
free(tmp);
return nums;
}
提交结果
4.三者取中+挖坑式快排
void Swap(int *a,int index1,int index2){//一个简单交换函数
int tmp=a[index1];
a[index1] = a[index2];
a[index2] = tmp;
}
void InvoidO2(int *a,int left,int right){//三者取中法
int mid = (left+right-1)>>1;//中部索引
if(a[left]<a[mid] && a[mid]<a[right-1])//中部索引值非最值
Swap(a,left,mid);
if(a[left]>a[mid] && a[mid]>a[right-1])//中部索引值非最值
Swap(a,left,mid);
if(a[right-1]<a[mid] && a[right-1]>a[left])//右部索引非最值
Swap(a,right - 1,left);
if(a[right-1]>a[mid] && a[right-1]<a[left])//右部索引非最值
Swap(a,right - 1,left);
}
int QKpass(int* a, int left, int right)
{//挖坑法+三者取中
int low = left, high = right - 1;
InvoidO2(a,left,right);
int key = a[low];//这里就解释了为什么三者取中将非最值与left交换
while (low < high) {
while (low < high && a[high] >= key)//遇大值左移
high--;
a[low] = a[high];//遇小值交换
while (low < high && a[low] < key)//遇小值右移
low++;
a[high] = a[low];//遇大值交换
}
a[low] = key;//将萝卜插入坑里
return low;//返回下次的左索引
}
void QKSort(int* a, int left, int right)
{
if(left<right){
int pos = QKpass(a,left,right);
QKSort(a,left,pos);//左半区快排
QKSort(a,pos+1,right);//右半区快排
}
}
int* sortArray(int* nums, int numsSize, int* returnSize) {
*returnSize = numsSize;
if (numsSize < 2)//没有排序必要
return nums;
QKSort(nums, 0, numsSize);
return nums;
}
提交结果
5.三者取中+挖坑式快排 【串联】直接插入
#define M 70
void Swap(int *a,int index1,int index2);
void InvoidO2(int *a,int left,int right);
int QKpass(int* a, int left, int right);
void InsertSort(int* a, int n)
{
int temp = 0;
int j = 0;
for (int i = 1; i < n; i++) {
if (a[i] < a[i - 1]) {//当前数字比有序区最后一个元素小,说明要将其插入有序区
temp = a[i];//监视哨
int left = 0, right = i - 1;//折半双索引
while (left <= right) {//寻找合适插入位置
int mid = (left + right) >> 1;
if (a[mid] < temp)
left = mid + 1;
else
right = mid - 1;
}//已找到合适的位置
for (j = i - 1; j >= left; j--)//大数后移
a[j + 1] = a[j];
a[j + 1] = temp;
}
}
}
void QKSort(int* a, int left, int right)
{
if (right-left <= M)//小于预设值,调用插入排序
InsertSort(a, right);
else {//优化的快排
int pos = QKpass(a, left, right);
QKSort(a, left, pos);
QKSort(a, pos + 1, right);
}
}
int* sortArray(int* nums, int numsSize, int* returnSize) {
*returnSize = numsSize;
if (numsSize < 2)
return nums;
QKSort(nums, 0, numsSize);
return nums;
}
提交结果
后话
我写的排序算法:
【非递归式快排】时间超限
猜测能过的还有:
【三者取中+双指针式快排】
不知道能不能过的有:
【Hoare版快排】
【非三者取中式快排】
【非递归归并】
如果你只需要三者取中函数返回中间值的索引,自己在QKpass函数里完成交换