C++学习笔记(第七章 自定义数据类型 动态链表应用 之四)

【题目7-动态链表体验】

     下面是一个建立动态链表的程序。阅读程序,在草稿纸上画出链表建立的过程,借此学会如何建立链表。然后改造程序,完成题目7的要求

<span style="font-size:14px;">#include  <iostream>
using namespace std;
struct Node
{
    int data;            //结点的数据
    struct Node *next;  //指向下一结点
};
Node *head=NULL;    //将链表头定义为全局变量,以便于后面操作
void make_list();   //建立链表
void out_list();    //输出链表
  
int main( )
{
    make_list();
    out_list();
    return 0;
}
void make_list()
{
    int n;
    Node *p;
    cout<<"输入若干正数(以0或一个负数结束)建立链表:"
    cin>>n;
    while(n>0)   //输入若干正数建立链表,输入非正数时,建立过程结束
    {
        p=new Node;  //新建结点
        p->data=n;   
        p->next=head;  //新建的结点指向原先的链表头
        head=p;    //链表头赋值为新建的节点,这样,新结点总是链表头
        cin>>n;    //输入下一个数,准备建立下一个结点
    }
    return;
}
void out_list()
{
    Node *p=head;
    cout<<"链表中的数据为:"<<endl;
    while(p!=NULL)
    {
        cout<<p->data<<" ";
        p=p->next;
    }
    cout<<endl;
    return;
}</span>

 

 在上面的程序基础上定义下面的函数,实现相应的功能。为简便起见,每编写一个函数,立刻在main函数中调用进行测试。

  (1)编写make_list2()函数建立链表,使建立链表时,后输入的数据,将新输入的数字对应的结点放在链表末尾。若输入为3 5 2 9 4 7 0,建立的链表为:

     



2)编写函数void search(int x),输出链表中是否有值为x的结点。


3)编写函数delete_first_node(),删除链表中的第一个结点。


4)编写函数delete_node(int x),删除结点值为x的结点。


5)编写make_list3()函数建立有序链表,使建立链表时,结点中的数据呈现升序。若输入为3 5 2 9 4 7 0,建立的链表为:


6)编写函数void insert(int x),将值为x的结点插入到由make_list3建立起来的有序链表中。

