C++模板实现十大排序算法

所有排序算法默认升序排序。

1. 冒泡排序

适用:元素个数小的情况

template<typename T>
void Swap(T &lhs, T&rhs)
{
    T tmp = rhs;
    rhs = lhs;
    lhs = tmp;
}

template<typename T>
void bubble_sort(T *arr, int n)
{
    for (int i = 0; i < n - 1; ++i) {
        for (int j = 0; j < n - 1 - i; ++j) {
            if (arr[j] > arr[j + 1]) {
                Swap(arr[j], arr[j + 1]);
            }
        }
    }
}

template<typename T>
void bubble_sort_2(T *arr, int n)
{
    for (int i = 0; i < n - 1; ++i) {
        bool done = true;
        for (int j = 0; j < n - 1 - i; ++j) {
            if (arr[j] > arr[j + 1]) {
                Swap(arr[j], arr[j + 1]);
                done = false;
            }
        }
        if (done) {
            break;
        }
    }
}

template<typename T>
void bubble_sort_3(T *arr, int n)
{
    T flag = n - 1, i;
    while (flag) {
        i = flag;
        flag = 0;
        for (int j = 0; j < i; ++j) {
            if (arr[j] > arr[j + 1]) {
                Swap(arr[j], arr[j + 1]);
                flag = j;
            }
        }
    }
}

2. 选择排序

//不稳定
template<typename T>
void selection_sort(T *a, int n)
{
    int min = 0;
    for (int i = 0; i < n - 1; ++i) {
        min = i;
        for (int j = i + 1; j < n; ++j) {
            if (a[min] > a[j]) {
                min = j;
            }
        }
        if (min != i) {
            Swap(a[min], a[i]);
        }
    }
}

3. 插入排序

将第一个数作为有限数列,后面的数作为无序数列。
从头到尾遍历无序数列,将无序数key插入到有序数列中。
从后往前遍历有序数列,将大于key的数往后移,最后将key插入到空位。

//从第一个元素开始,给定数列是有序的,将后面的元素插入到这个数列中,将数列从后向前遍历,将待排元素插入到指定位置
template<typename T>
void insertion_sort(T *a, int n)
{
    int i, j;
    T tmp;
    for (i = 1; i < n; ++i) {
        tmp = a[i];
        for (j = i; j > 0 && a[j - 1] > tmp; --j) {
            a[j] = a[j - 1];
        }
        a[j] = tmp;
    }
}

4. shell排序

将数列分组,每组进行插入排序
循环将数列分成len/2组,最后只有一组插入排序完成时流程就结束了

template<typename T>
void shell_sort(T *arr, int len)
{
    for (int stepLen = len / 2; stepLen >= 1; stepLen /= 2) {
        for (int i = stepLen; i < len; ++i) {
            T key = arr[i];
            T j;
            for (j = i; j >= stepLen && arr[j - stepLen] > key; j -= stepLen) {
                arr[j] = arr[j - stepLen];
            }
            arr[j] = key;
        }
    }
}

5. 快速排序

template<typename T>
void quick_sort_impl(T arr[], int left, int right) {
    if (left >= right) {
        return;
    }
    int i = left, j = right;
    T midValue = arr[i];

    while (i < j) { //递归终止条件:left>=right
        while (i < j && arr[j] >= midValue) { //从右往左找到第一个小于mid的数的下标
            --j;
        }
        if (i < j) {
            arr[i++] = arr[j];
        }
        while (i < j && arr[i] <= midValue) { //从左往右找到第一个大于mid的数的下标
            ++i;
        }
        if (i < j) {
            arr[j--] = arr[i];
        }
    }

    arr[i] = midValue;

    quick_sort_impl(arr, left, i - 1);
    quick_sort_impl(arr, i + 1, right);
}

template<typename T>
void quick_sort(T arr[], int len) {
    quick_sort_impl(arr, 0, len - 1);
}

6. 归并排序

/**
 * @brief 合并两个有序数组([left,mid]和[mid+1,right])为一个有序数组
 * @tparam T 
 * @param arr 数组指针
 * @param tmp 辅助数组(临时合并用)
 * @param left 第一个数组开始位置在arr中的下标
 * @param mid mid是第一个数组的结束位置,mid+1是第二个数组的开始位置
 * @param right 第二个数组结束位置在arr中的下标
*/
template<typename T>
void merge(T arr[], T tmp[], int left, int mid, int right) {
    int i = left, j = mid + 1;
    int k = 0;

    while (i <= mid && j <= right) {
        if (arr[i] < arr[j]) {
            tmp[k++] = arr[i++];
        }
        else {
            tmp[k++] = arr[j++];
        }
    }
    while (i <= mid) {
        tmp[k++] = arr[i++];
    }
    while (j <= right) {
        tmp[k++] = arr[j++];
    }

    for (int i = 0; i < k; ++i) {
        arr[left++] = tmp[i];
    }
}

