一 比较类排序
通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlog2n),因此也称为非线性时间比较类排序。
快排
优点:快排总体的平均效率是最好的。平均时间复杂度为O(nlog2n),空间复杂度为O(nlog2n)
缺点:当数组本身已经排序好了,而每一轮排序都以最后一个数字作为比较标准,此时快排效率只有O(n2)
#include <iostream>
#include <random>
// 实现快排的关键在于先在数组中取一个数字,接下来把数组中的数字划分。
// 比选择的数字小的数字移动到数组的左边,比选择的数字大的数字移动到数组的右边。
int Partition(int data[], int length, int start, int end)
{
// 检查输入数据
if(data == nullptr || length <= 0 || start < 0 || end >=length)
throw new std::invalid_argument("Invalid Parameters");
// 使用标准库的随机引擎生成随机下标
std::default_random_engine e;
std::uniform_int_distribution<unsigned> u(start,end);
int index = u(e);
// 将选择的数字移动到数据末尾,方便比较
std::swap(data[index], data[end]);
// small记录上一个比选择的数字要小的数字的下标
int small = start - 1;
for (index = start; index < end; ++index) {
if(data[index] < data[end])
{
++small;
if (small != index)
std::swap(data[index],data[small]);
}
}
++small;
std::swap(data[small],data[end]);
// 返回选择的数字的下标
return small;
}
// 递归思路分别对每次选中的数字的左右两边进行排序。
void QuickSort(int data[], int length, int start, int end)
{
if (start == end)
return;
int index = Partition(data, length, start, end);
if (index > start)
QuickSort(data, length, start, end);
if (index < end)
QuickSort(data, length, index+1, end);
}
int main(int,char**)
{
// 生成随机数组检测
int arr[10];
std::default_random_engine e;
std::uniform_int_distribution<unsigned> u(0,10);
for (int i = 0; i < 10; ++i) {
arr[i] = u(e);
std::cout << arr[i] << " ";
}
std::cout << std::endl;
QuickSort(arr,10,0,9);
for (int i = 0; i < 10; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
插入排序
平均、最坏时间复杂度:O(n2)
最好时间复杂度:O(n)
空间复杂度:O(1)
void insertSort(int arr[], int length)
{
if(arr == nullptr || length <= 0)
return;
// current:要插入的值
// preIndex:插入点
int current;
int preIndex;
for (int i = 1; i < length; ++i) {
current = arr[i];
preIndex = i - 1;
while (current < arr[preIndex] && preIndex >= 0)
{
arr[preIndex+1] = arr[preIndex];
--preIndex;
}
arr[preIndex+1] = current;
}
}
希尔排序
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行插入排序(步长一般是n/2)。然后会继续以一半的步长(n/4)进行插入排序,最终算法以步长为1进行插入排序。
Donald Shell最初建议步长选择为 n/2 并且对步长取半直到步长达到1。
已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,…)。
ShellSort的复杂度分析非常复杂,不同gap序列的设计对应不同的复杂度。
时间复杂度:根据步长序列的不同而不同。
最好时间复杂度:O(nlog2n)
空间复杂度:O(1)
原理:(竖着看 10 > 13 > 25 > 45)
void shellSort(int arr[], int length)
{
if(arr == nullptr || length <= 0)
return;
// gap:步长
// current:要插入的值
// preIndex:插入点
for (int gap = length / 2; gap > 0; gap /= 2) {
// 插入排序
for (int i = gap; i < length; i += gap) {
int current = arr[i];
int preIndex = i - gap;
while (preIndex >= 0 && arr[preIndex] > current)
{
arr[preIndex + gap] = arr[preIndex];
preIndex -= gap;
}
arr[preIndex + gap] = current;
}
}
}
堆排序
堆排序的基本思想是:将待排序序列构造成一个最大堆,此时,整个序列的最大值就是二叉树的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
最坏,最好,平均时间复杂度:O(nlog2n)
空间复杂度:O(1)
// len:堆的大小
int len;
// 维护最大堆性质
void heapify(int arr[], int i)
{
// left:左结点
// right:右结点
// largest:最大值下标
int left = i * 2 + 1;
int right = i * 2 + 2;
int largest = i;
if (left < len && arr[left] > arr[largest])
{
largest = left;
}
if (right < len && arr[right] > arr[largest])
{
largest = right;
}
// 当根结点变化时,递归维护子树的最大堆性质
if (largest != i)
{
std::swap(arr[i],arr[largest]);
heapify(arr,largest);
}
}
// 用数组构建最大堆
void buildMaxHeadp(int arr[], int length)
{
len = length;
for (int i = len / 2; i >= 0; --i) {
heapify(arr, i);
}
}
// 堆排序
void heapSort(int arr[], int length)
{
if (arr == nullptr || length <= 0)
return;
buildMaxHeadp(arr, length);
// 每次取最大堆中的root结点,放到数组尾部,并重新维护最大堆性质。
for (int i = length - 1; i > 0; --i) {
std::swap(arr[0], arr[i]);
--len;
heapify(arr, 0);
}
}
归并排序
把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
最坏,最好,平均时间复杂度:O(nlog2n)
空间复杂度:O(n)
// 归并
// arr:原数组 res:结果数组 start:下标起始位置
void merge(int arr[], int res[], int start, int mid, int end)
{
int indexOfA = start;
int indexOfB = mid + 1;
int indexOfRes = start;
while (indexOfA <= mid && indexOfB <= end)
{
if (arr[indexOfA] < arr[indexOfB])
{
res[indexOfRes++] = arr[indexOfA++];
}
else
{
res[indexOfRes++] = arr[indexOfB++];
}
}
while (indexOfA <= mid)
res[indexOfRes++] = arr[indexOfA++];
while (indexOfB <= end)
res[indexOfRes++] = arr[indexOfB++];
}
// 归并排序
// arr:原数组 des:结果数组 length:原数组长度
void mergeSort(int arr[], int des[], unsigned start, unsigned end, unsigned length)
{
if (arr == nullptr || des == nullptr || start > end)
return;
if (start == end)
{
des[start] = arr[start];
}
else
{
// 新建一个临时数组res存放子排序的结果
int* res = (int*)malloc(sizeof(int) * length);
if (res != nullptr)
{
unsigned mid = (start + end) / 2;
mergeSort(arr, res, start, mid, length);
mergeSort(arr, res, mid + 1, end, length);
//将子排序的结果归并到目标数组中
merge(res, des, start, mid, end);
}
free(res);
}
}
二 非比较类排序
不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
计数排序
平均、最好、最坏时间复杂度:O(n+k)
空间复杂度:O(n+k)
#include <iostream>
#include <random>
// 最大年龄
const int oldestAge = 99;
void sortAge(unsigned ages[], int length)
{
// 检查输入
if(ages == nullptr || length <= 0)
return;
// 定义辅助数组,每个下标记录对应年龄段的人数。
int timesOfAge[oldestAge + 1];
for (int i = 0; i <= oldestAge; ++i) {
timesOfAge[i] = 0;
}
for (int j = 0; j < length; ++j) {
int age = ages[j];
if (age < 0 || age > oldestAge)
throw new std::out_of_range("age out of range.");
++timesOfAge[age];
}
// 计数排序
int index = 0;
for (int i = 0; i <= oldestAge; ++i) {
for (int j = 0; j < timesOfAge[i]; ++j) {
ages[index] = i;
++index;
}
}
}
int main() {
std::default_random_engine e;
std::uniform_int_distribution<unsigned> u(0,oldestAge);
unsigned ages[30];
for (int i = 0; i < 30; ++i) {
ages[i] = u(e);
std::cout << ages[i] << ' ';
}
std::cout << std::endl;
sortAge(ages, 30);
for (int i = 0; i < 30; ++i) {
std::cout << ages[i] << ' ';
}
std::cout << std::endl;
return 0;
}
桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
平均时间复杂度:O(n+k)
最好时间复杂度:O(n)
最坏时间复杂度:O(n2)
空间复杂度:O(n+k)其中k为桶的数量
// 桶排序
void bucketSort(int arr[], int length, unsigned bucketSize)
{
if (arr == nullptr || length <= 0)
return;
// 通过数组的最大值、最小值来设置桶的数量与大小
int minValue = arr[0];
int maxValue = arr[0];
for (int i = 1; i < length; ++i) {
if (arr[i] < minValue) {
minValue = arr[i]; // 输入数据的最小值
} else if (arr[i] > maxValue) {
maxValue = arr[i]; // 输入数据的最大值
}
}
// 桶大小 默认是5
unsigned DEFAULT_BUCKET_SIZE = 5;
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
// 桶数量
unsigned bucketCount = (maxValue - minValue) / bucketSize + 1;
// 构造桶
std::vector<std::list<int>> buckets;
for (int i = 0; i < bucketCount; ++i) {
buckets.push_back(std::list<int>());
}
// 将数组中的数据插入桶中
for (int i = 0; i < length; ++i) {
int index = arr[i] / bucketSize;
buckets.at(index).push_back(arr[i]);
}
// 从桶中取出数据 start:arr数组起始下标
int start = 0;
for (auto bucket : buckets) {
bucket.sort();
for (auto item: bucket) {
arr[start++] = item;
}
}
}
基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
平均时间复杂度:O(n * k)
最好时间复杂度:O(n * k)
最坏时间复杂度:O(n * k)
空间复杂度:O(n+k)其中k为桶的数量
/*
* 对数组按照个、十、百、千等进制位进行桶排序
*
* 参数说明:
* arr -- 数组
* length -- 数组长度
* exp -- 进制位。
*
* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
* (01) 当exp=1表示按照"个位"对数组a进行排序
* (02) 当exp=10表示按照"十位"对数组a进行排序
* (03) 当exp=100表示按照"百位"对数组a进行排序
* ...
*/
void bucketSortByDigit(int arr[], int length, int exp)
{
// res:排序后的结果,临时变量,最后赋值给arr
int *res = (int *)malloc(sizeof(int) * length);
// 固定桶的数量为10,分别统计0,1,2,3,... 9出现的次数
int buckets[10] = {0};
// 桶排序
for (int i = 0; i < length; ++i) {
++buckets[(arr[i] / exp) % 10];
}
// 让每一个桶中分布的数,对应原数组中的数组下标
for (int i = 1; i < 10; ++i) {
buckets[i] += buckets[i - 1];
}
// 根据buckets中记录的下标,将arr[]重新排序 i必须由大到小
for (int i = length - 1; i >= 0; --i) {
res[ buckets[(arr[i] / exp) % 10] - 1 ] = arr[i];
// 更新buckets[],防止每次从对应的桶中取相同的数据
--buckets[(arr[i] / exp) % 10];
}
// 将res中的排序结果拷贝给arr
for (int i = 0; i < length; ++i) {
arr[i] = res[i];
}
free(res);
}
// 基数排序
void radixSort(int arr[], int length)
{
if (arr == nullptr || length <= 0)
return;
// maxVale:数组最大值
int maxValue = arr[0];
for (int i = 1; i < length; ++i) {
if (arr[i] > maxValue)
maxValue = arr[i];
}
// 从个位开始进行桶排序,直到最大值的最高位
int exp = 1;
while (maxValue / exp)
{
bucketSortByDigit(arr, length, exp);
exp *= 10;
}
}