文章目录
冒泡排序,快速排序
冒泡排序
基本思想
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
思维导图
特性总结
-
冒泡排序是一种非常容易理解的排序
-
时间复杂度:O(N^2) 最好:O(N)
-
空间复杂度:O(1)
-
稳定性:稳定
代码实现
void bubblesort(int* a, int n)
{
for (int j = 0; j < n-1; j++)
{
int flag = 0;
for (int i = 1; i < n - j; i++)
{
if (a[i - 1] > a[i])
{
flag = 1;
swap(&a[i - 1], &a[i]);
}
}
//程序走到这里,若flag等于0
//说明在遍历过程中没有发生交换,即整个数组都是有序的
//故直接跳出循环
if (flag==0)
{
break;
}
}
}
快速排序
基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中
的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右
子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序单趟
hoare版本
代码实现
//hoare版本
int partsort1(int *a,int left,int right)
{
//三数取中,
//面对有序的情况下,选中位数做key,使最坏情况变成最好情况
//三数取中会保证一定不会选到最大数或者最小数做key
int mid = midnums(a,left,right);
//选出中位数后,将这个数与left交换
swap(&a[mid], &a[left]);
int key = left;
while (left<right)
{
//右边先走,找小
while (left < right && a[right] >= a[key])
right--;
//左边走,找大
while (left<right && a[left]<=a[key])
left++;
swap(&a[left], &a[right]);
}
//left和right相遇了,跳出循环
//将相遇位置的值和key交换
swap(&a[key], &a[left]);
return left;
}
挖坑法
代码实现
//挖坑法
int partsort2(int* a, int left, int right)
{
//三数取中,
//面对有序的情况下,选中位数做key,使最坏情况变成最好情况
//三数取中会保证一定不会选到最大数或者最小数做key
int mid = midnums(a, left, right);
//选出中位数后,将这个数与left交换
swap(&a[mid], &a[left]);
int key = a[left];
int pit = left;
while (left<right)
{
//右边找小,找到了就放到坑位里面去
//这时right的位置就是新的坑位
while (left<right&&a[right]>=key)
{
right--;
}
a[pit] = a[right];
pit = right;
//左边找大,找到了就放到坑位里面去
//这时left的位置就是新的坑位
while (left<right&&a[left]<=key)
{
left++;
}
a[pit] = a[left];
pit = left;
}
a[pit] = key;
return pit;
}
前后指针法
代码实现
//前后指针法
int partsort3(int* a, int left, int right)
{
int mid = midnums(a, left, right);
//选出中位数后,将这个数与left交换
swap(&a[mid], &a[left]);
int key = left;
int prev = left;
int cur = prev + 1;
//cur往前走,找比key小的数,找到后,先++prev,
//然后再交换prev和cur的值
while (cur<=right)
{
if (a[cur]<a[key]&&++prev!=cur)
{
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[prev], &a[key]);
return prev;
}
快速排序优化
-
三数取中法选key
-
递归到小的子区间时,可以考虑使用插入排序
思维导图
代码实现
//三数取中
int midnums(int* a, int left, int right)
{
//取left和right的中间值
int mid = left+((right-left)>>1);
if (a[left]>a[mid])
{
if (a[mid]>a[right])
{
return mid;
}
else if (a[left]>a[right])
{
return right;
}
else
{
return left;
}
}
else
{
if (a[left]>a[right])
{
return left;
}
else if (a[right]>a[mid])
{
return mid;
}
else
{
return right;
}
}
}
//小区间优化
void quicklysort(int*a ,int left,int right)
{
if (left>=right)
{
return;
}
//小区间优化,当分割到小区间时,小区间的数已经接近有序
//不再用递归让其有序,用插入排序效率更高
if (right-left+1<10)
{
insertsort(a+left,right-left+1);
}
else
{
int key = partsort3(a, left, right);
quicklysort(a, left, key - 1);
quicklysort(a, key + 1, right);
}
}
特性总结
-
快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
-
时间复杂度:O(N*logN)
-
空间复杂度:O(logN)
-
稳定性:不稳定
快排递归思维导图
代码实现(递归法)
//前后指针法
int partsort3(int* a, int left, int right)
{
int mid = midnums(a, left, right);
//选出中位数后,将这个数与left交换
swap(&a[mid], &a[left]);
int key = left;
int prev = left;
int cur = prev + 1;
//cur往前走,找比key小的数,找到后,先++prev,
//然后再交换prev和cur的值
while (cur<=right)
{
if (a[cur]<a[key]&&++prev!=cur)
{
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[prev], &a[key]);
return prev;
}
//快速排序(递归法)
void quicklysort(int*a ,int left,int right)
{
if (left>=right)
{
return;
}
//小区间优化,当分割到小区间时,小区间的数已经接近有序
//不再用递归让其有序,用插入排序效率更高
if (right-left+1<10)
{
insertsort(a+left,right-left+1);
}
else
{
int key = partsort3(a, left, right);
//[left, key - 1] key [key + 1, right]
quicklysort(a, left, key - 1);
quicklysort(a, key + 1, right);
}
}
快排非递归思维导图
非递归相较于递归有哪些优势:
1.提高效率(递归建立栈帧是有消耗的,但对于现代的计算机,这个优化很小)
2.递归最大的缺陷是,如果栈帧的深度太深,可能会导致栈溢出,因为系统栈空间一般不大
3.数据结构栈模拟非递归,数据是存储在堆上的
代码实现(非递归法)
void quicklysortnotR(int* a, int left, int right)
{
ST s;
Stackinit(&s);
Stackpush(&s, left);
Stackpush(&s, right);
while (Stackempty(&s))
{
int end = Stacktop(&s);
Stackpop(&s);
int begin = Stacktop(&s);
Stackpop(&s);
int key = partsort3(a, begin, end);
//当begin>=key-1时,就表示[begin,key-1]这个区间只有一个值,或者这个区间不存在
//只有一个值,那就没有必要排序了
//区间不存在时,也没有必要入栈了
if (key-1>begin)
{
Stackpush(&s, begin);
Stackpush(&s, key - 1);
}
//同上
if (key+1<end)
{
Stackpush(&s, key + 1);
Stackpush(&s, end);
}
}
Stackdestory(&s);
}
总结
快速排序这一节知识点很多,要求掌握单趟快排的前后指针法,其他两种方法也要了解。快排的非递归也要掌握!快排的基本思想:选一个最左边或最右边的数做key,然后通过指针去将比key大的数,比key小的数分别放在key的两边,用到了分治的思想。快排的优化方法:1.三数取中。2.递归到小的子区间时,可以考虑使用插入排序 。但是,快速排序无论怎么优化还是会有一个缺点:当待排序序列的元素都是相同或者接近相同时,是快排的最坏情况,且解决不了。