C++实现排序 - 03 计数排序、桶排序和基数排序

简介: 今天我们继续来整理与 O(n+k) 有关的三个排序算法,即计数排序、桶排序和基数排序。
写在前面:
今天我们继续来整理与 O(n+k) 有关的三个排序算法,即计数排序、桶排序和基数排序。
排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 稳定性
计数排序 O(n+k) O(n+k) O(n+k) O(k) 稳定
桶排序 O(n+k) O(n+k) O(n^2^) O(n+k) 稳定
基数排序 O(n×k) O(n×k) O(n×k) O(n+k) 稳定

计数排序

计数排序是去统计每个值在数组中的数量,然后依次放在它们应该在的位置,所以这个算法不适合数组中的值过大,因为这是用空间来换时间。并且,计数排序也不适合按照字符串进行排序。算法步骤如下:

  1. 找到数组中最大的值,进行初始化操作。
  2. 统计数组中个数值的个数。
  3. 对所有的计数进行累加(等于求前缀和)。
  4. 将每个元素放到新的数组中去。

直接上图:

我们对数组先进行遍历,统计每个数值的个数。

1.jpg

2.jpg

3.jpg

4.jpg

5.jpg

6.jpg

7.jpg

8.jpg

9.jpg

10.jpg

统计完数量后,计算前缀和:

11.jpg

然后对每个元素的位置进行推断,可能会有小伙伴比较疑惑为什么要弄的这么麻烦,直接从前往后根据下标输出不就好了吗。这是因为每个元素都是一个个体,如果遇到相同元素为了保证稳定性,即排序后相同元素前后次序和原数组中的前后顺序要保持一致。

为了避免数据混乱即保持稳定性,我们从后往前进行遍历。每次都将元素对应的 nums 值减 1 ,然后从后往前赋值给 dis 数组(用来保存最终元素位置)。现在可能听起来会有点晕,我们直接看图。

12.jpg

13.jpg

14.jpg

15.jpg

16.jpg

17.jpg

18.jpg

19.jpg

20.jpg

21.jpg

22.jpg

至此,dis 数组更新完毕,我帮你将 dis 数组和 arr 数组放在一起。

23.jpg

现在是不是更清楚了,我们只用根据得到的 dis 数组将原数组中的元素放在 dis 数组中要求放的位置即可。

24.jpg

#include <bits/stdc++.h>
using namespace std;

//计数排序
void countSort(int arr[], int size, int maxnum) {
    int m = maxnum + 1;
    vector<int> count(m);    //计算数据的个数
    vector<int> nums(m);    //计算每个位置与前面的数据个数总和
    vector<int> dis(size);    //记录数据位置信息
    vector<int> newarr(size);    //储存排序后的数组

    //计算数据个数
    for (int i = 0; i < size; i++)
        count[arr[i]]++;

    //计算数据总和
    nums[0] = count[0];
    for (int i = 1; i < m; i++)
        nums[i] += nums[i - 1] + count[i];

    //推断数据位置信息
    for (int i = size - 1; i >= 0; i--) {
        //从后往前查找,避免数据顺序混乱
        int x = arr[i];
        nums[x]--;
        dis[i] = nums[x];
    }

    //数据排序
    for (int i = 0; i < size; i++)
        newarr[dis[i]] = arr[i];

    //输出数组
    for (int i = 0; i < size; i++)
        cout << newarr[i] << " ";
}

int main() {
    int arr[10] = { 2, 3, 1, 8, 5, 10, 4, 3, 9, 6 };
    int size = 10, maxnum = 10;
    countSort(arr, size, maxnum);
    return 0;
}

桶排序

桶排序就是创建若干个桶,每个桶代表一个区间,然后将数组中的元素放入对应的桶中。算法步骤如下:

  1. 具体要建立多少个桶根据元素的最大和最小值而定,我们这里采用如下公式确定。

区间大小 = (最大值 - 最小值)/(通的数量 - 1)

  1. 遍历原始数组,根据区间将元素放入对应的桶中。
  2. 将每个桶内的元素进行排序。
  3. 遍历所有的桶,将所有元素串起来输出。

不过因为桶排序不是很重要,计数排序和基数排序才是重点。我们下面实现的是最简单的方法,即直接根据元素最大值创建相应的桶,并且遍历进行计数,最后输出。由于简单的实现方法比较易懂,我就直接上代码啦~

#include <bits/stdc++.h>
using namespace std;

//计数排序
void bucketSort(int arr[], int size, int maxnum) {
    vector<int> newarr(size);
    vector<int> bucket(maxnum + 1, 0);

    //进行计数
    for (int i = 0; i < size; i++)
        bucket[arr[i]]++;

    //进行输出
    for (int i = 0; i < maxnum; i++) {
        while (bucket[i]) {
            cout << i << " ";
            bucket[i]--;
        }
    }
}

int main() {
    int arr[10] = { 2, 3, 1, 8, 5, 10, 4, 3, 9, 6 };
    int size = 10, maxnum = 10;
    bucketSort(arr, size, maxnum);
    return 0;
}

基数排序

基数排序是一种多关键字的排序算法,算法步骤如下:

  1. 找到数组中最大的元素,并获得其位数。
  2. 从个位开始到最大元素最高位,对每个数根据每个位进行排序。
  3. 每次排序完之后,都从对每个位排好序的数组中取出来连成一串,最终得到有序的序列。

直接上图:

假设初始数组为 { 10 , 278 , 109 , 63 , 930 , 589 , 184 , 505 , 269 , 8 , 83 } 。