template<typename T>
void merge_sort_impl(T arr[], T tmp[], int left, int right) {
    if (left >= right) {
        return;
    }
    int mid = (left + right) / 2;
    merge_sort_impl(arr, tmp, left, mid);
    merge_sort_impl(arr, tmp, mid + 1, right);
    merge(arr, tmp, left, mid, right);
}

template<typename T>
void merge_sort(T arr[], int len, T tmp[]) {
    merge_sort_impl(arr, tmp, 0, len - 1);
}

7. 堆排序

堆排序一定要画图理解(二叉树)

虽然是从小到大排序,但是本方法使用的是最大堆

算法流程分两步:

第一步:先将所有元素构造最大堆

注意第一次构造最大堆时从最后一个有孩子的节点开始(也就是len/2-1处,因为叶子节点已经是最大堆,不需要操作),然后往前构造最大堆

第一次构造最大堆需要循环len/2次,后面只需要一次

第二步:循环里

将堆顶最大值与最后一个元素交换,此时最后一个元素有序

将未排序的所有元素构造最大堆

这样每次迭代最大值都移动到后,最后变成递增序列

template<typename T>
void build_max_heap(T arr[], int start, int end) {
    int dad = start;
    int son = 2 * dad + 1;
    // 子节点在未排序元素范围内才需要操作
    while (son <= end) {
        // 选择子节点中较大值
        if (son + 1 <= end && arr[son] < arr[son + 1]) {
            ++son;
        }
        // 父节点大于子节点表示构造完成
        if (arr[dad] > arr[son]) {
            return;
        }
        // 使父节点大于子节点
        else {
            std::swap(arr[dad], arr[son]);
            dad = son;
            son = 2 * dad + 1;
        }
    }
}

/**
 * @brief 虽然是从小到大排序,但是本方法使用的是最大堆
 * 第一步:先将所有元素构造最大堆
 * 注意第一次构造最大堆时从最后一个有孩子的节点开始(也就是len/2-1处,因为叶子节点已经是最大堆,不需要操作),然后往前构造最大堆
 * 第一次构造最大堆需要循环len/2次,后面只需要一次
 * 
 * 第二步:循环里
 * 将堆顶最大值与最后一个元素交换,此时最后一个元素有序
 * 将未排序的所有元素构造最大堆
 * 这样每次迭代最大值都移动到后,最后变成递增序列
 * 
 * @tparam T 
 * @param arr 数组名
 * @param len 数组长度
*/
template<typename T>
void heap_sort(T arr[], int len) {
    int end = len - 1;

    // 第一步
    for (int i = len / 2 - 1; i >= 0; --i) {
        build_max_heap(arr, i, end);
    }
    // 第二步
    for (int i = end; i > 0; --i) {
        std::swap(arr[0], arr[i]);
        build_max_heap(arr, 0, i - 1);
    }
}

8. 计数排序

通过比较元素的排序算法一般平均复杂度是O(nlogn),计数排序时间复杂度O(n+k) //n是数组长度,k是所有元素中的最大值
引入额外的数组辅助排序,利用数组的下标是有序的这个特点进行排序
流程:
1.创建计数数组count_arr,将排序数组arr的的值作为计数数组的下标,计数数组的值是排序数组的值出现次数
2.循环将计数数组上一个值增加到下一个值中,目的是保证排序数组的值的相对有序,这时候排序数组中的元素排序后相对位置是count_arr[j] - 1
3.循环给输出数组赋值,int j = arr[i]; sorted_arr[count_arr[j] - 1] = j; --count_arr[j];

缺点:
排序数组必须是整型
必须知道最大值

void counting_sort(int arr[], int sorted_arr[], int len, int max) {
    int *count_arr = new int[max + 1];
    for (int i = 0; i <= max; ++i) {
        count_arr[i] = 0;
    }
    for (int i = 0; i < len; ++i) {
        ++count_arr[arr[i]];
    }
    for (int i = 1; i <= max; ++i) {
        count_arr[i] += count_arr[i - 1];
    }
    //for (int i = len - 1; i >= 0; --i) {
    for (int i = 0; i < len; ++i) {
        int j = arr[i];
        sorted_arr[count_arr[j] - 1] = j;
        --count_arr[j];
    }
    delete[] count_arr;
}

void counting_sort_demo(int arr[], int len) {
    int *sorted_arr = new int[len];
    int max = 99;
    counting_sort(arr, sorted_arr, len, max);
    for (int i = 0; i < len; ++i) {
        std::cout << sorted_arr[i] << " ";
    }
    std::cout << std::endl;
    delete[] sorted_arr;
}

