1. 排序算法总结
这些高度总结的图也是将排序算法进行了一个深刻的总结,在此不必过分多说。
基于学习角度,肯定是首先考虑学习一些简单的排序算法,如直接插入排序、二分插入排序,再者就希尔排序、堆排序等中级排序算法,然后就是排序算法的精华所在:快速排序、归并排序等高级排序算法。这也是一个循序渐进的过程,理解每一个排序算法的细节,能否进行手撕代码、复杂度分析算是对基本的排序算法有所掌握。但是,我们学习算法的目的是为了应用到实际问题中,帮助我们快速解决问题,这也是前半段学习排序算法的不足之处。
所以这篇博文是作为一个承前启后的作用,总结前半段最为基础的排序算法的基本知识,并为后面的 OJ
习题做铺垫,力求熟练掌握排序算法。
2. 高精度计时器配合随机数进行性能测试
高精度计时.h
你没看错,就是中文的变量、类命名~~~
#include <windows.h>
class 高精度计时
{
public:
高精度计时(void)
{
QueryPerformanceFrequency(&CPU频率);
}
~高精度计时(void) {}
void 开始()
{
QueryPerformanceCounter(&开始时间);
}
void 结束()
{
QueryPerformanceCounter(&结束时间);
间隔 = ((double)结束时间.QuadPart - (double)开始时间.QuadPart) / (double)CPU频率.QuadPart;
}
double 间隔毫秒() const
{
return 间隔 * 1000;
}
private:
double 间隔;
LARGE_INTEGER 开始时间;
LARGE_INTEGER 结束时间;
LARGE_INTEGER CPU频率;
};
随机数测试函数:
#define SIZE (5 * 1000)
void TestSpeed() {
int array[SIZE];
int size = SIZE;
srand(20190118);
for (int i = 0; i < size; i++) {
array[i] = rand() % size;
}
QuickSort(array,0, size);
高精度计时 计时器;
计时器.开始();
HeapSort(array, size);
计时器.结束();
printf("耗时: %f 毫秒\n", 计时器.间隔毫秒());
}
sort.h
这些天来的所有排序算法源代码:
#include <iostream>
#include <assert.h>
#include "高精度计时.h"
using namespace std;
void PrintArray(int array[], int size) {
for (int i = 0; i < size; ++i) {
printf("%d ", array[i]);
}
printf("\n");
}
void Swap(int *x, int *y) {
int tmp = *x;
*x = *y;
*y = tmp;
//*x = ((*x) ^ (*y));
//*y = ((*x) ^ (*y));
//*x = ((*x) ^ (*y));
}
// 直接插入排序(递增)
void InsertSort(int array[], int size) {
for (int i = 1; i < size; ++i) { // 遍历array
int k = array[i];
int j = i - 1;
for (j; j >= 0; --j) { // 与array[i]前面各个元素比较
if (array[j] <= k) { // 如果比前一个元素大,说明前半段已经有序,返回
break;
}
array[j + 1] = array[j]; // 将大于array[i]的元素向后移动,注意从后向前赋值
}
array[j + 1] = k; // 在前半段有序处插入array[i]
}
}
// 二分插入排序(递增)
void BinInsertSort(int array[], int size) {
for (int i = 1; i < size; ++i) { // 遍历array
if (array[i] < array[i - 1]) {
int k = array[i];
int j = i - 1;
int left = 0, right = i - 1, mid = 0; // 直接对前段数据进行二分查找,找到array[i]应有的位置
while (left <= right) {
mid = (left + right) / 2; // 取中间位置
if (k < array[mid]) {
right = mid - 1; // 插入点在左半区
}
else {
left = mid + 1; // 插入点在右半区
}
} // 找到位置 right
for (j; j >= right + 1; --j) {
array[j + 1] = array[j]; // 将大于array[i]的元素向后移动,注意从后向前赋值
}
array[right + 1] = k; // 在前半段有序处插入array[i]
}
}
}
// 希尔排序(递增)
void ShellSort(int array[], int size) {
int gap = size;
while (1) {
gap = (gap / 3) + 1;
for (int i = gap; i < size; i++) {
int k = array[i];
int j;
for (j = i - gap; j >= 0; j -= gap) {
if (array[j] <= k) {
break;
}
array[j + gap] = array[j];
}
array[j + gap] = k;
}
if (gap == 1) {
break;
}
}
}
// 冒泡排序(递增)
void BubbleSort(int array[], int size) {
for (int i = 1; i < size; ++i) {
int sorted = 1;
for (int j = 0; j < size - i; ++j) { // 将剩余数据的最大值归位
if (array[j] > array[j + 1]) {
int tmp = array[j];
array[j] = array[j + 1];
array[j + 1] = tmp;
sorted = 0;
}
}
if (sorted == 1) { // 在一趟中没有排序,则算法结束
break;
}
}
}
// 快速排序(递增)
// 三数取中法
int BaseNumber(int array[], int begin, int end) {
int mid = begin + ((end - begin) >> 1); // 采用位运算效率高
if (array[begin] > array[mid]) { // 逻辑判断取出中位数的下标,当然排序及其它方法均可
if (array[begin] > array[end]) {
if (array[mid] > array[end]) {
return mid;
}
else {
return end;
}
}
else {
return begin;
}
}
else {
if (array[mid] > array[end]) {
if (array[begin] > array[end]) {
return begin;
}
else {
return end;
}
}
else {
return mid;
}
}
}
// 快速排序(递增)
// hoare版本
int QuickSort1(int array[], int begin, int end) {
// 找基准
// 由于每次规划取到最大值或最小值的概率都非常高,
// 这样容易使树变成单支树,所以采用三数取中法来降低取到最值的概率
//int index = BaseNumber(array, begin, end); // 基准值在数组中的下标
//if (index != end) {
// Swap(&array[index], &array[end]); // 将基准值与最后一个数字进行值交换
//}
// 基准值
int key = array[end];
// 基准值的下标
int k = end;
// 两个指针、begin从0开始,end从size-1开始
while (begin != end) {
// begin向后移动,找比基准值大的元素,且begin不能大于end
// 如果array[begin]比key小,则++begin
while (array[begin] <= key && (begin < end)) {
++begin;
}
// end向前移动,找比基准值key小的元素,且end不能小于begin
// 如果array[end]比key大,则--end
while (array[end] >= key && (begin < end)) {
--end;
}
// 如果下标begin和下标end不相等,则交换所对应的数组元素值
if (begin != end) {
Swap(&array[begin], &array[end]);
}
}
// 如果begin的最终位置就是基准的位置则不用交换
if (begin != k) {
// 将基准值挪到相应位置上
Swap(&array[begin], &array[k]);
}
return begin;
}
// 快速排序(递增)
// 挖坑法
int QuickSort2(int array[], int begin, int end) {
// 依旧三数取中法确定基准
int index = BaseNumber(array, begin, end);
if (index != end) {
Swap(&array[index], &array[end]);
}
// 第一个坑
int key = array[end];
int k = end;
while (begin != end) {
// begin从左边开始找比关键字大的元素将其入坑
// begin所在位置变为坑
while (array[begin] <= key && begin < end) {
++begin;
}
if (begin != end) {
array[end] = array[begin];
--end;
}
// end从右开始找比关键字小的元素将其入begin坑
while (array[end] >= key && begin < end) {
--end;
}
if (begin != end) {
array[begin] = array[end];
++begin;
}
}
if (begin != k) {
array[begin] = key;
}
return begin;
}
// 快速排序(递增)
// 双指针方法
int QuickSort3(int array[], int begin, int end) {
int index = BaseNumber(array, begin, end);
int cur = begin, prev = begin - 1;
if (index != end) {
Swap(&array[index], &array[end]);
}
int key = array[end];
// cur不能超过序列长度
while (cur <= end) {
if (array[cur] <= key && ++prev != cur) {
Swap(&array[cur], &array[prev]);
}
++cur;
}
return prev;
}
// 快排递归实现(递增)
void QuickSort(int array[], int left, int right) {
/*
// 由于快速排序是递归调用,容易产生栈溢出
// 但是快速排序排到最后元素也接近有序,则采用插入排序
if (right - left < 2) {
InsertSort(array + left, right - left);
}
*/
// 数据较小时,直接判断即可
if (left == right) {
return; // 区间内只有一个数
}
if (left > right) {
return; // 区间内没有数
}
// 基准值是array[right]
int pos;
pos = QuickSort1(array, left, right - 1); // 仅修改QuickSort1,1,2,3即可完成测试
QuickSort(array, 0, pos); // 快速排序基准值左侧
QuickSort(array, pos + 1, right); //快速排序基准值右侧
}
// 快排非递归实现(递增)
// 模拟实现栈
typedef struct Stack {
int *data;
int size;
}stack;
void InitStack(stack *s) {
int *data = (int*)malloc(20 * sizeof(int));
if (data == NULL) {
assert(0);
return;
}
s->data = data;
s->size = 0;
}
void PushStack(stack *s, int d) {
assert(s);
if (s->size > 20) {
return;
}
else {
s->data[s->size++] = d;
}
}
void PopStack(stack *s) {
assert(s);
if (s->size == 0) {
return;
}
else {
s->size--;
}
}
int TopStack(stack *s) {
assert(s);
return s->data[s->size - 1];
}
int EmptyStack(stack *s) {
assert(s);
return s->size == 0;
}
// 快排非递归实现(递增)
void QuickSortStack(int array[], int size) {
stack s;
int pos, left = 0, right = 0;
InitStack(&s);
PushStack(&s, 0);
PushStack(&s, size - 1);
while (!EmptyStack(&s)) {
right = TopStack(&s);
PopStack(&s);
left = TopStack(&s);
PopStack(&s);
if (left >= right) {
continue;
}
else {
pos = QuickSort1(array, left, right);
//先快排基准左侧,则先将后侧的下标入栈
if ((right - left) > pos + 1) {
PushStack(&s, pos + 1);
PushStack(&s, right - left);
}
if (pos > 0) {
PushStack(&s, 0);
PushStack(&s, pos - 1);
}
}
}
}
// 直接选择排序(递增)
void SelectSort(int array[], int size) {
for (int i = 0; i < size; ++i) {
// [0, size - i)
int m = 0;
for (int j = 0; j < size - i; ++j) {
if (array[j] > array[m]) {
m = j;
}
}
// m 就是最大数的下标了
//if (array + m == array + size - i - 1)
// continue;
Swap(array + m, array + size - i - 1);
}
}
// 升级版直接选择排序,一次即找最大的,也找最小的
void SelectSortOP(int array[], int size) {
int minSpace = 0; // 用来放找到的最小数的下标
int maxSpace = size - 1; // 用来放找到的最大数的下标
// 因为是闭区间,minSpace == maxSpace 时
// [minSpace, maxSpace] 区间内只剩一个数了
// 所有可以停止了
while (minSpace < maxSpace) {
int min = minSpace; // 假设最小的是 minSpace 位置
int max = minSpace; // 假设最大的是 minSpace 位置
// 在 [minSpace + 1, maxSpace] 区间里找真正的最小和最大
for (int j = minSpace + 1; j <= maxSpace; j++) {
if (array[j] < array[min]) {
min = j;
}
if (array[j] > array[max]) {
max = j;
}
}
// min 和 max 分别时最小和最大的数的下标
// 先交换小的
{
int t = array[min];
array[min] = array[minSpace];
array[minSpace] = t;
}
// 再交换大的
if (max == minSpace) {
max = min;
}
{
int t = array[max];
array[max] = array[maxSpace];
array[maxSpace] = t;
}
minSpace++;
maxSpace--;
}
}
// 堆排序(递增)
// 大顶堆的向下调整
void AdjustDown(int array[], int size, int r) {
int left = 2 * r + 1;
int right = 2 * r + 2;
if (left >= size) { // 是否为叶子节点
return; // 叶子节点直接结束
}
int m = left; // 有左孩子
// 是不是有右孩子,并找最大的孩子
if (right < size && array[right] > array[left]) {
m = right;
}
// 如果根的值大于最大孩子,直接返回
if (array[r] >= array[m]) {
return;
}
Swap(array + r, array + m); // 将最大值最为新的根
AdjustDown(array, size, m); // 递归向下调整
}
// 建堆
void CreateHeap(int array[], int size) {
// i=最后一个非叶子节点
// 已知parent,则 left=2* parent+1,right=2*parent+2
// 已知child,则parent=(child-1)/2,在此均为数组的下标
// 故最后一个非叶子节点,就是数组最后一个下标size-1,再-1,结果除2即可
for (int i = (size - 1 - 1) / 2; i >= 0; --i) {
AdjustDown(array, size, i);
}
}
// 堆排序(递增)
void HeapSort(int array[], int size) {
CreateHeap(array, size);
for (int i = 0; i < size; ++i) {
Swap(array, array + size - i - 1);
AdjustDown(array, size - i - 1, 0);
}
}
// 归并排序(递增)
// 合并两个有序序列
void Merge(int array[], int left, int mid, int right, int extra[]) {
int size = right - left;
int left_index = left;
int right_index = mid;
int extra_index = 0;
while (left_index < mid && right_index < right) {
if (array[left_index] <= array[right_index]) {
extra[extra_index] = array[left_index];
++left_index;
}
else {
extra[extra_index] = array[right_index];
++right_index;
}
extra_index++;
}
while (left_index < mid) {
extra[extra_index++] = array[left_index++];
}
while (right_index < right) {
extra[extra_index++] = array[right_index++];
}
for (int i = 0; i < size; i++) {
array[left + i] = extra[i];
}
}
void __MergeSort(int array[], int left, int right, int extra[]) {
// 终止条件
if (right == left + 1) {
// 只剩一个数
return;
}
if (right <= left) {
// 区间内没有数了
return;
}
int mid = left + (right - left) / 2;
// [left, mid) [mid, right)
__MergeSort(array, left, mid, extra);
__MergeSort(array, mid, right, extra);
// 左右区间都有序
Merge(array, left, mid, right, extra);
}
void MergeSortNor(int array[], int size) {
int *extra = (int *)malloc(sizeof(int)* size);
for (int i = 1; i < size; i = i * 2) {
for (int j = 0; j < size; j = j + 2 * i) {
int left, mid, right;
left = j;
mid = j + i;
right = mid + i;
if (mid >= size) {
// 没有右边的区间 [mid, right)
continue;
}
if (right > size) {
right = size;
}
Merge(array, left, mid, right, extra);
}
}
free(extra);
}
// 归并排序(递增)
void MergeSort(int array[], int size) {
int *extra = (int *)malloc(sizeof(int)* size);
__MergeSort(array, 0, size, extra);
free(extra);
}
// 桶排序(递增)
#define BUCKSIZE 100
typedef struct {
int data[BUCKSIZE];
int count; // 桶中元素的个数
} BuckType; // 桶类型
void BucketSort(int array[], int size) {
int max, min, num, pos;
BuckType *pB;
max = min = array[0];
for (int i = 1; i < size; ++i) {
if (array[i] > max) {
max = array[i];
}
else if (array[i] < min) {
min = array[i];
}
}
num = (max - min + 1) / 10 + 1; // 求出桶的个数
pB = (BuckType*)malloc(sizeof(BuckType) * num);
memset(pB, 0, sizeof(BuckType) * num);
for (int i = 0; i < size; ++i) { // 将数组元素分配进桶
int k = (array[i] - min + 1) / BUCKSIZE; // 求出array[i]对应的桶号,在此为10个桶
(pB + k)->data[(pB + k)->count] = array[i];
(pB + k)->count++;
}
pos = 0;
for (int i = 0; i < num; ++i) {
QuickSort((pB + i)->data, 0, (pB + i)->count); // 单个桶快速排序
for (int j = 0; j < (pB + i)->count; ++j) {
array[pos++] = (pB + i)->data[j];
}
}
}
// 基数排序(递增)
// 动态二维数组实现
int Maxbit(int array[], int size) { // 求待排序序列最大元素位数
int maxvalue = array[0], digits = 0; // 初始化最大元素为array[0],最大位数为0
for (int i = 1; i < size; i++) { // 找到序列中最大元素
if (array[i] > maxvalue)
maxvalue = array[i];
}
while (maxvalue != 0) { // 分解得到最大元素的位数
digits++;
maxvalue /= 10;
}
return digits;
}
int Bitnumber(int x, int bit) { // 求x第bit位上的数字,例如238第2位上的数字为3
int temp = 1;
for (int i = 1; i < bit; ++i) {
temp *= 10;
}
return (x / temp) % 10;
}
// 基数排序(递增)
// 动态二维数组实现
void RadixSort(int array[], int size) {
int i, j, k, bit, maxbit;
maxbit = Maxbit(array, size); //求最大元素位数
cout << "最大元素位数为:" << maxbit << "位 " << endl;
int **B = new int *[10]; // 分配二维动态数组
for (i = 0; i < 10; ++i)
B[i] = new int[size + 1]; // 每个桶都是size+1个空间,其中每个桶的第一个位置即B[0]第0位存放元素个数
for (i = 0; i < 10; i++)
B[i][0] = 0; // 统计第i个桶的元素个数
// 从个位到高位,对不同的位数进行桶排序
for (bit = 1; bit <= maxbit; ++bit) {
for (j = 0; j < size; j++) { // 分配
int num = Bitnumber(array[j], bit); // 取array[j]第bit位上的数字
int index = ++B[num][0];
B[num][index] = array[j];
}
for (i = 0, j = 0; i < 10; ++i) { // 收集
for (k = 1; k <= B[i][0]; ++k)
array[j++] = B[i][k];
B[i][0] = 0; // 收集后元素个数置零
}
}
for (int i = 0; i < 10; i++)
delete[]B[i];
delete B;
}
// 基数排序(递增)
// 一维数组实现
const int maxn = 1000;
int a[maxn], size;
int maxbit(int array[], int size) { //辅助函数,求数据的最大位数
int d = 1;//统计最大的位数
int p = 10;
for (int i = 0; i < size; ++i) {
while (array[i] >= p) {
p *= 10;
++d;
}
}
return d;
}
// 基数排序(递增)
// 一维数组实现
void radixsort(int array[], int size) {
int d = maxbit(array, size); // 求最大位数
int *tmp = new int[size]; // 辅助数组
int *count = new int[10]; // 计数器
int i, j, k;
int radix = 1;
for (i = 1; i <= d; i++) { // 进行d次排序
for (j = 0; j < 10; ++j) {
count[j] = 0; // 每次分配前清空计数器
}
for (j = 0; j < size; ++j) {
k = (array[j] / radix) % 10; // 取出个位数,然后是十位数,...
count[k]++; // 统计每个桶中的记录数
}
for (j = 1; j < 10; ++j) {
count[j] += count[j - 1]; // 将tmp中的位置依次分配给每个桶
}
for (j = size - 1; j >= 0; --j) { //将所有桶中记录依次收集到tmp中
k = (array[j] / radix) % 10;
tmp[--count[k]] = array[j];
}
for (j = 0; j < size; j++) { // 将临时数组的内容复制到array中
array[j] = tmp[j];
}
cout << "第" << i << "次排序结果:" << endl;
for (int i = 0; i < size; ++i)
cout << array[i] << " ";
cout << endl;
radix = radix * 10;
}
delete[]tmp;
delete[]count;
}
// 测试函数
void TestRight() {
int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
int size = sizeof(array) / sizeof(int);
SelectSortOP(array, size);
PrintArray(array, size);
}
#define SIZE (5 * 1000)
void TestSpeed() {
int array[SIZE];
int size = SIZE;
srand(20190118);
for (int i = 0; i < size; i++) {
array[i] = rand() % size;
}
// 需要有序序列时可调用
// QuickSort(array,0, size);
高精度计时 计时器;
计时器.开始();
HeapSort(array, size);
计时器.结束();
printf("耗时: %f 毫秒\n", 计时器.间隔毫秒());
}
main.cpp
主函数:
#include "sort.h"
int main() {
TestRight();
TestSpeed();
system("pause");
return 0;
}
3. 性能测试
有序 | 乱序 | 逆序 |
---|---|---|
冒泡 | 0.2ms | 7.9s |
插排 | 0.2ms | 1.7s |
希尔 | 2.3ms | 10ms |
选择 | 3.4s | 3.3s |
选择OP | 4.7s | 2s |
堆排序 | 9ms | 15ms |
… | … | … |
测了测库的 sort()
函数,真——被吊打…感兴趣者自行测试…