快速排序(升序排序):快速排序的核心就是找到一个基准值(我们这里采用最后一个元素作为基准值),然后在剩下的区间里从左往右找到一个大于基准值的数,再从右往左找到一个小于基准值的数然后将二者交换,直到二者相遇一趟排序结束。然后在继续下一趟排序。
////////////////////////////////////
//快速排序的递归版本
////////////////////////////////////
//交换法
int Partion(int arr[],int beg,int end)
{
if(end - beg <= 1)
{
return beg;
}
int left = beg;
int right = end-1;
//取数组的最后一个元素作为基准值
int key = arr[right];
while(left < right)
{
//从左往右找到一个大于基准值的元素
while(left < right && arr[left] <= key)
{
left++;
}
//从右往左找到一个小于基准值的元素
while(left < right && arr[right] >= key)
{
right--;
}
if(left < right)
{
swap(&arr[left],&arr[right]);
}
}
//最后把left指向的位置和基准值的位置进行交换
swap(&arr[left],&arr[end-1]);
return left;
}
//挖坑法
int Partion2(int arr[],int beg,int end)
{
if(end -beg <= 1)
{
return beg;
}
int left = beg;
int right = end-1;
int 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相遇,那么说明整个区间就整理完毕了
//还剩下一个坑需要填,此时就把基准值填进去就可以了
arr[left] = key;
return left;
}
void _QuickSort(int arr[],int beg,int end)
{
if(end - beg <= 1)
{
//要么没有元素要么就只有一个元素
return;
}
//[beg,mid)左区间
//[mid+1,end)右区间
//左区间的所有元素一定都小于等于右区间的所有元素
int mid = 0;
mid = Partion(arr,beg,end);
_QuickSort(arr,beg,mid);
_QuickSort(arr,mid+1,end);
}
//快速排序
void QuickSort(int arr[],int len)
{
if(len <= 1)
{
//不需要排序
return;
}
//[0,len)
_QuickSort(arr,0,len);
}
///////////////////////////////////
//非递归版本
///////////////////////////////////
void QuickSortByLoop(int arr[],int len)
{
if(len <= 1)
{
//不需要排序
return;
}
Stack stack;
StackInit(&stack);
int beg = 0;
int end = len;
//需要注意的是我们这里每次入栈总是入栈两次
//即我们入栈了一对儿值。而这一对值就是一个区间
//[beg,end)
StackPush(&stack,beg);
StackPush(&stack,end);
while(1)
{
//取栈顶元素
//由于入栈时的一对值是一个区间
//所以后入展的一定是该区间的右边范围值
//即该区间的结束位置
int ret = StackTop(&stack,&end);
if(ret == 0)
{
//栈为空,说明快速排序就结束了
break;
}
//走到这里说明有区间待排序
//所以将该栈顶元素弹出
StackPop(&stack);
//再取栈顶元素就取到该区间的左边结束位置的值
StackTop(&stack,&beg);
//[beg,end)相当于是即将要进行快速排序
//进行整理的区间
if(end-beg <= 1)
{
//此时该区间内有吗有一个元素要么一个元素都没有
//因此不需要排序,直接进行下一趟循环
continue;
}
//整理该区间
int mid = Partion(arr,beg,end);
//在继续将区间压栈,然后进行下一趟循环
//[beg,mid),[mid+1,end]
StackPush(&stack,beg);
StackPush(&stack,mid);
StackPush(&stack,mid+1);
StackPush(&stack,end);
}
}
//测试一下
void TestQuickSort()
{
Test_Header;
int arr[] = {2,5,1,9,6,23,12,0,45,31,44,25};
int len = sizeof(arr)/sizeof(arr[0]);
//QuickSort(arr,len);
QuickSortByLoop(arr,len);
printf("expect:0,1,2,5,6,9,12,23,25,31,44,45\n");
printf("actual:");
int i = 0;
for(;i < len;i++)
{
if(i != len-1)
printf("%d,",arr[i]);
else
printf("%d\n",arr[i]);
}
}
测试结果:
快速排序:
时间复杂度:平均为O(N*logN),最坏O(N^2)
空间复杂度:最坏O(N)
稳定性:不稳定排序
归并排序(升序排序):将待排序的的区间分成两个长度相等的小区间,对每一个小区间进行排序,所有的小区间排序完毕在将小区间合并成一个区间。
//////////////////////////////////////////
//递归版本
//////////////////////////////////////////
//合并区间函数(这与我们之前写的合并两个有序链表的思路相同)
void _MergeArr(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])
{
//此时cur1对应的元素比较小
//就将cur1对应的元素放到新区间中去
tmp[tmp_index++] = arr[cur1++];
//并且将cur1和tmp_index往后移动一步
//用于下一次的元素比较和存放
}
else
{
//此时cur2对应的元素比较小
//就将cur2对应的元素放到新区间中去
tmp[tmp_index++] = arr[cur2++];
//并且将cur2和tmp_index往后移动一步
//用于下一次的元素比较和存放
}
}
//走到这说明是某一个区间遍历完了
//此时我们需要把剩下的没有遍历完的区间
//中剩下的所有元素依次放入新的空间(tmp)中去
while(cur1 < mid)
{
//此时是cur2走完了,cur1还剩下有元素没有处理
//所以将cur1往后剩下的元素放到新的空间中去
tmp[tmp_index++] = arr[cur1++];
}
while(cur2 < end)
{
//此时是cur1走完了,cur2还剩下有元素没有处理
//所以将cur2往后剩下的元素放到新的空间中去
tmp[tmp_index++] = arr[cur2++];
}
//最后把tmp中的内容拷贝到数组中去即可完成排序
//进行归并的时候,处理的区间是[beg,end)
//对应的会把这部分区间的元素填充到tmp[beg,end)区间上
//此时这里的拷贝会arr的动作,要保证结果放到正确的区间上
memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
return;
}
//[beg,end)就是我们当前要处理的子数组
void _MergeSort(int arr[],int beg,int end,int *tmp)
{
if(end - beg <= 1)
{
//递归出口
//要么只有一个元素,要么没有元素
return;
}
int mid = beg+(end-beg)/2;
//此时我们就有两个区间
//[beg,mid),和[mid,end)
//然后对于这两个区间都调用该函数
//对于两个区间进行递归的处理
//此处的处理就是划分区间
//直到最后只剩下一个元素不能再划分为止
_MergeSort(arr,beg,mid,tmp);
_MergeSort(arr,mid,end,tmp);
//合并区间,合并后的区间内的元素是有序的
_MergeArr(arr,beg,mid,end,tmp);
return;
}
void MergeSort(int arr[],int len)
{
if(len <= 1)
{
//不需要排序
return;
}
//此时我们创建一个临时空间用来合并元素
int *tmp = (int*)malloc(sizeof(int)*len);
_MergeSort(arr,0,len,tmp);
free(tmp);
}
//////////////////////////////////////////
//非递归版本
//////////////////////////////////////////
void MergeSortByLoop(int arr[],int len)
{
if(len <= 1)
{
//不需要排序
return;
}
//创建一个临时空间用来合并元素
int *tmp = (int*)malloc(sizeof(int)*len);
//定义一个步长gap,初始为1,
//相当于每次合并两个长度为gap的有序区间
int gap = 1;
for(;gap < len;gap*=2)
{
//在当前gap下用i辅助完成所有长度为gap的区间的合并
int i = 0;
for(;i < len;i+=2*gap)
{
//[beg,mid),[mid,end)
int beg = i;
int mid = i+gap;
int end = i+2*gap;
if(mid > len)
{
mid = len;
}
if(end > len)
{
end = len;
}
//排序
_MergeArr(arr,beg,mid,end,tmp);
}
}
free(tmp);
}
//测试一下:
void TestMergeSort()
{
Test_Header;
int arr[] = {2,5,1,9,6,23,12,0,45,31,44,25};
int len = sizeof(arr)/sizeof(arr[0]);
//MergeSort(arr,len);
MergeSortByLoop(arr,len);
printf("expect:0,1,2,5,6,9,12,23,25,31,44,45\n");
printf("actual:");
int i = 0;
for(;i < len;i++)
{
if(i != len-1)
printf("%d,",arr[i]);
else
printf("%d\n",arr[i]);
}
}
测试结果:
归并排序:
时间复杂度:最差和平均时间复杂度都是O(n*logn)
空间复杂度:O(n)
稳定性:稳定排序