前言
花了将近一个月时间,学习并整理了常见15种排序算法,并亲自用C++代码实现了一遍。
通过这次学习,我对于每种算法的具体细节、适用场景、复杂度分析,有了更深的理解,代码能力也有不少提高。
建议大家有时间的话,都能亲自手写一遍每种排序的代码,收获真的要比只理解概念大很多。
参考文章
我在学习排序算法时,主要参考了以下文章。建议读者阅读本文前,可酌情考虑先通读一遍这些文章,食用效果更佳(我也对文中的纰漏在评论区做了纠正和补充)
1.常见8种排序算法分析笔记之-空间O(1)三种(冒泡、选择、插入)
2.常见8种排序算法分析笔记之-时间O(NlogN)三种(归并、快排、堆排序)
3.常见8种排序算法分析笔记之-时间性能突破O(n^2)的原地排序法-希尔排序法
下面进入正文:
常见排序算法分类

1.冒泡排序:O(n2)

2.选择排序:O(n2)

3.插入排序:O(n2)

三种O(n2)算法总结

4.归并排序:O(nlogn)

5.单路快速排序:O(nlogn)
6.双路快速排序:O(nlogn)

7.三路快速排序:O(nlogn)

三种快排总结

8.堆排序:O(nlogn)

三种O(nlogn)算法总结

9.二项堆排序:O(nlogn)


10.斐波那契堆排序:O(nlogn)

11.希尔排序:O(nlogn)
12.基数排序:O(nd)

13.计数排序:O(n+k)

14.桶排序:O(n+k)


15.鸽巢排序:O(n+k)