9. 桶排序

桶排序是计数排序的变种。计数排序只适用于整形,桶排序可以用于浮点型。
计数排序是将元素分到小桶中(一个桶一个元素),桶排序是将元素分到大桶中(一个桶多个元素)
平均时间复杂度(n+k)
流程:
1.已知排序数组中的最大值m,元素范围0到j(m < j), 设置桶的数量l,将排序数组的元素放入各个桶中,每个桶的区间宽度是j/l
2.对非空桶中元素排序,合并各个桶(合并链表)
3.将链表的值赋值到排序数组,完成排序

特点:
空间换时间,桶中元素越少则排序越快,但空间占用越大
快慢取决于桶中元素排序速度
适用场景:
数列均匀的分布在某个范围(已知上下限)

// 假设元素区间是[0,100)
// 不带头结点的链表
template<typename T>
struct ListNode {
    T value;
    ListNode *next;
    explicit ListNode(T value = 0) : value(value), next(nullptr) {}
};

template<typename T>
ListNode<T>* insert(ListNode<T>* head, T value) {
    ListNode<T> dumpNode;
    ListNode<T>* dummy = &dumpNode;
    dummy->next = head;
    ListNode<T>* pre = dummy;
    ListNode<T>* cur = head;
    while (nullptr != cur && cur->value <= value) {
        pre = cur;
        cur = cur->next;
    }
    ListNode<T>* new_node = new ListNode<T>(value);
    new_node->next = cur;
    pre->next = new_node;

    return dummy->next;
}

// 两个链表有序,并且right中元素大于left中元素
template<typename T>
ListNode<T>* merge(ListNode<T>* left, ListNode<T>* right) {
    if (nullptr == left) {
        return right;
    }
    else if (nullptr == right) {
        return left;
    }
    ListNode<T>* head = left;
    while (nullptr != left->next) {
        left = left->next;
    }
    left->next = right;
    return head;
}

// 当前桶的元素范围和数组长度一样
template<typename T>
void bucket_sort(T arr[], int len) {
    vector<ListNode<T>*> buckets(len, nullptr);
    ListNode<T>* head;
    for (int i = 0; i < len; ++i) {
        int index = arr[i] / len;
        head = buckets.at(index);
        buckets.at(index) = insert(head, arr[i]);
    }
    head = buckets.at(0);
    for (int i = 1; i < len; ++i) {
        head = merge(head, buckets.at(i));
    }
    for (int i = 0; i < len; ++i) {
        arr[i] = head->value;
        head = head->next;
    }
}

10. 基数排序

先计算最大位数d,对每一位进行计数排序(从右往左),经过d轮计数排序后,排序完成。

基数排序捅的数量是10,元素后进先出。

例如最大位数是3,先排序个位,在排序十位的时候由于稳定性,个位也是有序的,排序百位的时候十位个位都是有序的。

该算法将对整数的排序转为对整数的每一位进行计数排序。

template<typename T>
int get_max_digit(T arr[], int len) {
    if (len < 1) {
        return 1;
    }

    T maxValue = arr[0];
    for (int i = 1; i < len; ++i) {
        if (arr[i] > maxValue) {
            maxValue = arr[i];
        }
    }
    int digit = 1;
    int j = 10;
    while (maxValue / j) {
        ++digit;
        j *= 10;
    }
    return digit;
}

//template<typename T>
// 基数排序的桶相当于一个栈,元素后进先出,所以填充辅助数组的时候从后往前填充(len-1 -> 0)
void radix_sort(int arr[], int len) {
    int max_digit = get_max_digit(arr, len);
    int count_arr[10];
    int* tmp_arr = new int[len];
    int radix = 1;
    for (int d = 0; d < max_digit; ++d) {
        for (int i = 0; i < 10; ++i) {
            count_arr[i] = 0;
        }
        for (int i = 0; i < len; ++i) {
            int k = arr[i] / radix % 10;
            ++count_arr[k];
        }
        for (int i = 1; i < 10; ++i) {
            count_arr[i] += count_arr[i - 1];
        }
#if 0
        // 正向填充辅助数组的话就破坏了稳定性
        //for (int i = 0; i < len; ++i) {
#else
        for (int i = len - 1; i >= 0; --i) {
#endif
            int j = arr[i] / radix % 10;
            tmp_arr[count_arr[j] - 1] = arr[i];
            --count_arr[j];
        }
        for (int i = 0; i < len; ++i) {
            arr[i] = tmp_arr[i];
        }
        radix *= 10;
    }
    delete[] tmp_arr;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值