想要了解冒泡排序、选择排序、希尔排序等算法的,请移步常用排序算法汇总(一)
归并排序
- 平均时间复杂度:O(nlog2n),分治策略,有一个二分的过程;
- 最优时间复杂度:O(nlog2n);
- 稳定性:稳定,相邻元素比较,相对位置不变;
- 应用场景:大规模排序;
// 合并2个子序列
void MergeBranch(int A[], int left, int mid, int right)
{
int len = right - left + 1;
int *tempArr = new int[len];
int i = left;
int j = mid + 1;
int k = 0; // 临时元素下标
while (i <= mid || j <= right)
{
if (i <= mid && j <= right)
{
// 添加较小的元素
tempArr[k++] = A[i] <= A[j] ? A[i++] : A[j++];
}
else if (i <= mid)
{
// 左边元素未添加完
tempArr[k++] = A[i++];
}
else
{
// 右边元素未添加完
tempArr[k++] = A[j++];
}
}
// 拷贝到原数组
memcpy(A + left, tempArr, len*sizeof(int));
delete[] tempArr;
}
void MergeSort(int A[], int left, int right)
{
if (left >= right)
{
// 不需要继续划分
return;
}
int mid = (left + right) / 2;
MergeSort(A, left, mid);
MergeSort(A, mid + 1, right);
// 合并子序列
MergeBranch(A, left, mid, right);
}
堆排序
- 平均时间复杂度:O(nlog2n),构造二叉树逻辑结构;
- 最优时间复杂度:O(nlog2n);
- 稳定性:不稳定,非相邻元素比较,相对位置可能改变;
- 应用场景:大规模排序;
// 调整堆:沿某一条调整线路,自顶向下调整
void AdjustHeap(int A[], int topIndex, int size)
{
int left = topIndex * 2 + 1; // 左子树
int right = topIndex * 2 + 2; // 右子树
int maxIndex = topIndex;
if (left < size && A[left] > A[maxIndex])
{
maxIndex = left;
}
if (right < size && A[right] > A[maxIndex])
{
maxIndex = right;
}
if (maxIndex != topIndex)
{
// 做一次有效交换
A[maxIndex] ^= A[topIndex] ^= A[maxIndex] ^= A[topIndex];
AdjustHeap(A, maxIndex, size);
}
}
void HeapSort(int A[], int size)
{
// 建堆时,从非叶节点开始,自底向上整理,使局部堆有序
for (int i = size / 2 - 1; i >= 0; i--)
{
AdjustHeap(A, i, size);
}
while (size > 1)
{
// 交换顶部元素,置于最底部
A[0] ^= A[size - 1] ^= A[0] ^= A[size - 1];
AdjustHeap(A, 0, --size);
}
}
堆排序主要分2步:(1)创建堆,使每个子堆局部有序;(2)调整堆,沿某一条线路调整,找出一个最大元素;
快速排序
- 平均时间复杂度:O(nlog2n),分治策略,有一个二分的过程;
- 最优时间复杂度:O(nlog2n);
- 稳定性:不稳定,非相邻元素比较,相对位置可能改变;
- 应用场景:大规模排序,通常比其他O(nlogn)算法更快;
int BinaryPart(int a[], int left, int right)
{
int baseElem = a[right]; // 将最后一个元素,作为基准对象
for (int i = left; i < right; i++)
{
if (a[i] < baseElem)
{
if (i != left)
{
// 小于基准值的,统一挪至左边
a[left] ^= a[i] ^= a[left] ^= a[i];
}
left++;
}
}
// 交换元素,将基准元素放到新的基准位置
a[left] ^= a[right] ^= a[left] ^= a[right];
return left;
}
void QuickSort(int a[], int left, int right)
{
if (left >= right)
{
return;
}
int basePos = BinaryPart(a, left, right);
QuickSort(a, left, basePos - 1);
QuickSort(a, basePos + 1, right);
}
BinaryPart
函数实现相对有点绕,目的避免开辟额外存储空间
计数排序
- 平均时间复杂度:O(n+k),创建一个规模为k的数组计数;
- 最优时间复杂度:O(n+k);
- 稳定性:稳定,不存在元素交换;
- 应用场景:大规模排序,元素数值相差不大;
void CountingSort(int A[], int size)
{
// 计算基数
int maxVal = -1;
int minVal = 1 << 30;
for (int i = 0; i < size; i++)
{
if (A[i] > maxVal) maxVal = A[i];
if (A[i] < minVal) minVal = A[i];
}
int baseN = maxVal - minVal + 1;
int *countArr = new int[baseN];
memset(countArr, 0, baseN*sizeof(int));
// 计数
for (int i = 0; i < size; i++)
{
countArr[A[i] - minVal]++;
}
for (int i = 1; i < baseN; i++)
{
countArr[i] = countArr[i] + countArr[i - 1];
}
// 存储新的排序结果
int *B = new int[size];
for (int i = size - 1; i >= 0; i--) // 从后往前排序,保证相对有序
{
B[--countArr[A[i] - minVal]] = A[i];
}
memcpy(A, B, size*sizeof(int));
// 释放内存
delete[] countArr;
delete[] B;
}
基数排序
- 平均时间复杂度:O(n*dn),dn为元素的位数或字符串长度;
- 最优时间复杂度:O(n*dn);
- 稳定性:稳定,不存在元素交换;
- 应用场景:大规模排序,元素结构可拆分为小规模计数排序;
// 获取数字的指定位
int GetDigit(int num, int iBit)
{
int iRet = 0;
while (iBit--)
{
iRet = num % 10;
num /= 10;
}
return iRet;
}
// 按“数字的位”排序
void BitSort(int A[], int size, int iBit)
{
int countArr[10] = { 0 };
for (int i = 0; i < size; i++)
{
countArr[GetDigit(A[i], iBit)]++;
}
for (int i = 1; i < 10; i++)
{
countArr[i] = countArr[i] + countArr[i - 1];
}
int *B = new int[size];
for (int i = size - 1; i >= 0; i--)
{
B[--countArr[GetDigit(A[i], iBit)]] = A[i];
}
memcpy(A, B, size*sizeof(int));
delete[] B;
}
void RadixSort(int A[], int size)
{
// 计算一下,最大数的位数
int maxVal = -1;
int bitNum = 0;
for (int i = 0; i < size; i++)
{
if (A[i] > maxVal) maxVal = A[i];
}
do
{
maxVal /= 10;
bitNum++;
} while(maxVal);
// 按位排序(低->高)
for (int i = 1; i <= bitNum; i++)
{
BitSort(A, size, i);
}
}
桶排序
- 平均时间复杂度:O(n),主要取决于桶的映射函数和桶内元素均匀程度;
- 最优时间复杂度:O(n),每个桶内只有一个元素;
- 稳定性:稳定,不存在元素交换;
- 应用场景:大规模排序,元素可通过某种映射关系均匀划分;
int *countArr = NULL;
// 分桶
int MapToBarrel(int elemVal)
{
return elemVal / 10;
}
// 外部排序(桶之间)
void BarrelOutsideSort(int A[], int size, int bn)
{
countArr = new int[bn];
memset(countArr, 0, bn*sizeof(int));
// 计数
for (int i = 0; i < size; i++)
{
int barrelIndex = MapToBarrel(A[i]);
countArr[barrelIndex]++;
}
for (int i = 1; i < bn; i++)
{
countArr[i] = countArr[i] + countArr[i - 1];
}
// 存储新的排序结果
int *B = new int[size];
for (int i = size - 1; i >= 0; i--) // 从后往前排序,保证相对有序
{
int barrelIndex = MapToBarrel(A[i]);
B[--countArr[barrelIndex]] = A[i];
}
memcpy(A, B, size*sizeof(int));
// 释放内存
delete[] B;
}
void BarrelSort(int A[], int size)
{
int maxVal = -1;
for (int i = 0; i < size; i++)
{
if (A[i] > maxVal) maxVal = A[i];
}
// 外部排序
int bn = maxVal / 10 + 1; // 桶的数量
BarrelOutsideSort(A, size, bn);
// 桶内排序
for (int i = 0; i < bn; i++)
{
int left = countArr[i]; // countArr[i]为i号桶第一个元素的位置
int right = (i == bn - 1 ? size - 1 : countArr[i + 1] - 1);// countArr[i+1]-1为i号桶最后一个元素的位置
QuickSort(A, left, right);
}
delete[] countArr;
}