目录
你好
1.快速排序
1.1前后指针法
设置两个指针,prev与cur
cur找小:
cur
从left + 1
遍历到right
。如果a[cur]
小于a[keyi]
,并且prev
和cur
不相等(即prev
还未更新到cur
的位置),就将a[prev]
和a[cur]
交换,将所有小于keyi的值交换到数组前面部分
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev!=cur)//cur找小
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
将
a[keyi]
与a[prev]
交换,使基准值位于其最终的位置。最终返回prev
,即基准值的索引,用于在快速排序中进一步递归排序左右子数组。
Swap(&a[prev], &a[keyi]);
return prev;
完整代码:
//快速排序 前后指针
int PartSort2(int* a, int left, int right)
{
//三数取中
int midi = GetMidi(a, left, right);
Swap(&a[left], &a[midi]);
//取keyi,左边取keyi,右边先走;右边取keyi,左边先走,保证begin与end相遇位置比keyi小
int keyi = left;
int prev = left;
int cur = prev + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev!=cur)//cur找小
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
return prev;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int keyi = PartSort2(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
1.2非递归实现快速排序
使用栈模拟递归的过程,保持每个子区间的处理状态。
核心操作是分区,将数组分为左右两个子区间,并处理这些子区间。
非递归实现避免了递归深度过大可能导致的问题,同时保持了快速排序的基本效率。
调用
PartSort2
函数对数组a
中的区间[begin, end]
进行分区操作,并返回基准值的位置keyi
。基准值左边的元素都小于它,右边的元素都大于它。如果
keyi + 1
小于end
,说明右子区间[keyi + 1, end]
需要处理。将右子区间的begin
和end
入栈如果
begin
小于keyi - 1
,说明左子区间[begin, keyi - 1]
需要处理。将左子区间的begin
和end
入栈
int keyi = PartSort2(a, begin, end);
//分为左右两个区间
//[begin,key-1] keyi [key+1,end]
if (keyi + 1 < end)//右区间入栈,两个数据以上入栈
{
STpush(&st, end);
STpush(&st, keyi + 1);
}
if (begin < keyi - 1)//左区间入栈
{
STpush(&st,keyi - 1);
STpush(&st, begin);
}
利用栈保存待处理的子区间,依次处理这些子区间,确保每个子区间都得到排序,能避免深递归导致的栈溢出问题
完整代码:
//非递归
void QuickSortNonR(int* a, int left, int right)
{
ST st;
STInit(&st);
//头与尾入栈
STpush(&st, right);
STpush(&st, left);
while (!STEmpty(&st))
{
int begin = STTop(&st);
STPop(&st);
int end = STTop(&st);
STPop(&st);
int keyi = PartSort2(a, begin, end);
//分为左右两个区间
//[begin,key-1] keyi [key+1,end]
if (keyi + 1 < end)//右区间入栈,两个数据以上入栈
{
STpush(&st, end);
STpush(&st, keyi + 1);
}
if (begin < keyi - 1)//左区间入栈
{
STpush(&st,keyi - 1);
STpush(&st, begin);
}
}
STDestroy(&st);
}
2.归并排序
2.1.递归
时间复杂度是 O(n log n)
,空间复杂度是 O(n)
归并排序是一种分治法(Divide and Conquer)的排序算法,其主要步骤是将数组分成两半,递归地对每一半进行排序,然后将两个有序的半数组合并成一个有序数组
计算中间位置
mid
,将当前子数组分成两个子数组[begin, mid]
和[mid + 1, end]
。
int mid = (begin + end) / 2;
分别递归地对两个子数组进行排序。
_MergeSort(a, tmp, begin, mid);
_MergeSort(a, tmp, mid + 1, end);
归并
- 使用两个指针
begin1
和begin2
分别遍历左右子数组。- 比较两个子数组中的元素,将较小的元素放入临时数组
tmp
中。- 处理完两个子数组中较小的一部分后,将剩余的部分(如果有)直接拷贝到
tmp
中。
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
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++];
}
最后把tmp数组拷贝回原数组a中相应位置即可
memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
完整·代码
void _MergeSort(int* a, int* tmp, int begin, int end)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
_MergeSort(a, tmp, begin, mid);
_MergeSort(a, tmp, mid + 1, end);
//归并
int begin1 = begin, end1 = mid;
int begin2 = mid+1, end2 = end;
int i = begin;
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, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, tmp, 0, n - 1);
free(tmp);
tmp = NULL;
}
2.2非递归
定义一个gap
控制每组子数组的大小,开始时为1,随后每次归并后翻倍。
for
循环每次归并相邻的两个子数组,大小为 gap
。
void MergeSortNonR(int* a, int n)
{
// 分配一个临时数组,用于存储归并过程中间结果
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
// gap表示每组归并数据的大小,初始值为1
int gap = 1;
while (gap < n)
{
// 遍历数组,按每个gap大小进行归并
for (int i = 0; i < n; i += 2 * gap)
{
// 设置第一个子数组的起始和结束索引
int begin1 = i, end1 = i + gap - 1;
// 设置第二个子数组的起始和结束索引
int begin2 = i + gap, end2 = i + 2 * gap - 1;
// 打印当前正在归并的两个子数组的索引范围
printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
// 如果第二个子数组的起始索引超出数组范围,跳出循环
if (begin2 >= n)
break;
// 如果第二个子数组的结束索引超出数组范围,修正结束索引
if (end2 >= n)
end2 = n - 1;
// 用于临时存储合并后的数据的索引
int j = i;
// 合并两个子数组
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
// 如果第一个子数组还有剩余,将其复制到临时数组
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
// 如果第二个子数组还有剩余,将其复制到临时数组
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
// 将临时数组中的合并结果复制回原数组的对应位置
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
// 打印一行,以便观察归并过程
printf("\n");
// 每次归并后,gap翻倍,处理更大的块
gap *= 2;
}
// 释放临时数组的内存
free(tmp);
tmp = NULL;
}
3.计数排序
计数排序是一种非比较排序算法,适用于范围较小的整数数据
// 确定数组中的最小值和最大值
int min = a[0], max = a[0];
for (int i = 1; i < n; i++)
{
// 更新最小值
if (a[i] < min)
min = a[i];
// 更新最大值
if (a[i] > max)
max = a[i];
}
// 计算值的范围
int range = max - min + 1;
循环遍历数组,找到最小值
min
和最大值max
,以确定排序范围计算值的范围:
range
是最大值和最小值之间的范围加1,用于分配计数数组的大小。
// 统计每个值的出现次数
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
统计每个值的出现次数,遍历数组
a
,根据每个元素值更新count
数组中对应索引的位置,出现一次count++
注:
数组
a
中的值可能是任意整数,并且可能不是从0开始。例如,如果a
的最小值是3
,最大值是7
,那么a
中的值可能是3
、4
、5
、6
或7
我们希望将这些值映射到
count
数组的索引中。为了使count
数组能够覆盖所有可能的值,我们需要将a
中的每个值映射到从0开始的索引
- 由于
count
数组的索引从0开始,我们需要将a
中的每个值a[i]
映射到一个非负的索引。通过减去最小值min
,我们将a
中的值调整到从0开始的范围。- 例如,如果
min
是3
,则count[a[i] - 3]
会将值3
映射到count[0]
,值4
映射到count[1]
,以此类推。这确保了count
数组的索引是从0开始的,并且能够准确记录每个值的出现次数。
// 将排序后的值放回原数组
int j = 0;
for (int i = 0; i < range; i++)
{
// 将计数数组中每个值放入原数组
while (count[i]--)
{
a[j++] = i + min;
}
}
最后排序好放回a数组
完整代码:
#include <stdio.h>
#include <stdlib.h>
void CountSort(int* a, int n)
{
// 确定数组中的最小值和最大值
int min = a[0], max = a[0];
for (int i = 1; i < n; i++)
{
// 更新最小值
if (a[i] < min)
min = a[i];
// 更新最大值
if (a[i] > max)
max = a[i];
}
// 计算值的范围
int range = max - min + 1;
// 分配计数数组并初始化
int* count = (int*)calloc(range, sizeof(int));
if (count == NULL)
{
// 分配失败时打印错误并退出函数
perror("calloc fail");
return;
}
// 统计每个值的出现次数
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
// 将排序后的值放回原数组
int j = 0;
for (int i = 0; i < range; i++)
{
// 将计数数组中每个值放入原数组
while (count[i]--)
{
a[j++] = i + min;
}
}
// 释放计数数组的内存
free(count);
}
感谢,再见