文章目录
前面我们讲了插入排序的两种,接下来我们来学习其他几种排序算法吧
1. 选择排序
- 选择排序的思想:每次从待排序的数据中选取最小(最大)的元素,然后存放在序列的起始位置,直到数据全部排完序
- 选择排序分为两种:直接选择排序、堆排序
1.1 直接选择排序
- 直接选择排序的思想:首先选取待排序集合的最大(最小)的数据元素,如果最大的元素不是序列的最后一个位置,则让它与最后的元素交换;同样,最小的元素如果不再数据的起始位置,那么让它与第一个元素交换,之后继续重复上述操作待排序的数据,直到排好序
下面我将用图示的方式来帮助大家理解
- 以上是我们对特殊情况的分析,接下来我们还是继第三步继续分析
- 接下来我们就看下代码是如何实现的吧
//直接选择排序
void SelectSort(int* arr, int sz)
{
int begin = 0;
int end = sz - 1;
while (begin < end)
{
int max = begin;
int min = begin;
//寻找最大、最小元素
for (int i = begin + 1; i <= end; i++)//由于max和min都在首元素,这里直接从begin+1开始找就行
{
//寻找最大元素
if (arr[i] > arr[max])
{
max = i;
}
//寻找最小元素
if (arr[i] < arr[min])
{
min = i;
}
}
//特殊情况处理(begin == max)
if (begin == max)
{
max = min;
}
//交换
if (min != begin)
{
Swap(&arr[min], &arr[begin]);
}
if (max != end)
{
Swap(&arr[max], &arr[end]);
}
begin++;
end--;
}
}
- 直接选择排序的时间复杂度是O(n^2),空间复杂度是O(1)
1.2 堆排序
- 前面我们讲二叉树的时候就已经讲过堆了,这里只带大家简单熟悉一下,需要深入了解的小伙伴可点击下面的链接
- 链接: 【数据结构与算法】第8课—数据结构之二叉树(堆)
- 总体来讲,堆排序的时间复杂度是O(nlogn),它是一个比较好的排序算法
- 接下来附上堆排序的代码
//交换
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向下调整算法
void AdjustDown(int* arr, int parent, int n)
{
int child = parent * 2 + 1;//左孩子节点
while (child < n)
{
//先找最小的孩子
if (child + 1 < n && arr[child] < arr[child + 1])
{
child++;
}
//交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* arr, int sz)
{
//向下调整算法建堆
for (int i = (sz - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, i, sz);
}
//堆排序
int end = sz - 1;
while (end)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
2. 交换排序
- 交换排序的思想:就是根据序列交换数据的位置
- 交换排序的特点:数据大(小)的值向尾部移动,而数据小(大)的值向头部移动
- 交换排序也分为两种:冒泡排序和快速排序
2.1 冒泡排序
学到这里我们对冒泡排序应该很熟悉了,尽管它是一个很差的排序算法,它的时间复杂度是O(N^2),如果还有小伙伴对冒泡排序不熟悉的,可点击下面的链接,不废话,直接上代码
链接: C语言进阶版第8课—指针(2)
//冒泡排序
void BubbleSort(int* arr, int sz)
{
for (int i = 0; i < sz; i++)
{
int flag = 0;
for (int j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
flag = 1;
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
if (flag == 0)
break;
}
}
2.2 快速排序(找基准值法1----Hoare版本)
- 快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两字序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为⽌
- Hoare提出的快速排序其实可以看成二叉树递归,首先定义一个基准值,然后调整基准值位置,再根据基准值二分数组,将划分的每个子树重新定义一个基准值,再调整新的基准值位置,然后根据新的基准值再二分新的数组,直到最后只剩一个元素时结束递归,这时快速排序就完成了
2.2.1 特殊场景1
2.2.2 特殊场景2
- 最特殊的场景
2.2.3 代码
- 接下来看看代码是如何实现的
//找基准值
int _QuickSort(int* arr, int left, int right)
{
int key = left;
left++;
while (left <= right)
{
//right:从右向左找比基准值小的数
while (left <= right && arr[right] > arr[key])
{
right--;
}
//left:从左向右找比基准值大的数
while (left <= right && arr[left] < arr[key])
{
left++;
}
//left和right交换
if (left <= right)
{
Swap(&arr[left], &arr[right]);
left++;
right--;
}
}
//此时left>right,且arr[right]<arr[key],arr[left]>arr[key]
//交换arr[key]和arr[right]
Swap(&arr[key], &arr[right]);
//此时right就是基准值校准后的下标
return right;
}
//快速排序
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
//找基准值
int key = _QuickSort(arr, left, right);
//二分
QuickSort(arr, left, key - 1);//左子树
QuickSort(arr, key + 1, right);//右子树
}
2.2.4 快速排序的时间复杂度
2.3 快速排序(找基准值法2—挖坑法)
- 创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准⼤的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)
2.3.1 特殊情况1处理
2.3.2 特殊情况2处理
- 代码实现
//找基准值法2---挖坑
int _QuickSort2(int* arr, int left, int right)
{
int key = arr[left];//先定义基准值
int hole = left;
while (left < right)
{
//right向左找比基准值小的数
while (left < right && arr[right] >= key)
{
right--;
}
//找到后
arr[hole] = arr[right];
hole = right;
//left向右找比基准值大的数
while (left < right && arr[left] <= key)
{
left++;
}
//找到后
arr[hole] = arr[left];
hole = left;
}
//此时left=right,将基准值放入坑位
arr[hole] = key;
return hole;
}
//快速排序
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
//找基准值
int key = _QuickSort2(arr, left, right);
//二分
QuickSort(arr, left, key - 1);//左子树
QuickSort(arr, key + 1, right);//右子树
}
2.4 快速排序(找基准值法3-----lomuto(双指针法))
- 双指针法法:定义两个指针prev和cur,cur指针用来探路,找比基准值要小的数
- 两种情况:
- cur未找到比基准值小的数据:cur++
- cur找到了比基准值小的数据:prev++,然后prev和cur位置的数据互换,cur再++找比基准值小的数,直到cur越界都未找到,那么交换prev位置的数据和基准值
- 代码实现
//找基准值法3---双指针法
int _QuickSort3(int* arr, int left, int right)
{
int key = left;//基准值
int prev = left;
int cur = left + 1;//cur指针用来探路找比基准值小的数
while (cur <= right)
{
//cur找到比基准值小的数
if (cur <= right && arr[cur] < arr[key])
{
prev++;
if (prev != cur)
{
Swap(&arr[prev], &arr[cur]);
}
}
//cur未找到比基准值小的数,不论找没找到,cur都得++
cur++;
}
//cur越界,交换arr[prev]和基准值
Swap(&arr[prev], &arr[key]);
return prev;//prev为校准后的基准值下标
}
//快速排序
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
//找基准值
int key = _QuickSort3(arr, left, right);
//二分
QuickSort(arr, left, key - 1);//左子树
QuickSort(arr, key + 1, right);//右子树
}
2.5 快速排序(非递归版本)
- 非递归版本的思路:需要借助数据结构——栈,首先将数组的尾元素下标入栈,再让数组的首元素的下标入栈,保证栈不为空,然后取栈顶元素,先后将栈顶元素赋给begin和end,然后利用双指针法找基准值,找到基准值后将数组二分,然后先让右子树入栈,再让左子树入栈,入栈依然是尾元素下标优先,且入栈的都是子树的首尾元素下标,然后再取栈顶元素分别赋给begin和end,然后双指针找基准值,二分数组,入栈,取栈顶元素,以此往复,直到栈为空,排序完成
- 接下来我将用图示来帮助大家理解
- 代码实现
//快速排序---非递归版本
void QuickSortNone(int* arr, int left, int right)
{
Stack st;//定义栈的变量
StackInit(&st);//初始化
//入栈
StackPush(&st, right);
StackPush(&st, left);
//栈不为空
while (!StackEmpty(&st))
{
//取栈顶元素
int begin = StackTop(&st);
StackPop(&st);//取出一个销毁一个
int end = StackTop(&st);
StackPop(&st);
//找基准值---双指针法
int key = begin;
int prev = begin;
int cur = prev + 1;//探路,找比基准值小的数
while (cur <= end)
{
//找比基准值小的数
if (arr[cur] < arr[key])
{
prev++;//找到了,prev先往后走
if (cur != prev)
{
Swap(&arr[cur], &arr[prev]);
}
}
//未找到/找到cur都得++
cur++;
}
//cur越界,交换arr[prev]和基准值,此时prev就是校准后的基准值位置
Swap(&arr[prev], &arr[key]);
key = prev;
//右子树先入栈
if (key + 1 < end)
{
StackPush(&st, end);//右子树尾元素下标
StackPush(&st, key + 1);//右子树首元素下标
}
//左子树入栈
if (begin < key-1)
{
StackPush(&st, key - 1);//左子树尾元素下标
StackPush(&st, begin);//左子树首元素下标
}
}
StackDestroy(&st);//销毁栈
}