“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。
代码如下:
//一次融合函数 时间复杂度O(n) 空间复杂度O(n)
void Merge(int arr[], int len, int gap)
{
int low1 = 0;
int high1 = low1+gap-1;
int low2 = high1+1;
int high2 = low2+gap-1<len ? low2+gap-1 : len-1;
int *brr = (int*)malloc(len * sizeof(int));
assert(brr != NULL);
int i=0;//i指向辅助空间brr的下标
while(low2 < len)//写法不唯一:目的就一个,证明两个手抓到的两个组都有数据
{
while(low1<=high1 && low2<=high2)//保证两个组内还有数据去拿来比较
{
if(arr[low1] <= arr[low2])
{
brr[i++] = arr[low1++];
}
else
{
brr[i++] = arr[low2++];
}
}
//此时,肯定有一个手抓的组为空了,需要判断一下哪个手,做对应的出来
while(low1 <= high1)//左手没空,此时将左手数据向brr中挪动
{
brr[i++] = arr[low1++];
}
while(low2 <= high2)//右手没空,此时将右手数据向brr中挪动
{
brr[i++] = arr[low2++];
}
//此时应该,继续向后抓两个组
low1 = high2+1;
high1 = low1+gap-1;
low2 = high1+1;
high2 = low2+gap-1<len ? low2+gap-1: len-1;
}
//此时,最外层while退出,有两种可能性:1.左手有数据,右手空了 2.左右手都空了
while(low1 < len)//左手没空,此时将左手数据向brr中挪动 //不要写成low1<=high1 因为这里没有办法保证high1的合法性
{
brr[i++] = arr[low1++];
}
//最后将brr中的数据全部重新覆盖到arr里,即可
for(int i=0; i<len; i++)
{
arr[i] = brr[i];
}
free(brr);
}
//归并排序 时间复杂度O(nlogn) 空间复杂度O(n) 稳定性:稳定
void MergeSort(int *arr, int len)
{
for(int i=1; i<len; i*=2)// O(logn)
{
Merge(arr, len, i);
}
}
快速排序:
这是一次划分的结果,然后再对前后两个子序列继续划分,直到每部分内只有一个元素或空为止。
代码如下(递归写法)
//一次划分函数 核心函数 //返回基准值最终所在下标
int Partition(int *arr, int left, int right)
{
//先讲arr数组里的[left, right]的第一个值 作为基准值
int tmp = arr[left];
while(left < right)
{
while(left<right && arr[right] > tmp)//左右边界没有相遇且当前右边的值大于基准值tmp
right--;
if(left < right)//如果此时,左右边界没有相遇,那就只能证明右边right找到了一个小于等于基准值tmp的值
{
arr[left] = arr[right];
}
else
{
break;
}
while(left<right && arr[left] <= tmp)//左右边界没有相遇且当前左边的值小于等于基准值tmp
left++;
if(left < right)//如果此时,左右边界没有相遇,那就只能证明左边left找到了一个大于基准值tmp的值
{
arr[right] = arr[left];
}
else
{
break;
}
}
arr[left] = tmp;//此时 因为 left == right
return left;//return right ok
}
void Quick(int* arr, int left, int right)
{
if(left < right)//保证在这个范围内至少两个数据
{
int par = Partition(arr, left, right);
if (left < par - 1)
{
Quick(arr, left, par-1);
}
if (par + 1 < right)
{
Quick(arr, par + 1, right);
}
}
}
//快速排序:时间复杂度O(nlogn) 空间复杂度O(1) 稳定性:不稳定
void QuickSort(int *arr, int len)
{
Quick_Stack(arr, 0, len-1);
}
快排的特点:数据越有序越慢,越乱越快。
因此需要对代码进行优化,让数据变的越乱越好
//快排优化:
// 1.需要判断len的长度,数据量过小,直接调用直接插入排序
// 2.三数取中法,取第一个值,中间位置的值,最后的值,取不大不小的值放在第一个位置
// 3.随机数法(保证原数据越乱越好)
代码如下:
//新增加一个函数,用于三数取中法
//三数取中法,取第一个值,中间位置的值,最后的值,取不大不小的值放在第一个位置
void Three_Mid(int *arr, int left, int mid, int right)
{
if(arr[mid] > arr[right])//保证中间值小于右边的值
{
int tmp = arr[mid];
arr[mid] = arr[right];
arr[right] = tmp;
}
if(arr[mid] > arr[left])//保证中间的值 一定是三个数的最小值
{
int tmp = arr[mid];
arr[mid] = arr[left];
arr[left] = tmp;
}
//此时 那个不大不小的值 要么在左边,要么在右边
if(arr[left] > arr[right])
{
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
//此时 不大不小的值就在arr的left位置了
}
//在上面代码快排函数中加一个判定条件,如果数据长度小于等于1000,直接调用直接插入函数
void QuickSort(int* arr, int len)
{
if (len <= 1000)
{
InsertSort(arr, len);
return;
}
Quick(arr, 0, len - 1);
}
快排非递归写法(需要用到栈)
这里我直接调用我之前写好的栈相关的函数,代码会有相应的注释。
//快速排序的非递归写法
void Quick_Stack(int *arr, int left, int right)
{
LStack st;//建立一个栈头结点
Init_LStack(&st);//初始化
if(left < right)
{
int par = Partition(arr, left, right);
if(left < par-1)
{
Push(&st, left);//push为入栈函数
Push(&st, par-1);
}
if(par+1 < right)
{
Push(&st, par+1);
Push(&st, right);
}
}
while(!IsEmpty(&st))//判空
{
int p;
int q;
Pop(&st, &q);
Pop(&st, &p);//pop为出栈函数
int par = Partition(arr, p, q);
if(p < par-1)
{
Push(&st, p);
Push(&st, par-1);
}
if(par+1 < q)
{
Push(&st, par+1);
Push(&st, q);
}
}
}
//快速排序:时间复杂度O(nlogn) 空间复杂度O(1) 稳定性:不稳定
void QuickSort(int *arr, int len)
{
if(len<=1000) //优化1:如果n特别小
{
return InsertSort(arr, len);
}
Quick_Stack(arr, 0, len-1);
}