前言
在上篇博客中已经介绍了插入排序中的直接插入排序和希尔排序,这篇博客将会介绍选择排序和交换排序。
选择排序
选择排序的基本思想:
每一次从待排序的数据元素中选出最小(或最大)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
直接选择排序
思路:
- 在元素集合
array[i]--array[n-1]
中选择关键码最大(小)的数据元素 - 若它不是这组元素中的最后⼀个(第⼀个)元素,则将它与这组元素中的最后⼀个(第⼀个)元素交换
- 在剩余的
array[i]--array[n-2](array[i+1]--array[n-1])
集合中,重复上述步骤,直到集合剩余1个元素
代码实现:
void SelectSort(int* arr, int n)
{
int begin = 0;
while (begin < n-1)
{
int mini = begin;
for (int i = begin+1; i < n; i++)
{
if (arr[mini] > arr[i])
{
mini = i;
}
}
swap(&arr[begin], &arr[mini]);
begin++;
}
}
优化:
我们可以同时将最大和最小值分别放入数组的头和尾
void SelectSort(int* arr, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mini = begin;
int maxi = end;
for (int i = begin + 1; i <=end-1; i++)
{
if (arr[mini] > arr[i])
{
mini = i;
}
if (arr[maxi] < arr[i])
{
maxi = i;
}
}
if (maxi == begin)
{
maxi = mini;
}
swap(&arr[begin], &arr[mini]);
swap(&arr[end], &arr[maxi]);
begin++;
end--;
}
}
直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
堆排序
堆排序是指利用堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
在⼆叉树章节我们已经实现过堆排序,这里不再赘述。
交换排序
交换排序基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
冒泡排序
冒泡排序是⼀种最基础的交换排序,之所以叫做冒泡排序,因为每⼀个元素都可以像小气泡⼀样,根据自身大小⼀点⼀点向数组的⼀侧移动。
void BubbleSort(int* a, int n)
{
int exchange = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
exchange = 1;
swap(&a[j], &a[j + 1]);
}
}
if (exchange == 0)
{
break;
}
}
}
冒泡排序的特性总结:
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
快速排序
快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序方法,其基本思想为:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
hoare版本
思路:
- 创建左右指针,确定基准值
- 从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环
int _QuickSort(int* arr, int left, int right)
{
int keyi = left;
left++;
while (left <= right)
{
while (left <= right && arr[left] < arr[keyi])
{
left++;
}
while (left <= right && arr[right] > arr[keyi])
{
right--;
}
if (left <= right)
{
swap(&arr[left++], &arr[right--]);
}
}
swap(&arr[keyi],&arr[right]);
return right;
}
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
int keyi = _QuickSort(arr, left, right);
QuickSort(arr, left, keyi - 1);
QuickSort(arr, keyi + 1, right);
}
问题1:为什么跳出循环后
right
位置的值⼀定不大于keyi
?
当left > right
时,即right
走到left
的左侧,而left
扫描过的数据均不大于keyi
,因此right
此时指向的数据⼀定不大于keyi
问题2:为什么
left
和right
指定的数据和keyi
值相等时也要交换?
相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。