<think>好的,我现在需要为用户准备一份关于C++容器的超级详细笔记。首先,我得回忆一下C++标准库中的容器种类,确保覆盖所有内容。标准容器主要分为顺序容器、关联容器、无序关联容器和容器适配器这几大类。 顺序容器包括vector、list、deque、array、forward_list和string。我得逐一分析它们的特点、内部实现、常用操作和时间复杂度。比如,vector是动态数组,支持随机访问,尾部插入高效,中间插入可能涉及元素移动。而list是双向链表,插入删除快,但随机访问慢。需要对比它们的优缺点,帮助用户选择适的情境。 接下来是关联容器,比如set、multiset、map、multimap。这些通常是基于红黑树实现的,所以元素是有序的。需要解释键的唯一性,multiset和multimap允许重复键的情况。操作如插入、删除、查找的时间复杂度都是对数级别,这点很重要。 然后是C++11引入的无序关联容器,unordered_set、unordered_multiset、unordered_map、unordered_multimap。这些基于哈希表实现,元素无序,但查找效率平均情况下是常数时间。需要讨论哈希函数的重要性,冲突处理的方法,比如链地址法,以及负载因子的影响。 容器适配器部分包括stack、queue和priority_queue。它们不是独立的容器,而是基于其他容器实现的接口。比如stack默认用deque实现,也可以指定vector或list。需要说明它们的行为,比如LIFO、FIFO,以及优先队列的堆结构。 接下来要考虑各个容器的常用成员函数,比如size(), empty(), begin(), end(), insert(), erase()等。对于特定容器,如vector的push_back(), capacity(), reserve(), resize();map的operator[]和insert的区别,以及如何正确使用emplace方法提高效率。 内存管理也是一个重点,尤其是vector的动态扩容机制。当元素数量超过容量时,vector会重新分配内存,通常容量翻倍,这可能导致迭代器失效。需要提醒用户注意在迭代过程中修改容器可能引发的错误。 异常安全性方面,不同容器在操作时的异常保证不同。比如vector的push_back在发生异常时保持强异常安全,而某些操作可能只有基本保证。需要指出这些细节,帮助用户编写健壮的代码。 迭代器失效的情况是另一个关键点。例如,在vector中间插入元素会导致后面的迭代器失效;而删除元素时,被删除元素的迭代器及其之后的都会失效。对于list这样的链表结构,插入删除通常不会影响其他元素的迭代器,除非直接删除当前元素。 性能比较方面,需要制作表格或列表,对比不同容器在插入、删除、查找等操作上的时间复杂度,帮助用户根据具体需求选择最适的容器。例如,如果需要频繁查找,关联容器或无序容器可能更适;而频繁在头部插入则可能选择list或deque。 C++11及以后版本的新特性也需要涵盖,比如emplace系列函数,移动语义对容器的影响,以及智能指针在容器中的使用。比如使用emplace_back可以直接在容器中构造对象,避免不必要的拷贝。 应用场景举例部分,可以通过实际例子说明如何选择容器。例如,需要维护一个有序且快速查找的数据集,可以使用set;需要键值对快速查找且不关心顺序,使用unordered_map;需要双端队列操作则选择deque。 最后,总结最佳实践,比如预分配vector的容量以减少重新分配次数,避免在循环中频繁查询容器大小,使用适当的算法(如sort、find)结容器特性提升效率。同时提醒常见陷阱,如迭代器失效、未初始化的访问等。 需要检查是否有遗漏的容器或新特性,比如C++17引入的std::string_view虽然不属于容器,但可能和string有关联。另外,容器相关的非成员函数,比如swap,erase_if(C++20)等也值得提及。 确保内容准确无误,参考最新的C++标准(如C++20),必要时比较不同版本间的差异。最后组织内容结构,使其逻辑清晰,便于用户理解和查阅。</think>以下为关于C++容器的超级详细笔记,按模块化结构组织,包含数学公式和代码示例: # C++标准库容器完全指南 $\boxed{\text{最新标准:C++20 核心特性全覆盖}}$ ## 一、容器分类体系 $$ \text{容器} \begin{cases} \text{顺序容器} \\ \text{关联容器} \\ \text{无序关联容器} \\ \text{容器适配器} \end{cases} $$ ### 1.1 顺序容器特性对比 | 容器 | 数据结构 | 访问方式 | 插入效率 | 内存布局 | |---------------|----------------|----------|------------------------|----------| | vector | 动态数组 | 随机访问 | $O(1)$尾部/$O(n)$中间 | 连续 | | deque | 分块数组 | 随机访问 | $O(1)$头尾 | 伪连续 | | list | 双向链表 | 双向迭代 | $O(1)$任意位置 | 非连续 | | forward_list | 单向链表 | 前向迭代 | $O(1)$ | 非连续 | | array | 固定数组 | 随机访问 | 不可插入 | 连续 | | string | 字符专用容器 | 随机访问 | $O(1)$尾部 | 连续 | ### 1.2 关联容器红黑树实现 对于有序集/映射: $$ \text{查找时间复杂度} = O(\log n) $$ $$\text{平衡因子} \Rightarrow \text{树高度} \leq 2\log_2(n+1)$$ ### 1.3 哈希表实现原理 无序容器负载因子: $$ \lambda = \frac{\text{元素数量}}{\text{桶数量}} $$ 当$\lambda > \text{max_load_factor()}$时自动rehash ## 二、核心容器深度解析 ### 2.1 vector内存管理 扩容策略: $$ \text{新容量} = \max(\text{需求容量}, \text{当前容量} \times \text{增长因子}) $$ 多数实现采用$growth\_factor = 2$ ```cpp vector<int> v; v.reserve(100); // 直接分配100元素空间 while(v.size() < 100) { v.emplace_back(rand()%100); // 无重新分配 } ``` ### 2.2 map的键排序特性 对于自定义类型作为key: ```cpp struct Point { int x, y; bool operator<(const Point& other) const { return std::tie(x, y) < std::tie(other.x, other.y); } }; std::map<Point, std::string> location_map; ``` ### 2.3 unordered_map自定义哈希 ```cpp struct MyHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; std::unordered_map<Point, std::string, MyHash> custom_hash_map; ``` ## 三、时间复杂度大全表 $$ \begin{array}{c|cccc} \text{操作/容器} & \text{vector} & \text{list} & \text{map} & \text{unordered\_map} \\ \hline \text{随机访问} & O(1) & O(n) & - & - \\ \text{头插入} & O(n) & O(1) & - & - \\ \text{尾插入} & O(1) & O(1) & - & - \\ \text{查找} & O(n) & O(n) & O(\log n) & O(1)^{*} \\ \text{删除} & O(n) & O(1) & O(\log n) & O(1)^{*} \end{array} $$ (* 平均情况) ## 、迭代器失效规则 重要法则: $$ \text{修改操作} \Rightarrow \begin{cases} \text{vector/deque} & \text{插入导致capacity变化⇒全部失效} \\ \text{list/map} & \text{仅被删除元素迭代器失效} \\ \text{unordered容器} & \text{rehash后全部失效} \end{cases} $$ ## 五、高级技巧与最佳实践 ### 5.1 移动语义优化 ```cpp std::vector<std::string> createStrings() { std::vector<std::string> temp; // ...填充数据 return temp; // NRVO优化或移动构造 } ``` ### 5.2 类型萃取应用 ```cpp template<typename T> void smartReserve(std::vector<T>& v, size_t n) { if constexpr(std::is_nothrow_move_constructible_v<T>) { v.reserve(n); // 安全扩容 } else { // 使用更安全但较慢的扩容策略 } } ``` ### 5.3 视图类型(C++20) $$ \text{string\_view} \subset \text{span} \subset \text{range} $$ ```cpp std::vector<int> data{1,2,3,4,5}; std::span<int> window(data.data()+1, 3); // 无所有权视图 ``` ## 六、性能关键点 ### 6.1 元素访问效率对比 $$ \text{cache命中率} \propto \frac{1}{\text{访问时间}} $$ 实验数据: ``` 连续容器 vs 节点容器 L1 cache访问时间 ≈ 0.5ns RAM访问时间 ≈ 100ns ``` ### 6.2 预分配策略比较 $$\text{vector.resize() vs reserve()}$$ - resize():立即构造对象 - reserve():仅分配内存 ## 七、特殊容器应用 ### 7.1 bitset位操作 $$ \text{存储效率} = \frac{\text{位数}}{sizeof(bitset)*8} \times 100\% $$ ```cpp constexpr size_t N = 1024; std::bitset<N> flags; flags.set(42); // 置位第42位 ``` ### 7.2 多索引容器 使用Boost.MultiIndex或自定义: ```cpp std::map<int, std::string> primary; std::unordered_map<std::string, int> secondary; // 保持两个容器同步 void insert(int id, std::string name) { primary.emplace(id, name); secondary.emplace(name, id); } ``` ## 八、容器选择决策树 $$ \text{选择流程} \begin{cases} \text{需要快速查找?} \rightarrow \text{关联/无序容器} \\ \text{需要顺序保持?} \rightarrow \text{顺序容器} \\ \text{高频头尾操作?} \rightarrow \text{deque/list} \\ \text{内存敏感?} \rightarrow \text{array/vector} \end{cases} $$ ## 九、C++20重要更新 1. contains() 方法: ```cpp if (my_map.contains(key)) { ... } ``` 2. 安全类型擦除视图:`std::span` 3. 范围库支持:`std::ranges::sort(vec);` ## 十、经典问题解决方案 ### 10.1 高效去重方案 ```cpp // vector去重 std::sort(vec.begin(), vec.end()); auto last = std::unique(vec.begin(), vec.end()); vec.erase(last, vec.end()); // 保持原始顺序(C++20) auto [first, last] = std::ranges::unique(vec); vec.erase(first, last); ``` ### 10.2 对象池模式 ```cpp class ObjectPool { std::vector<std::unique_ptr<Object>> pool; std::stack<size_t> free_list; public: Object* acquire() { if(free_list.empty()) { pool.emplace_back(std::make_unique<Object>()); return pool.back().get(); } size_t idx = free_list.top(); free_list.pop(); return pool[idx].get(); } }; ``` 本笔记持续更新,建议配编译器实践验证。最佳学习路径:理解原理→分析复杂度→编写测试代码→性能剖析→生产环境应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值