一.选择排序
1.思想
选择排序是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。 以此类推,直到全部待排序的数据元素的个数为零。
我们可以对其做一个优化:我们一趟选择,同时选出最小的和最大的,而非只选出最小/最大的。
但是选择排序的时间复杂度为O(n^2),最好情况下,也为O(n^2)
2.代码
void Swap(int* p, int* q)
{
int tmp = *p;
*p = *q;
*q = tmp;
}
//选择排序
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
int maxi = begin;
int mini = begin;
for (int i = begin; i <= end; i++)
{
if (a[maxi] < a[i])
{
maxi = i;
}
if (a[mini] > a[i])
{
mini = i;
}
}
Swap(&a[begin], &a[mini]);//将最小值交换到begin处
//begin == maxi 时,最大的数被换到了mini的标记处,需要修正一下maxi的位置
if (begin == maxi)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);//将最大值交换到end处
begin++;
end--;
}
}
3.运行结果
二. 快速排序
1.hoare版本
(1)实现思路:
标记一个数为key,一般为最开始的值或者最末尾的值。然后定义L、R两个下标,L为最左边数的下标,R为最右边数的下标。通过L++和R--,根据需求(交换完成后,key左边的数都小于key,key右边的数都大于key)来交换这两个下标指向的值,如下图所示:
然后6成为新的key的值。
注意:选最左边的值做key,让右边先走。
选最右边的值做key,让左边先走。
(2)代码(递归思想)
一趟排完后,key的左边都比key小,key的右边都比key大。如果key的左边变成有序的,key的右边也变成有序的,那么整体就有序了。
比如先使得左边变成有序,那么我们将左边进行一趟排序,这一趟排序的区间就比第一次变小了。
再排序再变小,最后区间变为1或者空。我们使用递归来解决这个问题。
//hoare版本单趟循环
int Partion(int* a, int left, int 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[keyi], &a[left]);
return left;
}
//快速排序
void QuickSort(int* a, int left,int right)
{
//递归跳出条件
if (left >= right)
{
return;
}
int keyi = Partion(a, left, right);
//对左边区间进行快排
QuickSort(a, left, keyi - 1);
//对右边区间进行快排
QuickSort(a, keyi + 1, right);
}
(3)运行结果
2.改进:
(1)缺陷:
当原数列是有序数列时,使用递归会很糟糕(可能会导致栈溢出等)
(2)代码
所以,我们将其进行改进。
//解决快排面对有序的选keyi问题:
// 1.随机选key(但是由于是随机,也不大好)
// 2.三数取中 (更好一些) 左边、中间、右边取不是最大也不是最小的那个做key
int GetMidIndex(int* a, int left, int right)
{
int mid = left + (right - left) / 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
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
int Partion(int* a, int left, int right)
{
//找出三数中 中间的数,将其与left的位置的值交换
int mini = GetMidIndex(a, left, right);
Swap(&a[mini], &a[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[keyi], &a[left]);
return left;
}
3.快速排序的另外一种方法:挖坑法
挖坑法实质是上面那种方法的变形。
(1)代码
//挖坑法
int Partion(int* a, int left, int right)
{
//三数取中
int mini = GetMidIndex(a, left, right);
Swap(&a[mini], &a[left]);
int key = a[left];
int pivot = left;
while (left < right)
{
//右边找比key的值小的数,放入左边的坑里
while (left < right && a[right] >= key)
{
right--;
}
a[pivot] = a[right];
pivot = right;
//左边找比key 的值大的数,放入右边的坑里
while (left < right && a[left] <= key)
{
left++;
}
a[pivot] = a[left];
pivot = left;
}
a[pivot] = key;
return pivot;
}
(2)运行结果