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()
函數,真——被吊打…感興趣者自行測試…