系列文章目录
第一章 A : 快速排序 冒泡排序 (含直接插入排序)
第二章 B : 更新中
目录
前言
较为常见的排序可大致四类,按个人的喜好,在本篇(A篇)中先介绍快速排序,冒泡排序
一、排序铺垫
在排序时,交换数一直很常见,单拎出来方便后续操作
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
在排序完后,次次都要调试才看出来,过于麻烦。打包一个函数,方便看出来排序排成了个啥,最起码 知道个对错😂。
void PrintfA(int* a, int n)
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
例如
void TestTQSort()
{
int a[] = { 6,1,2,7,9,3,4,5,1,8,10,8 };
//int a[] = { 2,2,2,2,2,2,2,2,2,2,2,2,2 };
//int a[] = { 6,1,2,7,9,3,4,5,1,8,10,8 };
TQSort(a,0, sizeof(a) / sizeof(int)-1);
PrintfA(a, sizeof(a) / sizeof(int));
}
二、快速排序(内含插入排序)
插入排序
快速排序(hoare版)图示作引
快速排序的基本套路:
1) 快排的关键便是找到一个key,以他为分界[begin,key-1],key,[key+1,end]独立的两部分。
2)在key左右两边同时经行排序 交换一部分记录的元素值均比基准元素值小,另一部分记录的 元素值比基准值大。
3)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。(一般递归即可)
快速排序中有两个较恶劣情况:
1.要排序的数非常非常多,针对key的寻找较为困难,可采用随机值版的三数取中
2.所有数都相等,例如 对2000个2进行排序,这种情况可采用三路快排的方法
现直接出示我认为最舒服的版本,并会在后面放出一般的非递归版本
1.插入排序(在这作为快排的一部分)
直接插入排序虽性能较差,但胜在思路与操作简单,非常适合在小区间使用
void InsertSort(int* a, int n)
{
for(int i=0;i<n-1;++i)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])//升序
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
其他的插入排序有二分插入排序,2-路插入排序。
2.三数取中(随机值版)
三数取中即是为了找个基准值key,这对其性能影响较大。
即在在起始位置,中间位置,末尾位置中选出中间值,作为基准值。
int GetMidIndex(int* a, int begin, int end)
{
//int mid = (begin + end) / 2;
int mid=begin+rand()%(end-begin);
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
{
return mid;
}
else if (a[begin] > a[end])
{
return begin;
}
else
{
return end;
}
}
else //a[begin] > a[mid]
{
if (a[mid] > a[end])
{
return mid;
}
else if (a[begin] < a[end])
{
return begin;
}
else
{
return end;
}
}
}
3.三路快排(排序的主体)
void TQSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if ((end - begin + 1) < 15)
{
// 小区间用直接插入替代,减少递归调用次数
InsertSort(a+begin, end - begin + 1);
}
else
{
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int left = begin, right = end;
int key = a[left];
int cur = begin + 1;
while (cur <= right)
{
if (a[cur] < key)
{
Swap(&a[cur], &a[left]);
cur++;
left++;
}
else if (a[cur] > key)
{
Swap(&a[cur], &a[right]);
--right;
}
else // a[cur] == key
{
cur++;
}
}
// [begin, left-1][left, right][right+1,end]
TQSort(a, begin, left - 1);
TQSort(a, right + 1, end);
}
}
三.冒泡排序
冒泡排序
就是一个带一个,两两一对送到后排
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; ++j)
{
int exchange = 0;
for (int i = 0; i < n - j; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
//如果一趟冒泡
if (exchange == 0)
{
break;
}
}
}
经常出现(n-1)是因为只剩两个元素时只需要一趟就可以完成)
冒泡排序可谓是c语言中的扫盲级存在。。。
四.快速排序的非递归
快速排序非递归实现,需要借助栈,栈中存放的是需要排序的左右区间。
而且非递归可以彻底解决栈溢出的问题。
其实他的思想与递归是类似的:
- 将数组左右下标入栈,
- 若栈不为空,两次取出栈顶元素,分别为闭区间的左右界限
- 将区间中的元素按照前后指针法排序(其余两种也可)得到基准值的位置
- 再以基准值为界限,若基准值左右区间中有元素,则将区间入栈
- 重复上述步骤直到栈为空
代码如下:
void QuickSortNonR(int* a, int left, int right)
{
//创建栈
Stack st;
StackInit(&st);
//原始数组区间入栈
StackPush(&st, right);
StackPush(&st, left);
//将栈中区间排序
while (!StackEmpty(&st))
{
//注意:如果right先入栈,栈顶为left
left = StackTop(&st);
StackPop(&st);
right = StackTop(&st);
StackPop(&st);
//得到基准值
int mid = PartSort3(a, left, right);
// 以基准值为分割点,形成左右两部分
if (right > mid+1)
{
StackPush(&st, right);
StackPush(&st, mid + 1);
}
if (left < mid - 1)
{
StackPush(&st, mid - 1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
五.总结
快速排序的特性总结:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序,一个人外号叫飞毛腿,那肯定是有道理。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
冒泡排序的特性总结:
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
如鲠在喉,不吐不快:快排有一种分而治之的美。
—————— 我希望正在读这句话的人永远开心
六.避坑版本(请自行观察,注意避错)
如有需要将全部打上"//",以免对大家产生误导
1.经典整个交换函数还传值,入门级错误
void Swap(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
2.浪费版冒泡
{
for (int j = 0; j < n; ++j)
{
for (int i = 0; i < n - j; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
}
}
}
}
3.真有这么简单吗😄(不止一处,且需注意链表队列的常见错 )
void QuickSort(int* a, int n)
{
int left = 0, right = n - 1;
int key = a[left];
while (left < right)
{
//右边先走找小
while (a[right] > key)
{
--right;
}
//左边再走,找大
while (a[left] < key)
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], key);
}
本篇彻底完结,感谢你的支持。送人玫瑰,手有余香,你的三连将会是我最大动力,谢谢。