16----桶计数法

一、桶计数法:

        桶计数法(Bucket Counting)是一种常用的计数排序算法。它将待排序的元素分布到有限数量的桶(或容器)中,然后对每个桶中的元素进行排序(如果是一个桶,也可以不排),最后按照桶的顺序依次输出所有元素,即可得到排序结果。

        基本思想:假设待排序的元素是随机分布在一个已知范围内的整数,我们可以将这个范围划分为若干个大小相等的子区间,每个子区间对应一个桶。然后依次遍历待排序的元素,将每个元素放入对应的桶中。接着对每个桶中的元素进行排序,可以使用其他排序算法(如插入排序、快速排序等)或递归地应用桶计数法。最后按照桶的顺序,依次输出每个桶中的元素,即可得到排序结果。

        桶计数法的时间复杂度取决于桶的数量和桶内元素的排序算法。如果桶的数量较大且每个桶内的元素较少,那么桶计数法的时间复杂度可以接近线性时间复杂度O(n)。但是,如果桶的数量较少或者每个桶内的元素较多,那么桶计数法的时间复杂度可能会接近O(nlogn)。

        需要注意的是,桶计数法适用于待排序的元素满足一定的分布特性,例如均匀分布或近似均匀分布。如果元素的分布不均匀,可能会导致某些桶内的元素较多,从而影响排序的效率。因此,在使用桶计数法时,需要根据具体情况选择合适的桶的数量和分布方式,以获得较好的排序性能。

#include <stdio.h>

// 桶计数排序函数
void bucketSort(int arr[], int n) {
    // 确定最大值和最小值
    int min_val = arr[0];
    int max_val = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < min_val) {
            min_val = arr[i];
        }
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }
    
    // 计算桶的数量
    int bucket_size = 5;
    int bucket_count = (max_val - min_val) / bucket_size + 1;
    
    // 创建桶
    int buckets[bucket_count][n];
    int bucket_sizes[bucket_count];
    for (int i = 0; i < bucket_count; i++) {
        bucket_sizes[i] = 0;
    }
    
    // 将元素放入桶中
    for (int i = 0; i < n; i++) {
        int index = (arr[i] - min_val) / bucket_size;
        buckets[index][bucket_sizes[index]] = arr[i];
        bucket_sizes[index]++;
    }
    
    // 对每个桶中的元素进行排序(可以使用其他排序算法)
    for (int i = 0; i < bucket_count; i++) {
        for (int j = 0; j < bucket_sizes[i]; j++) {
            for (int k = 0; k < bucket_sizes[i] - j - 1; k++) {
                if (buckets[i][k] > buckets[i][k + 1]) {
                    int temp = buckets[i][k];
                    buckets[i][k] = buckets[i][k + 1];
                    buckets[i][k + 1] = temp;
                }
            }
        }
    }
    
    // 输出排序结果
    int index = 0;
    for (int i = 0; i < bucket_count; i++) {
        for (int j = 0; j < bucket_sizes[i]; j++) {
            arr[index] = buckets[i][j];
            index++;
        }
    }
}