第一步:获得数组元素中最大的元素即 930 ,计算其位数为 3 。

未命名文件 (1).jpg

第二步:对每个元素的个数进行排序,下面遇到相同位置的元素我们用链表串起来,并且用尾插法进行插入。

未命名文件 (2).jpg

未命名文件 (3).jpg

未命名文件 (4).jpg

未命名文件 (5).jpg

未命名文件 (6).jpg

未命名文件 (7).jpg

未命名文件 (8).jpg

未命名文件 (9).jpg

未命名文件 (10).jpg

未命名文件 (11).jpg

未命名文件 (12).jpg

第三步:个位数排序已完成,将数组中元素依次输出,连成一串。

未命名文件 (13).jpg

第四步:对十位数进行排序。

未命名文件 (14).jpg

第五步:十位数排序已完成,将数组中元素依次输出,连成一串。

未命名文件 (15).jpg

第六步:对百位数进行排序。

未命名文件 (16).jpg

第七步:百位数排序已完成,将数组中元素依次输出,连成一串。

未命名文件 (17).jpg

至此,排序位数已经是最大数字的最大位数即 3 ,故排序结束,输出数组。

下面代码部分我们加入了对负数进行排序的操作,如果数组中存在负数,则将数组中所有元素先加上数组中最小负数的绝对值再进行基数排序(因为基数排序用的数组下标都必须大于零),最后输出的时候再减去最小负数的绝对值。

另外,我们对每一趟排序都进行了输出方便大家观察结果。

#include <bits/stdc++.h>
using namespace std;

struct Node {
    int val = INT_MAX;
    Node *next = NULL;
};

int res, minx;
void Sort(int a[], Node *arr[]) {
    int cnt = 0;
    //将下标数组中的元素取出排成一列
    for (int i = 0; i < 10; i++) {
        Node *temp = arr[i]->next;
        while (temp != NULL) {
            a[cnt++] = temp->val;
            temp = temp->next;
        }
    }
    //将下标数组初始化
    for (int i = 0; i < 10; i++) {
        arr[i]->next = NULL;
    }
    //如果原数组中存在负数,则还原数组
    if (minx < 0) {
        cout << a[0] - res;
        for (int i = 1; i < cnt; i++)
            cout << " " << a[i] - res;
    } else {
        cout << a[0];
        for (int i = 1; i < cnt; i++)
            cout << " " << a[i];
    }
    cout << endl;
}

//桶排序
void BarrelSort(int a[], Node *arr[], int size, int flag) {
    int x = 1;
    //判断是对哪个位置进行排序
    while (flag != 1) {
        x *= 10;
        flag--;
    }
    //进行对应位置的排序
    for (int i = 0; i < size; i++) {
        Node *node = new Node;
        node->val = a[i];
        Node *temp;
        temp = arr[a[i] / x % 10];
        while (temp->next != NULL) {
            temp = temp->next;
        }
        node->next = temp->next;
        temp->next = node;
    }
    //输出排好序的桶
    for (int i = 0; i < 10; i++) {
        cout << i << ":";
        if (arr[i]->next == NULL) {
            cout << "NULL" << endl;
            continue;
        }
        Node *temp = arr[i]->next;
        while (temp != NULL) {
            cout << "->" << temp->val;
            temp = temp->next;
        }
        cout << "->^" << endl;
    }
    Sort(a, arr);
}

//基数排序
void cardinalSort(int a[], int size, int k) {
    Node *arr[10];    //根据下标大小存放节点
    //初始化数组
    for (int i = 0; i < 10; i++) {
        Node *node = new Node;
        arr[i] = node;
    }
    //对数字的每一位进行桶排序
    for (int i = 1; i <= k; i++)
        BarrelSort(a, arr, size, i);
}

int main() {
    int t;
    cin >> t;    //输入组数
    while (t--) {
        int n, k = 1;
        cin >> n;    //输出元素数量
        int *a = new int[n];
        minx = 0;
        //找到数组中最小的数
        for (int i = 0; i < n; i++) {
            cin >> a[i];
            if (a[i] < minx)
                minx = a[i];
        }
        //如果数组中最小的数为负数,则所有数都加上该最小数的绝对值,为了能够后续的基数排序
        if (minx < 0) {
            res = abs(minx);
            for (int i = 0; i < n; i++)
                a[i] = a[i] + res;
        }
        //找到数组中长度最大的数
        for (int i = 0; i < n; i++) {
            int temp = a[i], cnt = 0;
            while (temp) {
                cnt++;
                temp = temp / 10;
            }
            k = max(k, cnt);
        }
        cardinalSort(a, n, k);
        cout << endl;
    }
    return 0;
}

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

目录
相关文章
|
9月前
|
人工智能 算法 测试技术
【数学】【排序】【C++算法】3027人员站位的方案数
【数学】【排序】【C++算法】3027人员站位的方案数
|
1月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
36 10
|
1月前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
53 10
|
1月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
41 7
|
1月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
38 5
|
9月前
|
算法 搜索推荐 C++
【C++】sort()、stable_sort()和partial_sort()排序函数详解
【C++】sort()、stable_sort()和partial_sort()排序函数详解
251 0
|
8月前
|
C++ 容器
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
|
8月前
|
算法 搜索推荐 C++
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
|
9月前
|
存储 搜索推荐 算法
C++桶排序的实现
C++桶排序的实现
|
9月前
|
算法 测试技术 C#
【模拟】【C++算法】2826. 将三个组排序
【模拟】【C++算法】2826. 将三个组排序