1.选择排序
直接遍历数组,找出最大值和最小值,记录下标,将最大值和最小值分别与首位交换
但是由于当begin == maxi时,会导致出错,因此需要 if 特殊判断
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
//选出最大最小值的位置
int maxi = end;
int mini = begin;
for (int i = begin; i <= end; i++)
{
if (a[end] < a[i])
{
maxi = i;
}
if (a[begin] > a[i])
{
mini = i;
}
}
Swap(&a[begin], &a[mini]);
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
begin++;
end--;
}
}
2.快速排序
快速排序是一种基于二叉树结构的排序
首先将数组的开头作为关键字key,
并设置左右指针left 和 right 分别从头尾开始寻找比key的值大和小的数,(right先走,left后走)
并将二者交换,直到left >= right,
并将key与 left 和 right 所指向的值(比key小)交换,
注:相遇位置一定比key小
原因:
1.L遇到R:R先走,R在比key小的位置停下,L没有找到比key大的数,L与R就会相遇,相遇位置就比key小
2.R遇到L:
(1).第一轮交换过后先交换了则L位置的值一定小于key,R的位置一定大于key,R继续走,找比key小的数,没找到,与L相遇,相遇位置就是L停的位置,一定比key小
(2).第一轮就没找到比key大的数,R与L相遇,此时L还没有开始走,则L在key的位置,相遇位置就是key的位置
这样key的左边就全是比他小的数,key的右边就全是比key大的数,
即key的位置放对了,
之后将数组以已经放好的key为边界分割
进行递归,直到所有递归完成,每一部分都只剩一个节点,就完成了
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left, end = right;
int keyi = left;
while (left < right)
{
while (left < right && a[right] > a[keyi])
{
right--;
}
while (left < right && a[left] < a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
1.缺点
但是,这种快速排序存在一定的缺陷,
数组越接近有序,快速排序的速度越慢,
当数组为有序时,导致每次key都只会移动一位,如图
导致时间复杂度变为O(N^2)
2.时间复杂度优化
1.随机选key
如果将随机值作为key,就可以避免上述问题,
但是如果直接将随机数作为key的话,就需要重新完成单次快速排序(将数组分割为比key大或小)的代码
因此,可以在使用随机数作为key时,可以将选中的key与数组头部交换,就可以复用之前的代码了
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left, end = right;
//选区间中的随机数做key
int randi = rand();
randi %= (right - left + 1);//保证randi的值与区间长度一致
randi += left;//让randi与区间对齐,保证randi一定在范围内
Swap(&a[randi], &a[left]);
int keyi = left;
while (left < right)
{
while (left < right && a[right] > a[keyi])
{
right--;
}
while (a[left] < a[keyi] && left < right)
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
2.三数取中选key
取随机值的方法不够稳定,也可以选择三数取中法
这是一种将有序和随机综合起来的方法
选出三个数left, mid, right,并从她们中取出大小居中的数
这样就可以尽可能避开较坏的情况
//获取三者中数值居中的
int GetMid(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
else//a[left] > a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
{
return right;
}
}
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left, end = right;
//三数取中
int midi = GetMid(a, left, right);
Swap(&midi, left);
int keyi = left;
while (left < right)
{
while (left < right && a[right] > a[keyi])
{
right--;
}
while (left < right && a[left] < a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
3.递归次数优化
由于快速排序的实现依赖递归,而每一次递归都需要建立栈帧,.
而快速排序又有着类似于二叉树的结构,
这样会导致消耗最后一层递归占了所有空间的1/2,
最后的几层数据占据了快速排序的绝大多数空间
而较小的数组区间排序又比较容易且快捷
因此我们可以对较小的区间进行小区间优化
小区间优化可以选择插入排序,可以减少90%左右的递归
4.代码
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
//小区间优化
if (right - left + 1 < 10)
{
InsertSort(a + left, right - left + 1);
}
else
{
int begin = left, end = right;
//三数取中
int midi = GetMid(a, left, right);
Swap(&midi, left);
int keyi = left;
while (left < right)
{
while (left < right && a[right] > a[keyi])
{
right--;
}
while (left < right && a[left] < a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}
3.另一种快排
1.优势
代码简单
2.思想
同样设置关键值key
设置双指针prev 和 cur,分别指向数组头和数组头后面的一个节点
如果a[cur] >= key ,++cur
如果a[cur] < key,++prev,交换a[prev]和a[cur],++cur
这样cur和prev之间就是比key大的值(如果还没有遇到比key大的值就是相邻)
直到cur走到最后,再将key和a[prev]交换
这样就将比key大的数和比key小的数分割了
key就到了正确的位置了
后续进行相同的递归就行了
3.代码
void QuickSort2(int* a, int left, int right)
{
int keyi = left;
int prev = left, cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)//如果++prev == cur,则说明两个数都是小于keyi的,就没必要交换了
Swap(&a[prev], &a[cur]);
++cur;
}
Swap(&a[keyi], &a[prev]);
keyi = prev;
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
4.非递归快速排序
如果递归深度过深,可能会出现问题,因此可以将递归改为非递归
将递归改为非递归可以利用循环或栈(栈符合递归的后进先出的特性)
1.核心
将递归改为非递归的核心是使用栈来存储原本每次递归的区间(left 和 right)
每次都取出栈顶的区间,单次排序
2.代码
void QuickSortNonR(int* a, int left, int right)
{
ST st;//创建一个栈
STInit(&st);
STPush(&st, right);//先让right入栈,则right后出栈,left先出栈
STPush(&st, left);//在栈中存储区间(left 和 right)
while (!STEmpty(&st))
{
int begin = STTop(&st);// 取出left
STPop(&st);
int end = STTop(&st);//取出right
STPop(&st);
//单次
int keyi = begin;
int prev = begin, cur = end;
while (cur <= end)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[prev], &a[cur]);
++cur;
}
Swap(&a[keyi], &a[left]);
keyi = prev;
if (keyi + 1 < end)//说明区间内还有两个及以上的值
{
STPush(&st, end);
STPush(&st, begin + 1);
}
if (begin < keyi - 1)
{
STPush(&st, keyi - 1);
STPush(&st, begin);
}
}
STDestroy(&st);
}