常见15种排序算法总结及C++代码实现

前言

花了将近一个月时间,学习并整理了常见15种排序算法,并亲自用C++代码实现了一遍。

通过这次学习,我对于每种算法的具体细节、适用场景、复杂度分析,有了更深的理解,代码能力也有不少提高。

建议大家有时间的话,都能亲自手写一遍每种排序的代码,收获真的要比只理解概念大很多。

参考文章

我在学习排序算法时,主要参考了以下文章。建议读者阅读本文前,可酌情考虑先通读一遍这些文章,食用效果更佳(我也对文中的纰漏在评论区做了纠正和补充)

1.常见8种排序算法分析笔记之-空间O(1)三种(冒泡、选择、插入)

2.常见8种排序算法分析笔记之-时间O(NlogN)三种(归并、快排、堆排序)

3.常见8种排序算法分析笔记之-时间性能突破O(n^2)的原地排序法-希尔排序法

【十八】常见十种排序算法总结笔记

二项堆 - 知乎

Fibonacci heap

桶排序复杂度分析

下面进入正文:

常见排序算法分类

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

算法执行时间对比 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值