// 测试
int main() {
    int arr[] = {29, 10, 14, 37, 13, 25, 16, 22};
    int n = sizeof(arr) / sizeof(arr[0]);
  
    bucketSort(arr, n);
  
    printf("排序结果:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
  
    return 0;
}

/*
上述代码中,首先确定待排序数组的最大值和最小值,然后根据桶的大小确定桶的数量。接下来创建二维数组`buckets`作为桶,并创建一维数组`bucket_sizes`记录每个桶中元素的数量。然后将待排序数组中的元素根据值的范围放入对应的桶中。接着对每个桶中的元素进行排序,这里使用了冒泡排序算法进行示例。最后将排序后的元素依次放回原数组中,即可得到排序结果。在示例中,桶的大小为5,所以元素范围在0-40之间,共计8个元素,被分为了8个桶。最终的排序结果为10 13 14 16 22 25 29 37。
*/

二、其他应用:

        除了利用桶计数法来排序以外,还可以用来记录输入,寻找相关规律。

        下面我们以两道例题为主来进行相关讲解:

1:最长平台

题目描述

对于一个数组,其连续的相同段叫做一个平台,例如,在 1,2,2,3,3,3,4,5,5,6 中 1,2−2,3−3−3,4,5−5,6 都是平台。

编写一个程序,接收一个数组,找出最长的平台。在上面的例子中 3−3−3 就是最长的平台。

输入格式

第一行有一个整数 n,为数组元素的个数。(1≤n≤100)

第二行有 n 个整数,整数之间以一个空格分开,整数 k 范围(0<k<2000)。

输出格式

输出最长平台的长度。

输入输出样例

输入 

10
1 2 2 3 3 3 4 5 5 6

输出 

3

        显然这道题就可以利用桶计数法来快速实现,定义一个数组,将它视为桶组,然后遍历输入,将相同的数存到同一个桶中,从而记录出与当前数相同的数的个数,然后再循环遍历找到装的数最多的桶即可。

//最长平台
#include<iostream>
using namespace std;
int main(void)
{
	int n;
	cin >> n;

	int a[101] = { 0 };
	int b[101] = { 0 };

	for (int i = 0; i < n; i++)
	{
		cin >> a[i];		
	}

	int j = 0;
	for (int i = 1; i < n; i++)
	{
		if (a[i] == a[i - 1]) b[j] ++;
		else j++;
	}
	
	int max=b[0];
	for (int i = 0; i < j; i++)
	{
		
		if (b[i] > max) max = b[i];
	}
	cout<<max+1;
	return 0;

}

        最后max+1是因为存入桶的只是与当前数相同的数的个数,还要加上其本身。

2:整数去重

题目描述

给定含有 n 个整数的序列,要求对这个序列进行去重操作。所谓去重,是指对这个序列中每个重复出现的数,只保留该数第一次出现的位置,删除其余位置。

输入格式

输入包含两行:

第一行包含一个正整数 n(1≤n≤20000),表示第二行序列中数字的个数;

第二行包含 n 个整数,整数之间以一个空格分开。每个整数大于等于 10 、小于等于 100。

输出格式

输出只有一行,按照输入的顺序输出其中不重复的数字,整数之间用一个空格分开。

输入输出样例

输入 

5
10 12 93 12 75

输出 

10 12 93 75

        很明显,这道题也可以利用桶计数法:每读入一个数,就去判断存放该数的桶是否为空,若为空,则存放并记录;否则 只存放不记录(也可以不存放,但存放有一定的好处,可以同时记录所有数字的输入次数)。最后输出即可

//整数去重

#include<iostream>
using namespace std;
int main(void)
{
    int n;
    cin >> n;

    int a[20001] = { 0 };
    int b[20001] = { 0 };
    int c[20001] = { 0 };
    int j = 0;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        if (b[a[i]] == 0) c[j++] = a[i];
        b[a[i]]++;
        
    }

    for (int i = 0; i < j; i++)
    {
        cout<<c[i]<<" ";
    }

    
    return 0;

}

/*

赋值号和等于号不要混淆

=       ==
*/

        下面这个代码就是统计输入中每个数字出现的次数:(从0到max)

#include<iostream>
using namespace std;
int main(void)
{
    int n;
    cin >> n;
    int a[100001] = { 0 };
    int b[100001] = { 0 };
    int max;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];

        if(i==0)  max = a[0];    

        if (max < a[i]) max = a[i];

        b[a[i]]++;
    }

    for (int i = 0; i <= max; i++)
    {
        cout << b[i] << endl;
    }

    return 0;

}

