排序的分类
排序:指按照一定的顺序排列一组数据元素的过程,以便对数据进行信息管理以及数据分析。(以下都通过升序进行解释)
1. 冒泡排序
将数组头部元素与相邻元素进行比较,若前者大于后者,则交换位置,直至末尾;一轮比较后,最大的元素就置于末尾,接下来,再将除去最大元素的子数组重复上述操作,直至数组有序。
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++)
{
bool exchange = true;
for (int i = 1; i < n-j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = false;
}
}
if (exchange == true)
break;
}
}
细节:在单趟比较中,给i 赋值1,是为了在最后一次比较中,数组越界;使用布尔类型进行逻辑判断,是为了在数组有序是,无需比较。
时间复杂度最好情况:O(n)
时间复杂度最坏情况:O(n2)
稳定性:好(在元素大小相同时,不需要交换)
2、插入排序
基本思想:
- 首先将数组分为已排序和未排序两部分。初始时,已排序部分只有第一个元素,未排序部分包含其余元素。
- 从未排序部分中取出第一个元素,与已排序部分的所有元素进行比较,找到合适的位置插入,使得已排序部分始终维持有序。
- 继续从未排序部分取出元素,执行上述插入操作,直到未排序部分为空,整个数组有序。
void InserSort(int* a, int n)
{
// 遍历数组,从第二个元素开始,因为只有一个元素时默认已排序
for (int i = 1; i < n; ++i)
{
// 将当前元素作为插入的目标,存储为临时变量tmp
int tmp = a[i];
int end = i-1; // end表示已排序部分的最后一个元素的索引
// 在已排序部分中找到tmp的插入位置
while (end >= 0)
{
// 从后往前遍历已排序部分
if (tmp < a[end]) { // 当前元素比tmp大,需要向后移动
a[end + 1] = a[end]; // 将较大的元素向后移动一位
end--; // 继续向前比较
} else
{
break; // 找到合适位置,退出循环
}
}
// 将tmp插入到正确的位置
a[end + 1] = tmp;
}
}
3、希尔排序
希尔排序是一种基于插入排序的排序算法。它是插入排序的一种优化版本,通过分组插入排序和缩小增量的方式,大幅度减少了逆序对的数量,从而提高了排序效率。
- 希尔排序将数组分成若干个子序列,每个子序列通过插入排序进行排序。
- 初始步长为数组长度的一半,每次循环后步长减半,直到步长为1(当gap为1是,就是插入排序)。
- 对每个步长,从索引 gap 开始,将每个元素插入到当前分组的正确位置。
- 通过内部循环,逐步将分组中较大的元素向后移动,为当前元素腾出插入位置。
void ShellSort(int* a, int n)
{
int gap = n / 2; // 初始化步长,通常从数组长度的一半开始
while (gap >= 1)
{
gap /= 2; // 每次循环将步长减半,直到步长为1
for (int j = 0; j < gap; j++)
{
// 处理每个分组,每个分组的起始位置为j,步长为gap
for (int i = j; i < n - gap; i += gap)
{
int end = i; // 当前已排序分组的末尾索引
int tmp = a[i + gap]; // 需要插入的元素
while (end >= 0)
{
// 将较大的元素向后移动,为tmp腾出位置
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap; // 继续向前比较
} else
{
break; // 找到合适位置,退出循环
}
}
a[end + gap] = tmp; // 将tmp插入到正确的位置
}
}
}
}
- 插入排序的平均和最坏时间复杂杂度为O(n2)
- 希尔排序的平均时间复杂度为O(n3/2)
- 在插入排序中,如遇到相同元素,会插入到其后面,保证了相对顺序的不变,故插入排序是稳定的。
- 在希尔排序中,元素被分组并跳跃式比较与交换,相等元素的相对位置可能会被改变,故希尔排序是不稳定的。
4、快速排序
快速排序是一种基于分治法的排序算法。从数组中选取一个基准值,通过一趟排序,将其分为独立的两部分,左边数组小于基准值,右边数组大于基准值,再进行递归处理。
思路:
- 首先找到一个基准值,将其置于数组最左侧(也可以至于最右侧)
- 从数组的尾部开始寻找小于基准值的数(从右向左),找到后,再从数组的头部寻找大于基准值的数(从左向右)
- 二者都找到后,在交换它们的值,直至相遇,相遇的值一定比基准值小,再将其与基准值交换
- 递归其子数组
若想排序变得更加有效率,则需要获得一个合适的基准值,可以在头部,尾部和中部,选取一个中间值作为基准值
// 三数取中函数
int GetMidNumi(int* a, int left, int right) {
int mid = (left + right) / 2;
if (a[mid] > a[left])
{
if (a[right] > a[mid])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
return right;
}
else//a[mid] <= a[left]
{
if (a[right] < a[mid])
{
return mid;
}
else if (a[right] > a[left])
{
return left;
}
else
return right;
}
}
// 交换函数
void Swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 快速排序函数
void QuickSort(int* a, int left, int right) {
if (left >= right) {
return;
}
int begin = left, end = right;
//三数取中
int midi = GetMidNumi(a, left, right);// 获取基准值索引
Swap(&a[midi], &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]);
keyi = left;
// 递归排序左右子数组
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi+1, end);
}
(挖坑法)
- 选择基准值:选择数组中的一个元素作为基准值
- 从右向左找到第一个小于基准值的元素,将其放入左边的“坑”中。
- 从左向右找到第一个大于基准值的元素,将其放入右边的“坑”中。
- 重复上述步骤,直到 left 和 right 相遇或交错。
- 放置基准值:将基准值放入最终的“坑”中,此时基准值已经归位
void QuickSort(int* a, int left, int right)//挖坑法
{
if (left >= right)
{
return;
}
int begin = left, end = right;
int midi = GetMidNumi(a, left, right);
Swap(&a[midi], &a[left]);
int key = a[left];
int hole = left;
while (left < right)
{
while (left < right && a[right] >= key)
right--;
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
left++;
a[hole] = a[left];
hole = left;
}
a[hole] = key;
QuickSort(a, begin,hole-1);
QuickSort( a, hole+1, end);
}
(前后指针法)
- 选择基准值: 一般取序列的中间元素。
- 若cur找到比key小的值,++prev;当cur的值和prev的值不相等时,则交换它们的值
- 继续++cur
- 若cur的值比key大,则++cur
1、prev要么紧跟着cur
2、prev和cur之间隔着一段比key大的区间值
//前后指针法
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left, end = right;
int midi = GetMidNumi(a, left, right);
Swap(&a[midi], &a[left]);
int key = a[left];
int keyi = left;
int prve = left, cur = left + 1;
while (cur <= right)
{
if (a[cur] < key && ++prve != cur)
Swap(&a[cur], &a[prve]);
++cur;
}
Swap(&a[prve], &a[left]);
keyi = prve;
QuickSort(a,begin,keyi-1);
QuickSort(a,keyi+1,end);
}
QuickSort非递归的写法
5、归并排序
归并排序也是一种基于分治法的排序算法。将待排序数组分为若干小数组,直至小数组只有一个元素,再递归的对子数组进行排序,将相邻的两个有序子数组合并为一个新的有序数组。合并时,通过双指针比较元素,依次选择较小者放入临时数组,剩余元素直接追加。
// 归并排序辅助函数
void _Merge(int* a, int begin, int end, int* tmp)
{
// 如果子数组只有一个元素或空,直接返回
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2; // 计算中间点
_Merge(a, begin, mid, tmp); // 递归排序左半部分
_Merge(a, mid + 1, end, tmp); // 递归排序右半部分
int begin1 = begin; // 左子数组的起始索引
int end1 = mid; // 左子数组的结束索引
int begin2 = mid + 1; // 右子数组的起始索引
int end2 = end; // 右子数组的结束索引
int i = begin1; // 临时数组的索引
// 合并两个有序子数组到临时数组
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++]; // 左子数组的元素较小,放入临时数组
}
else
{
tmp[i++] = a[begin2++]; // 右子数组的元素较小,放入临时数组
}
}
// 将左子数组剩余元素拷贝到临时数组
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
// 将右子数组剩余元素拷贝到临时数组
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 将临时数组中的有序元素拷贝回原数组
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
// 归并排序主函数
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n); // 分配临时数组
if (tmp == NULL) {
perror("malloc fail"); // 内存分配失败
return;
}
_Merge(a, 0, n - 1, tmp); // 调用归并排序辅助函数
free(tmp); // 释放临时数组
}
归并排序非递归的写法
6、选择排序
- 从数组的第一个元素开始,找到整个数组中的最小值,并将其与数组的第一个元素交换位置。
- 从数组的第二个元素开始,找到剩余未排序元素中的最小值,并将其与数组的第二个元素交换位置。
- 不断重复上述过程,每次从当前未排序的部分选择最小元素,并将其交换到已排序部分的末尾,直到整个数组排序完成。
// 交换函数,用于交换两个整数的值
void swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 选择排序函数
void selectsort(int* a, int n) {
int left = 0; // "left" 表示当前已排序部分的末尾索引
int mini = left; // "mini" 用于存储最小值的索引
// 遍历数组,每次确定一个元素的位置
while (left < n)
{
// 在未排序的部分中寻找最小值的索引
for (int i = left + 1; i < n; i++)
{
if (a[i] < a[mini])
{
// 如果找到更小的值,更新最小值的索引
mini = i;
}
}
// 将找到的最小值与已排序部分末尾的元素交换
swap(&a[mini], &a[left]);
left++;
}
}
7、堆排序
详情请点击该链接:堆排序