C++代码实现
注:除了斐波那契堆之外,上文提到的其余14种排序算法均已在本代码中实现。作者以后如有时间,会再手撕一下斐波那契堆。大家如果有兴趣的话,也欢迎补充一下自己写的实现代码~
#include <iostream>
#include <ctime>
#include <vector>
using namespace std;
/*
Program Name: 15 common sorting algorithms in C++
Author: Xin Li
Place: Los Angeles, CA
Date: 4:07AM, Aug 13, 2023 (PDT)
*/
// #define arr_size(x) sizeof(arr) / sizeof(arr[0])
#define ARRAY_SIZE 100000
enum Sort_Algorithm
{
BUBBLE_SORT,
SELECTION_SORT,
INSERTION_SORT,
MERGE_SORT,
QUICK_SORT_1WAY,
QUICK_SORT_2WAY,
QUICK_SORT_3WAY,
HEAP_SORT,
BINOMIAL_HEAP_SORT,
// FIBONACCI_HEAP_SORT,
SHELL_SORT,
RADIX_SORT,
COUNTING_SORT,
BUCKET_SORT,
PIGEON_SORT
};
// sorting algorithms
template <int N>
void bubble_sort(int (&arr)[N]);
template <int N>
void selection_sort(int (&arr)[N]);
template <int N>
void insertion_sort(int (&arr)[N]);
template <int N>
void merge(int (&arr)[N], int left, int middle, int right, int (&tmp)[N]);
template <int N>
void merge_sort(int (&arr)[N], int left, int right, int (&tmp)[N]);
template <int N>
void insertion_sort(int (&arr)[N], int left, int right);
template <int N>
void merge(int (&arr)[N], int left, int middle, int right, int (&tmp)[N]);
template <int N>
void quick_sort_1way(int (&arr)[N], int left, int right);
template <int N>
void quick_sort_1way(int (&arr)[N], int left, int right);
template <int N>
void quick_sort_2way(int (&arr)[N]);
template <int N>
void quick_sort_2way(int (&arr)[N], int left, int right);
void quick_sort_2way(vector<int> &arr);
void quick_sort_2way(vector<int> &arr, int left, int right);
template <int N>
void quick_sort_3way(int (&arr)[N]);
template <int N>
void quick_sort_3way(int (&arr)[N], int left, int right);
template <int N>
void heap_sort(int (&arr)[N]);
template <int N>
void heap_up(int (&arr)[N], int i);
template <int N>
void heap_down(int (&arr)[N], int i, int size);
template <int N>
void binomial_heap_sort(int (&arr)[N]);
template <int N>
void shell_sort(int (&arr)[N]);
template <int N>
void radix_sort(int (&arr)[N]);
template <int N>
void counting_sort(int (&arr)[N]);
template <int N>
void bucket_sort(int (&arr)[N]);
vector<int> bucket_sort(vector<int> &bucket, int bucket_size);
template <int N>
void pigeon_sort(int (&arr)[N]);
// auxiliary fuctions
template <int N>
void initialize_random_array(int (&arr)[N]);
template <int N>
void initialize_sequence_array(int (&arr)[N]);
template <int N>
void initialize_equal_array(int (&arr)[N]);
template <int N>
bool check_if_array_is_sorted(int (&arr)[N]);
template <int N>
clock_t choose_sort_algorithm(Sort_Algorithm algo, int (&arr)[N]);
/*
Bubble Sort
Avg Time: O(n2)
Best Time: O(n)
Worst Time: O(n2)
Space: O(1)
*/
template <int N>
void bubble_sort(int (&arr)[N])
{
for (int i = 0; i < N - 1; i++)
{
int last_swap = N - 1;
for (int j = N - 1; j > i; j--)
{
if (arr[j - 1] > arr[j])
{
swap(arr[j - 1], arr[j]);
last_swap = j - 1;
}
}
i = last_swap;
}
}
/*
Selection Sort
Avg Time: O(n2)
Best Time: O(n2)
Worst Time: O(n2)
Space: O(1)
*/
template <int N>
void selection_sort(int (&arr)[N])
{
for (int i = 0; i < N - 1; i++)
{
int minIndex = i;
for (int j = i; j < N; j++)
{
if (arr[j] < arr[minIndex])
{
minIndex = j;
}
}
swap(arr[i], arr[minIndex]); // arr[i] now is the ist smallest element
}
}
/*
Insertion Sort
Avg Time: O(n2)
Best Time: O(n)
Worst Time: O(n2)
Space: O(1)
*/
template <int N>
void insertion_sort(int (&arr)[N])
{
for (int i = 1; i < N; i++)
{
int tmp = arr[i]; // each turn, insert i into the proper pos
int j = i - 1;
for (; j >= 0 && arr[j] > tmp; j--)
{
arr[j + 1] = arr[j];
}
arr[j + 1] = tmp; // when arr[j]<=tmp or j=-1, tmp should be at j+1
}
}
/*
Merge Sort
Avg Time: O(nlogn)
Best Time: O(n)
Worst Time: O(nlogn)
Space: O(n)
*/
template <int N>
void merge_sort(int (&arr)[N])
{
int tmp[N];
merge_sort(arr, 0, N - 1, tmp);
}
template <int N>
void merge_sort(int (&arr)[N], int left, int right, int (&tmp)[N])
{
// if (left >= right)
// return; // infact only left==right could happen
if (right - left + 1 <= 10)
{
insertion_sort(arr, left, right); // not much improvement
return;
}
int middle = (left + right) / 2;
merge_sort(arr, left, middle, tmp);
merge_sort(arr, middle + 1, right, tmp);
if (arr[middle] > arr[middle + 1])
merge(arr, left, middle, right, tmp);
}
template <int N>
void merge(int (&arr)[N], int left, int middle, int right, int (&tmp)[N])
{
int l = left, r = middle + 1, t = left;
while (t <= right)
{
if (l > middle)
tmp[t++] = arr[r++];
else if (r > right)
tmp[t++] = arr[l++];
else if (arr[l] <= arr[r])
{
tmp[t++] = arr[l++];
}
else
tmp[t++] = arr[r++];
}
memcpy(&arr[left], &tmp[left], sizeof(int) * (right - left + 1));
}
template <int N>
void insertion_sort(int (&arr)[N], int left, int right)
{
for (int i = left + 1; i <= right; i++)
{
int tmp = arr[i];
int j;
for (j = i - 1; j >= left && arr[j] > tmp; j--)
{
arr[j + 1] = arr[j];
}
arr[j + 1] = tmp;
}
}
/*
Quick Sort (1 way)
Avg Time: O(nlogn)
Best Time: O(nlogn)
Worst Time: O(n2)
Avg Space: O(logn)
Worst Space: O(n)
*/
template <int N>
void quick_sort_1way(int (&arr)[N])
{
srand(time(nullptr));
quick_sort_1way(arr, 0, N - 1);
}
template <int N>
void quick_sort_1way(int (&arr)[N], int left, int right)
{
if (left >= right)
return;
int randIndex = rand() % (right - left + 1) + left;
swap(arr[left], arr[randIndex]);
int i = left + 1, j = left;
int v = arr[left];
while (i <= right)
{
if (arr[i] < v)
{
swap(arr[++j], arr[i]);
}
i++;
} // after the sort, left+1~j:<v; j+1~right:>=v
// bad performance for equal array: o(n2)
swap(arr[left], arr[j]);
quick_sort_1way(arr, left, j - 1);
quick_sort_1way(arr, j + 1, right);
}
/*
Quick Sort (2 way)
Avg Time: O(nlogn)
Best Time: O(nlogn)
Worst Time: O(nlogn)
Avg Space: O(logn)
Worst Space: O(logn)
*/
template <int N>
void quick_sort_2way(int (&arr)[N])
{
srand(time(nullptr));
quick_sort_2way(arr, 0, N - 1);
}
template <int N>
void quick_sort_2way(int (&arr)[N], int left, int right)
{
if (left >= right)
return;
int randIndex = rand() % (right - left + 1) + left;
swap(arr[left], arr[randIndex]);
int i = left + 1, j = right;
int v = arr[left];
while (i <= j)
{
while (arr[i] < v && i <= j)
{ // i<=j: prevent i from proceeding out of boundary
i++;
}
while (arr[j] > v && i <= j)
{
j--;
}
if (i <= j)
{ // if already i>j, no need to swap but directly end the loop
// still judge i=j in order to let i++ and j--
swap(arr[i++], arr[j--]);
}
} // when finish this loop, j refers to the first(or second, when the last loop is i==j && arr[i]=arr[j]=v) that disatisfy the condition arr[j]>v, so after swapping arr[left] and arr[j], all members before j are <=v, and all members after j are >=v
swap(arr[left], arr[j]);
quick_sort_2way(arr, left, j - 1);
quick_sort_2way(arr, j + 1, right);
}
void quick_sort_2way(vector<int> &arr)
{
srand(time(nullptr));
quick_sort_2way(arr, 0, arr.size() - 1);
}
void quick_sort_2way(vector<int> &arr, int left, int right)
{
if (left >= right)
return;
int randIndex = rand() % (right - left + 1) + left;
swap(arr[left], arr[randIndex]);
int i = left + 1, j = right;
int v = arr[left];
while (i <= j)
{
while (arr[i] < v && i <= j)
{ // i<=j: prevent i from proceeding out of boundary
i++;
}
while (arr[j] > v && i <= j)
{
j--;
}
if (i <= j)
{ // if already i>j, no need to swap but directly end the loop
// still judge i=j in order to let i++ and j--
swap(arr[i++], arr[j--]);
}
} // when finish this loop, j refers to the first(or second, when the last loop is i==j && arr[i]=arr[j]=v) that disatisfy the condition arr[j]>v, so after swapping arr[left] and arr[j], all members before j are <=v, and all members after j are >=v
swap(arr[left], arr[j]);
quick_sort_2way(arr, left, j - 1);
quick_sort_2way(arr, j + 1, right);
}
/*
Quick Sort (3 way)
Avg Time: O(nlogn)
Best Time: O(n)
Worst Time: O(nlogn)
Avg Space: O(logn)
Worst Space: O(1)
*/
template <int N>
void quick_sort_3way(int (&arr)[N])
{
srand(time(nullptr));
quick_sort_3way(arr, 0, N - 1);
}
template <int N>
void quick_sort_3way(int (&arr)[N], int left, int right)
{
if (left >= right)
return;
int randIndex = rand() % (right - left + 1) + left;
swap(arr[left], arr[randIndex]);
int lt = left, gt = right + 1, i = left + 1; // three pointer for navigation
int v = arr[left];
while (i < gt)
{
if (arr[i] < v)
swap(arr[++lt], arr[i++]);
else if (arr[i] > v)
swap(arr[--gt], arr[i]); // notice here i doesn't ++
else
i++; // arr[i]==v
} // after loop: left+1~lt:<v, lt+1~gt-1(i-1):=v, gt~right:>v
swap(arr[left], arr[lt]); // now: left~lt-1:<v, lt~gt-1:=v, gt~right:>v
quick_sort_3way(arr, left, lt - 1);
quick_sort_3way(arr, gt, right);
}
/*
Heap Sort
Avg Time: O(nlogn)
Best Time: O(nlogn)
Worst Time: O(nlogn)
Avg Space: O(1)
*/
template <int N>
void heap_sort(int (&arr)[N])
{
if (N <= 1)
return;
// heap: n's parent: ⌊(n-1)/2⌋, n's children: 2n+1, 2n+2
// build the max-heap (upward): O(n)
for (int i = (N - 2) / 2; i >= 0; i--)
{ // N-2/2: the last non-child node
heap_down(arr, i, N);
}
// build the max-heap (downward): O(nlogn) //not much dif in actual
// for (int i = 1; i < N; i++)
// {
// heap_up(arr, i);
// }
// heap sort O(nlogn)
for (int i = N - 1; i > 0; i--)
{
swap(arr[0], arr[i]);
heap_down(arr, 0, i);
}
}
template <int N>
void heap_up(int (&arr)[N], int i)
{
while (i > 0) // not reach the top
{
int parent = (i - 1) / 2;
if (arr[i] > arr[parent])
{
swap(arr[i], arr[parent]);
i = parent;
}
else
break;
}
}
template <int N>
void heap_down(int (&arr)[N], int i, int size)
{
while (2 * i + 1 < size) // notice that here isn't N
{
int larger_child = 2 * i + 1; // have children
if (larger_child + 1 < size && arr[larger_child] < arr[larger_child + 1])
{
larger_child++;
}
if (arr[i] < arr[larger_child])
{
swap(arr[i], arr[larger_child]);
i = larger_child;
}
else
break;
}
}
/*
Binomial Sort
Avg Time: O(nlogn)
Best Time: O(nlogn)
Worst Time: O(nlogn)
Avg Space: O(n)
*/
struct Binomial_Node
{
public:
Binomial_Node *parent;
int key;
int degree;
Binomial_Node *child;
Binomial_Node *next;
Binomial_Node(int k = 0) : parent(nullptr), key(k), degree(0), child(nullptr), next(nullptr){};
~Binomial_Node()
{
parent = nullptr, child = nullptr, next = nullptr; // incase the compiler doesn't wipe out cleanly.
}
};
class Binomial_Heap
{
public:
Binomial_Node *merge(Binomial_Node *pos2);
Binomial_Node *merge(Binomial_Heap *tree2);
Binomial_Node *insert(Binomial_Node *node);
Binomial_Node *findMin();
Binomial_Node *deleteNode(Binomial_Node *node);
int extractMin();
void print();
Binomial_Heap(Binomial_Node *node = nullptr) : root(node), minNode(node){};
private:
Binomial_Node *root;
Binomial_Node *minNode;
Binomial_Node *link(Binomial_Node *pos1, Binomial_Node *pos2);
Binomial_Node *combine(Binomial_Node *pos1, Binomial_Node *pos2);
Binomial_Node *flipChild(Binomial_Node *child_node);
void print(Binomial_Node *node, int height);
};
// O(logn)
Binomial_Node *Binomial_Heap::link(Binomial_Node *pos1, Binomial_Node *pos2)
{ // normally link two heaps into one, sequenced by the degree of each root nodes from smaller to bigger, pos1 pos2 represents the first root node from each heap.
// time complexity: O(logn)
if (!pos1 && !pos2)
return nullptr;
Binomial_Node dummyNode(0);
Binomial_Node *pos = &dummyNode;
while (pos1 && pos2)
{
if (pos1->degree <= pos2->degree)
{
pos->next = pos1;
pos1 = pos1->next;
}
else
{
pos->next = pos2;
pos2 = pos2->next;
}
pos = pos->next;
}
if (pos1)
{ // only pos1 remains
pos->next = pos1; // no need to loop to the end of pos1
}
if (pos2)
{
pos->next = pos2;
}
return this->root = dummyNode.next;
}
// function combine: merge two binomial trees of the same degree(usually they are adjacent to each other), return the new root of this merged tree, and automatically fix the new root's next pointer (but not fix the previous tree's root node's next pointer towards this new merged tree's root node)
// O(1)
Binomial_Node *Binomial_Heap::combine(Binomial_Node *pos1, Binomial_Node *pos2)
{
if (pos1->key <= pos2->key)
{
pos2->parent = pos1;
pos1->next = pos2->next; // change the newly merged tree's root node's next pointer
pos2->next = pos1->child;
pos1->child = pos2; // notice that the sequence of these assignments are important
pos1->degree++;
return pos1;
}
else
{
pos1->parent = pos2;
pos1->next = pos2->child;
pos2->child = pos1;
pos2->degree++;
return pos2;
}
}
// O(logn)
Binomial_Node *Binomial_Heap::merge(Binomial_Heap *tree2)
{
tree2->root = merge(tree2->root);
return this->root;
}
// time complexity: O(logn)
Binomial_Node *Binomial_Heap::merge(Binomial_Node *pos2)
{
Binomial_Node *pos1 = this->root;
Binomial_Node *pos = link(pos1, pos2);
if (!pos)
return nullptr;
else if (!pos->next)
return pos;
Binomial_Node dummyNode(0);
dummyNode.next = pos;
Binomial_Node *prev = &dummyNode;
Binomial_Node *pos_next = pos->next;
while (pos_next)
{
// case1: the degree is different, ignore and continue the next loop
if (pos->degree != pos_next->degree)
{
prev = pos;
pos = pos_next;
pos_next = pos_next->next;
}
// case2: the degree is same, but have 3 same binomial_tree, in this case we just leave the first and merge last two trees.
else if (pos_next->next && pos_next->degree == pos_next->next->degree)
{
// set the parameter(pointers) for the next loop
prev = pos;
pos = combine(pos_next, pos_next->next); // pos points to the newly merged tree
prev->next = pos; // notice that we need to manually link the previous node with this newly merged tree.
pos_next = pos->next; // pos_next points to the next node after this merged tree, which has been set in the function combine.
}
// case3: the degree is same, but different to the tree(pos_next->next), or the pos_next->next doesn't exist, in this case we merged the first two trees and continue the loop.
else
{
pos = combine(pos, pos_next);
prev->next = pos; notice that in this case, prev hasn't been changed
pos_next = pos->next;
}
}
// fix the minNode
pos = dummyNode.next;
while (pos)
{
if (pos->key < minNode->key)
minNode = pos;
pos = pos->next;
}
return this->root = dummyNode.next;
}
// insert an 0-degree new node: O(1)
Binomial_Node *Binomial_Heap::insert(Binomial_Node *node)
{
// direct way: call merge() everytime, this will traverse all the heap's root node that wastes a lot of time: O(logn)
// merge(this->minNode, node);
// optimized way: break when there's no further need to execute when calling link() and combine() in merging process. This could be an amortized O(1) complexity
// link:
if (!node)
return this->root;
if (!this->root)
return this->root = this->minNode = node;
Binomial_Node *pos = this->root;
if (node->key < minNode->key)
minNode = node;
if (pos->degree != 0 || (pos->degree == 0 && pos->key > node->key))
{
// insert node before the root
node->next = pos;
this->root = node;
if (pos->degree != 0)
return node; // no need to combine at all
pos = node;
}
else
{
// insert node after the root
node->next = pos->next;
pos->next = node;
}
// combine:
Binomial_Node dummyNode(0);
Binomial_Node *pre = &dummyNode;
Binomial_Node *pos_next = pos->next;
while (pos_next && pos->degree == pos_next->degree)
{
pos = combine(pos, pos_next);
pre->next = pos;
pos_next = pos->next;
}
return this->root = dummyNode.next;
}
// O(1)
Binomial_Node *Binomial_Heap::findMin()
{
return this->minNode;
}
// O(logn)
Binomial_Node *Binomial_Heap::flipChild(Binomial_Node *child_node)
{
if (!child_node->parent)
{
cerr << "error: attempt to flip non-child nodes" << endl;
return nullptr;
}
Binomial_Node *pre = nullptr;
while (child_node)
{
Binomial_Node *tmp = child_node->next;
child_node->parent = nullptr; // don't forget this
child_node->next = pre;
pre = child_node;
child_node = tmp;
}
return pre;
}
// O(logn)
Binomial_Node *Binomial_Heap::deleteNode(Binomial_Node *node)
{
// swap node to the root (only change the key)
while (node->parent)
{
swap(node->key, node->parent->key);
node = node->parent;
}
// remove this root from heap
Binomial_Node *pre = nullptr;
Binomial_Node *pos = this->root;
if (this->root == node)
this->root = node->next;
else
{
while (pos != node)
{
pre = pos;
pos = pos->next;
}
pre->next = node->next;
}
// if just delete the minimum node, need to reset the minNode, otherwise, in the merge's fix minNode process, it will still regard this deleted node as the minNode(the outcome is inaccurate)
if (node->key == this->minNode->key)
{
if (node->child)
this->minNode = node->child; // just reset minNode into a value bigger than node->key, merge() will update the exact minNode's value later.
else
{
// in this case, flipping and merging will not happen, thus, we need to specify the exact minNode right now. Otherwise, next time when calling findMin() from extractMin(), an unexpected result will happen.
Binomial_Node *pos = this->root;
if (pos)
{
minNode = pos; // reset the minNode
pos = pos->next;
}
while (pos)
{ // update the minNode
if (pos->key < minNode->key)
{
minNode = pos;
}
pos = pos->next;
}
}
}
// build a new heap by flipping the deleted root node's child. In this way, we don't need to insert all the nodes from the children one by one. We just need to flip the order of the children binomial_trees(from descent to ascent) and let them become a binomial_heap.
// if the deleted node is the only node(without any child) in a binomial tree, we don't need to do any further operations.
if (node->child)
{
Binomial_Node *reversed_root = flipChild(node->child); // reverse the children
Binomial_Heap tmp_heap(reversed_root);
merge(&tmp_heap);
}
// delete node; // if using dynamic allocation(heap) for Binomial_Node
return this->root;
}
// O(logn)
int Binomial_Heap::extractMin()
{
int min_value = findMin()->key;
deleteNode(findMin());
return min_value;
}
void Binomial_Heap::print()
{
print(this->root, 0);
}
void Binomial_Heap::print(Binomial_Node *node, int height)
{
// firstly, print the current node
for (int i = 0; i < height; i++)
{
cout << "—— ";
}
cout << node->key << " "
<< "(degree:" << node->degree << ")" << endl;
// then, traverse to its child, if not exist, to its sibling, if still not, return to its parent.
if (node->child)
{
print(node->child, height + 1);
}
if (node->next)
{
print(node->next, height);
}
return;
}
template <int N>
void binomial_heap_sort(int (&arr)[N])
{
Binomial_Heap b_heap;
// dynamic allocation is not recommended, which will cost much more time than using the stack
// for (int i = 0; i < N; i++)
// {
// Binomial_Node *b_node = new Binomial_Node(arr[i]);
// b_heap.insert(b_node); // o(n)
// }
Binomial_Node b_node[N];
for (int i = 0; i < N; i++)
{
b_node[i].key = arr[i];
b_heap.insert(&b_node[i]);
}
for (int i = 0; i < N; i++)
{
arr[i] = b_heap.extractMin(); // o(nlogn)
}
}
/*
Shell Sort
Avg Time: O(n(logn)^2)~O(n2)
Best Time: O(nlogn)
Worst Time: O(n(logn)^2)~O(n2)
Space: O(1)
*/
template <int N>
void shell_sort(int (&arr)[N])
{
if (N <= 1)
return;
// int h = N / 2; //basic method: worst case O(n2)
int h = 1;
while (h < N / 3)
{ // Knuth method: worst case O(n^1.5)
h = 3 * h + 1;
}
while (h >= 1)
{ // traverse with different gap distance
for (int i = h; i < N; i++)
{
int tmp = arr[i];
int j;
for (j = i - h; j >= 0 && arr[j] > tmp; j -= h)
{
arr[j + h] = arr[j];
}
arr[j + h] = tmp;
}
// h = h / 2;
h = h / 3;
}
}
/*
Radix Sort
Avg Time: O(nd)
Best Time: O(nd)
Worst Time: O(nd)
Space: O(n+k)
*/
template <int N>
void radix_sort(int (&arr)[N])
{
if (N <= 1)
return;
// get the maxdigit
int max = arr[0];
for (int i = 1; i < N; i++)
{
if (abs(arr[i]) > max)
max = abs(arr[i]); // considering negative value
}
// int max = RAND_MAX;
int maxdigit = 0;
while (max) // special case: max=0 (all arr[i]=0), maxdigit=0, no need to proceed further operations (build the bucket)
{
maxdigit++;
max = max / 10;
}
int div = 1;
// build 10 buckets based on radix(0-9)
vector<vector<int>> bucket(10, vector<int>());
while (maxdigit)
{
// group into the bucket
int bucket_index;
for (int i = 0; i < N; i++)
{
bucket_index = (arr[i] / div) % 10;
bucket[bucket_index].push_back(arr[i]);
}
// rearrange the array
int i = 0;
for (bucket_index = 0; bucket_index < 10; bucket_index++)
{
for (int j = 0; j < (int)bucket[bucket_index].size(); j++)
{
arr[i++] = bucket[bucket_index][j];
}
bucket[bucket_index].clear(); // don't forget this!
}
maxdigit--;
div *= 10;
}
}
/*
Counting Sort
Avg Time: O(n+k)
Best Time: O(n+k)
Worst Time: O(n+k)
Space: O(n+k)
*/
template <int N>
void counting_sort(int (&arr)[N])
{
if (N <= 1)
return;
// traverse and find max,min value
int max = arr[0], min = arr[0];
for (int i = 1; i < N; i++)
{
max = arr[i] > max ? arr[i] : max;
min = arr[i] < min ? arr[i] : min;
}
// build the buckets(k=max-min+1)
int bucket_size = max - min + 1;
int bucket[bucket_size] = {0}; // partial initialization will let all the latter elements become 0
for (int &i : arr)
{
bucket[i - min]++;
}
// sum the buckets' elements
for (int i = 1; i < bucket_size; i++)
{
bucket[i] += bucket[i - 1]; // now bucket[i] represents the number of elements that has the value less or equal to i+min(notice that there could be same elements with value i+min, so bucket[i] also refers to the pos of the last element valued i+min)
}
// generate the result array
int res[N];
for (int i = N - 1; i >= 0; i--)
{
res[--bucket[arr[i] - min]] = arr[i];
}
memcpy(arr, res, sizeof(int) * N);
}
/*
Bucket Sort
Avg Time: O(n+k)
Best Time: O(n)
Worst Time: O(n2+k)
Space: O(n+k)
*/
template <int N>
void bucket_sort(int (&arr)[N])
{
const int BUCKET_SIZE = 1000; // can change the initial size here: the more bucket_num, the more even distribution of data, the better efficiency of the result.
vector<int> bucket(arr, arr + N);
bucket = bucket_sort(bucket, BUCKET_SIZE);
for (int i = 0; i < N; i++)
{
arr[i] = bucket[i];
}
}
vector<int> bucket_sort(vector<int> &bucket, int bucket_size)
{
if (bucket.size() <= 1) // no need to sort if there's only 0 or 1 element left in the bucket
return bucket;
int max = bucket[0], min = bucket[0];
for (int i = 1; i < (int)bucket.size(); i++)
{
max = bucket[i] > max ? bucket[i] : max;
min = bucket[i] < min ? bucket[i] : min;
}
int bucket_num = (max - min) / bucket_size + 1; // not use (max-min+1)/bucket_size, as we need to make sure that there's at least one bucket when max-min<=bucket_size
// prepare to build the buckets
vector<vector<int>> buckets(bucket_num, vector<int>());
int bucket_index = 0;
for (int i = 0; i < (int)bucket.size(); i++)
{
bucket_index = (bucket[i] - min) / bucket_size;
buckets[bucket_index].push_back(bucket[i]);
}
// sort for each buckets (recursively call bucket_sort)
// the time complexity of creating vectors through each recursions is huge. Not effecient than using other algorithm to deal with the sub_buckets.
/*
vector<int> res;
for (int i = 0; i < bucket_num; i++)
{
if (bucket_size == 1)
{
for (int j = 0; j < (int)buckets[i].size(); j++)
{
res.push_back(buckets[i][j]);
}
}
else
{
// vector<int> tmp = bucket_sort(buckets[i], bucket_size - 1);
vector<int> tmp = bucket_sort(buckets[i], bucket_size / 2);
res.insert(res.end(), tmp.begin(), tmp.end());
}
}
return res;
*/
// sort for each buckets (use other sorting algorithm)
vector<int> res;
for (int i = 0; i < bucket_num; i++)
{
quick_sort_2way(buckets[i], 0, buckets[i].size() - 1);
res.insert(res.end(), buckets[i].begin(), buckets[i].end());
}
return res;
}
/*
Pigeon Sort
Avg Time: O(n+k)
Best Time: O(n+k)
Worst Time: O(n+k)
Space: O(k)
*/
template <int N>
void pigeon_sort(int (&arr)[N])
{
if (N <= 1)
return;
int max = arr[0], min = arr[0];
for (int i : arr)
{
max = i > max ? i : max;
min = i < min ? i : min;
}
// build the nest
int count[max - min + 1] = {0}; // use this array to record arr's each element value's appearance time
for (int i : arr)
{
count[i - min]++;
}
// depart from the nest
int i = 0;
for (int j = 0; j < max - min + 1; j++)
{
while (count[j]--)
{
arr[i++] = j + min;
}
}
}
template <int N>
void initialize_random_array(int (&arr)[N])
{
srand(time(nullptr));
for (int i = 0; i < N; i++)
{
arr[i] = rand();
}
}
template <int N>
void initialize_sequence_array(int (&arr)[N])
{
srand(time(nullptr));
for (int i = 0; i < N; i++)
{
// arr[i] = i; // forward
arr[i] = N - i; // backward
}
}
template <int N>
void initialize_equal_array(int (&arr)[N])
{
srand(time(nullptr));
for (int i = 0; i < N; i++)
{
arr[i] = 1;
}
}
template <int N>
bool check_if_array_is_sorted(int (&arr)[N])
{
bool res = true;
for (int i = 0; i < N - 1; i++)
{
if (arr[i] > arr[i + 1])
{
cerr << "error: arr[" << i << "] (" << arr[i] << ") > arr[" << i + 1 << "] (" << arr[i + 1] << ")" << endl;
res = false;
return false; // only report one error and then ends
}
}
return res;
}
template <int N>
clock_t choose_sort_algorithm(Sort_Algorithm algo, int (&arr)[N])
{
clock_t begin = clock();
switch (algo)
{
case BUBBLE_SORT:
bubble_sort(arr);
cout << "bubble_sort: ";
break;
case SELECTION_SORT:
selection_sort(arr);
cout << "selection_sort: ";
break;
case INSERTION_SORT:
insertion_sort(arr);
cout << "insertion_sort: ";
break;
case MERGE_SORT:
merge_sort(arr);
cout << "merge_sort: ";
break;
case QUICK_SORT_1WAY:
quick_sort_1way(arr);
cout << "quick_sort_1way:";
break;
case QUICK_SORT_2WAY:
quick_sort_2way(arr);
cout << "quick_sort_2way:";
break;
case QUICK_SORT_3WAY:
quick_sort_3way(arr);
cout << "quick_sort_3way:";
break;
case HEAP_SORT:
heap_sort(arr);
cout << "heap sort:";
break;
case BINOMIAL_HEAP_SORT:
binomial_heap_sort(arr);
cout << "binomial heap sort:";
break;
case SHELL_SORT:
shell_sort(arr);
cout << "shell sort:";
break;
case RADIX_SORT:
radix_sort(arr);
cout << "radix sort:";
break;
case COUNTING_SORT:
counting_sort(arr);
cout << "counting sort:";
break;
case BUCKET_SORT:
bucket_sort(arr);
cout << "bucket sort:";
break;
case PIGEON_SORT:
pigeon_sort(arr);
cout << "pigeon sort:";
break;
default:
cout << "unknown algorithm: ";
break;
}
clock_t run_time = clock() - begin;
cout << run_time << "ms" << endl;
if (!check_if_array_is_sorted(arr))
cerr << "(warning: the sorted result is incorrect!)" << endl;
return run_time;
}
int main()
{
#if 1
cout << "Run time comparasion for 15 famous sorting algorithms." << endl;
cout << "Array Size: " << ARRAY_SIZE << endl;
cout << "=================" << endl;
cout << "<Test 1: Random array>" << endl;
int arr1[ARRAY_SIZE]; // if directly use arr1[10][100000] will cause stack overflow
initialize_random_array(arr1);
for (int i = 0; i < 14; i++)
{
int arr[ARRAY_SIZE]; // "dynamic" allocation in stack
memcpy(arr, arr1, sizeof(arr1));
choose_sort_algorithm(static_cast<Sort_Algorithm>(i), arr);
}
cout << "=================" << endl;
cout << "<Test 2: Sequence reversed array>" << endl;
int arr2[ARRAY_SIZE];
initialize_sequence_array(arr2);
for (int i = 0; i < 14; i++)
{
int arr[ARRAY_SIZE];
memcpy(arr, arr2, sizeof(arr2));
choose_sort_algorithm(static_cast<Sort_Algorithm>(i), arr);
}
cout << "=================" << endl;
cout << "<Test 3: Equal array>" << endl;
int arr3[ARRAY_SIZE];
initialize_equal_array(arr3);
for (int i = 0; i < 14; i++)
{
int arr[ARRAY_SIZE];
memcpy(arr, arr3, sizeof(arr3));
choose_sort_algorithm(static_cast<Sort_Algorithm>(i), arr);
}
#endif
#if 0
// binomial_heap test_case
const int SIZE = 10000;
Binomial_Heap b_heap;
srand(time(nullptr));
for (int i = 0; i < SIZE; i++)
{
// Binomial_Node *b_node = new Binomial_Node(SIZE - i);
Binomial_Node *b_node = new Binomial_Node(rand() % SIZE);
b_heap.insert(b_node);
}
b_heap.print();
cout << "===============" << endl;
int res[SIZE];
for (int i = 0; i < SIZE; i++)
{
// if (i == 391 || i == 392 || i == 393)
// b_heap.print();
res[i] = b_heap.extractMin();
}
for (int i = 0; i < SIZE; i++)
{
cout << res[i] << " ";
}
cout << endl;
cout << "sorted:" << check_if_array_is_sorted(res) << endl;
cout << endl;
#endif
#if 0
// array-based algorithm test_case
const int SIZE = 100000;
int test[SIZE];
for (int i = 0; i < SIZE; i++)
{
test[i] = SIZE - i;
}
clock_t begin = clock();
bucket_sort(test);
clock_t runtime = clock() - begin;
cout << runtime << " ms" << endl;
// for (int i : test)
// {
// cout << i << " ";
// }
cout << endl;
cout << check_if_array_is_sorted(test) << endl;
#endif
return 0;
}
// remove the stack-overflow(too many recursion) warning:
// g++ -Wall "-Wl,--stack=268435456" 43_sort_algorithms.cpp -o 43_sort_algorithms.exe
// 43_sort_algorithms.exe
// ↑ already add this command in tasks.json in vscode
算法执行时间对比





376

被折叠的 条评论
为什么被折叠?