<think>嗯,用户想要找C++相关的笔试和面试题,特别是第2到25题。不过根据提供的参考引用,里面只提到了第一个问题关于C++和C的区别,后面的题目可能没显示出来。首先,我需要确认用户的需求是不是需要我根据现有的资料生成更多题目,或者是否有其他来源的信息可用。 不过用户可能没有意识到参考内容有限,所以需要我根据常见的C++面试题来补充2到25题。接下来,我需要回忆常见的C++面试问题,涵盖基础概念、面向对象、内存管理、STL、新特性等方面,确保覆盖全面且难度适中。 然后,我要按照用户的要求,使用中文回答,结构清晰,分步骤解释每个问题,并提供详细的解析。同时,注意数学公式的格式是否正确,比如使用$...$和$$...$$。虽然大部分C++问题可能不涉及数学公式,但如果有相关的内容,比如算法复杂度,就需要正确应用格式。 另外,用户提到要生成相关问题,可能需要我在回答完问题后,再提出一些扩展问题,帮助用户深入理解。不过根据示例,用户可能希望在每个问题解析后添加相关问题,或者最后统一添加。需要根据示例结构来处理。 最后,检查所有回答是否符合LaTeX语法,确保没有格式错误,同时引用标记正确。可能需要在每个引用的地方添加[^1],但根据参考内容,只有引用[1]可用,所以需要确认是否需要其他引用来源,或者仅使用现有的。</think>以下是整理的C++笔试/面试题第2到25题及详细解析,涵盖常见核心知识点: --- ### **2. 指针和引用的区别是什么?** - **指针**是变量,存储内存地址,可重新指向其他对象,允许空值(`nullptr`),需要解引用操作符`*`访问值。 - **引用**是别名,必须在初始化时绑定对象,不能重新绑定,不能为空,直接通过名称访问对象。 ```cpp int a = 10; int *p = &a; // 指针 int &r = a; // 引用 ``` --- ### **3. `const`和`constexpr`的区别** - `const`表示“只读”,运行时确定值(如`const int x = func();`)。 - `constexpr`表示编译期常量,要求表达式可在编译时求值(如`constexpr int y = 42;`)。 --- ### **4. 虚函数如何实现多态?** 通过虚函数表(vtable)和虚函数指针(vptr)实现。每个包含虚函数的类有一个vtable,存储函数指针;对象通过vptr访问vtable,实现动态绑定。 --- ### **5. 堆和栈的内存分配区别** | **特征** | **堆** | **栈** | |----------------|---------------------------|---------------------| | 分配方式 | 手动分配(`new/malloc`) | 自动分配(编译器) | | 内存大小 | 较大,动态扩展 | 较小,固定 | | 访问速度 | 较慢 | 较快 | | 生命周期 | 手动释放 | 作用域结束自动释放 | --- ### **6. 什么是深拷贝和浅拷贝?** - **浅拷贝**:仅复制指针,新旧对象指向同一内存。 - **深拷贝**:复制指针指向的内容,新旧对象独立。 ```cpp class String { public: char* data; // 深拷贝构造函数 String(const String& other) { data = new char[strlen(other.data) + 1]; strcpy(data, other.data); } }; ``` --- ### **7. `new`和`malloc`的区别** | **特征** | `new` | `malloc` | |----------------|---------------------------|-----------------------| | 类型安全 | 是 | 否 | | 调用构造函数 | 是 | 否 | | 返回类型 | 具体类型指针 | `void*` | | 内存大小计算 | 自动 | 手动指定 | | 失败处理 | 抛出异常 | 返回`NULL` | --- ### **8. 什么是RAII?** RAII(资源获取即初始化)通过对象的生命周期管理资源(如内存、文件句柄)。资源在构造函数中获取,在析构函数中释放,避免泄漏。 ```cpp class FileHandle { public: FileHandle(const char* filename) { file = fopen(filename, "r"); } ~FileHandle() { if (file) fclose(file); } private: FILE* file; }; ``` --- ### **9. `std::vector`的底层实现** `std::vector`使用动态数组,元素连续存储。当容量不足时,重新分配更大的内存(通常2倍扩容),并拷贝原有元素。 --- ### **10. 智能指针类型及用途** - `unique_ptr`:独占所有权,不可拷贝,可移动。 - `shared_ptr`:共享所有权,引用计数。 - `weak_ptr`:解决`shared_ptr`循环引用问题,不增加计数。 --- ### **11. 什么是右值引用?** 右值引用(`T&&`)用于标识临时对象,支持移动语义(转移资源而非拷贝),提升性能。 ```cpp std::string s1 = "Hello"; std::string s2 = std::move(s1); // s1资源转移到s2 ``` --- ### **12. 虚析构函数的作用** 若基类指针指向派生类对象,基类析构函数非虚时,删除指针会导致派生类析构函数未调用,引发内存泄漏。虚析构函数确保正确调用所有析构函数。 --- ### **13. `override`和`final`关键字** - `override`:显式标记重写虚函数,增强代码可读性。 - `final`:禁止类被继承或虚函数被重写。 ```cpp class Base { public: virtual void func() {} }; class Derived : public Base { public: void func() override final {} // 正确重写且禁止进一步重写 }; ``` --- ### **14. 模板元编程的应用场景** - 编译期计算(如斐波那契数列)。 - 类型检查与转换(`std::is_integral`)。 - 优化代码(避免运行时开销)。 --- ### **15. `volatile`关键字的作用** 告诉编译器变量可能被外部修改(如硬件寄存器),禁止优化相关代码,确保每次访问都从内存读取。 --- ### **16. 什么是SFINAE?** “替换失败不是错误”原则,用于模板元编程中通过条件启用/禁用特定重载。 ```cpp template<typename T> auto foo(T t) -> decltype(t.size(), void()) { // 仅当T有size()方法时启用 } ``` --- ### **17. Lambda表达式的组成部分** ```cpp [capture](params) mutable -> ret_type { body } ``` - **捕获列表**:`[=]`(值捕获)、`[&]`(引用捕获)。 - **参数列表**:与普通函数参数类似。 - **可变规范**:`mutable`允许修改值捕获的变量。 - **返回类型**:可自动推导或显式指定。 --- ### **18. `std::unordered_map`的冲突解决** 使用开链法(每个存储链表),当哈希冲突时,元素添加到同一的链表中。 --- ### **19. 什么是移动构造函数?** 移动构造函数通过“窃取”资源(如动态内存)初始化新对象,避免深拷贝,参数为右值引用。 ```cpp class Vector { public: Vector(Vector&& other) : data(other.data), size(other.size) { other.data = nullptr; // 置空原对象指针 } }; ``` --- ### **20. `noexcept`关键字的用途** 标记函数不会抛出异常,帮助编译器优化代码。若标记为`noexcept`的函数抛出异常,程序终止。 --- ### **21. 类型推导规则(`auto`和`decltype`)** - `auto`:推导变量类型,忽略顶层`const`和引用。 - `decltype`:保留表达式类型(包括引用和`const`)。 ```cpp int a = 10; const int& b = a; auto c = b; // c类型为int decltype(b) d = a; // d类型为const int& ``` --- ### **22. 什么是CRTP?** 奇异递归模板模式(Curiously Recurring Template Pattern),通过基类模板派生类实现静态多态。 ```cpp template<typename T> class Base { public: void interface() { static_cast<T*>(this)->impl(); } }; class Derived : public Base<Derived> { public: void impl() { /* ... */ } }; ``` --- ### **23. `std::async`的启动策略** - `std::launch::async`:异步执行任务。 - `std::launch::deferred`:延迟执行(调用`get()`时执行)。 - 默认策略为两者组合,由系统决定。 --- ### **24. 什么是内存对齐?** 数据在内存中的地址需为特定值的倍数(如4字节对齐),以提升CPU访问效率。可通过`alignas`指定对齐方式。 ```cpp struct alignas(16) AlignedStruct { float data[4]; }; ``` --- ### **25. C++17新特性举例** - 结构化绑定:`auto [x, y] = std::make_pair(1, 2);` - `if constexpr`:编译期条件判断。 - 内联变量:允许头文件中定义`inline`变量。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐远方的梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值