高性能并行编程与优化 | 第02讲回家作业

简介: 本文是关于高性能并行编程与优化课程的第二讲回家作业,包括作业要求、初始运行结果、抄的答案以及改进后的运行结果。作业内容涉及对双链表类`List`的修改,要求避免不必要的拷贝、修复智能指针问题、实现深拷贝构造函数、解释为什么可以删除拷贝赋值函数以及改进`Node`的构造函数。同时,还提供了如何提交作业、设置https代理的链接以及评分规则。

一 作业要求

# 高性能并行编程与优化 - 第0x讲的回家作业

通过 pull request 提交作业。会批分数,但是:

没有结业证书,回家作业仅仅作为评估学习效果和巩固知识的手段,不必为分数感到紧张 :)
量力而行,只要能在本课中,学到昨天的自己不懂的知识,就是胜利,没必要和别人攀比。
注意不要偷看别人的作业哦!

- 课件:https://github.com/parallel101/course
- 录播:https://space.bilibili.com/263032155

作业提交时间不限 :) 即使完结了还想交的话我也会看的~ 不过最好在下一讲开播前完成。

- 如何开 pull request:https://zhuanlan.zhihu.com/p/51199833
- 如何设置 https 代理:https://www.jianshu.com/p/b481d2a42274

## 评分规则

- 完成作业基本要求 50 分(详见下方"作业要求")
- 能够在 PR 描述中用自己的话解释 25 分
- 代码格式规范、能够跨平台 5 分
- 有自己独特的创新点 20 分
- 明显抄袭现象 -100 分
- 改用 `unique_ptr` 10 分

## 作业要求

修改 main.cpp,改良其中的双链表类 `List`:

- 避免函数参数不必要的拷贝 5 分
- 修复智能指针造成的问题 10 分

- 实现拷贝构造函数为深拷贝 15 分
- 说明为什么可以删除拷贝赋值函数 5 分
- 改进 `Node` 的构造函数 5 分

并通过 `main()` 函数中的基本测试。

## 关于内卷

如果你把 List 改成了基于迭代器的,或是作为模板 `List`:
只要是在 **满足作业要求的基础** 上,这是件好事!
老师会酌情加分,视为“独特的创新点”,但最多不超过 20 分。

/* 基于智能指针实现双向链表 */
#include <cstdio>
#include <memory>

struct Node {
   
    // 这两个指针会造成什么问题?请修复
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
    // 如果能改成 unique_ptr 就更好了!

    int value;

    // 这个构造函数有什么可以改进的?
    Node(int val) {
   
        value = val;
    }

    void insert(int val) {
   
        auto node = std::make_shared<Node>(val);
        node->next = next;
        node->prev = prev;
        if (prev)
            prev->next = node;
        if (next)
            next->prev = node;
    }

    void erase() {
   
        if (prev)
            prev->next = next;
        if (next)
            next->prev = prev;
    }

    ~Node() {
   
        printf("~Node()\n");   // 应输出多少次?为什么少了?
    }
};

struct List {
   
    std::shared_ptr<Node> head;

    List() = default;

    List(List const &other) {
   
        printf("List 被拷贝!\n");
        head = other.head;  // 这是浅拷贝!
        // 请实现拷贝构造函数为 **深拷贝**
    }

    List &operator=(List const &) = delete;  // 为什么删除拷贝赋值函数也不出错?

    List(List &&) = default;
    List &operator=(List &&) = default;

    Node *front() const {
   
        return head.get();
    }

    int pop_front() {
   
        int ret = head->value;
        head = head->next;
        return ret;
    }

    void push_front(int value) {
   
        auto node = std::make_shared<Node>(value);
        node->next = head;
        if (head)
            head->prev = node;
        head = node;
    }

    Node *at(size_t index) const {
   
        auto curr = front();
        for (size_t i = 0; i < index; i++) {
   
            curr = curr->next.get();
        }
        return curr;
    }
};

void print(List lst) {
     // 有什么值得改进的?
    printf("[");
    for (auto curr = lst.front(); curr; curr = curr->next.get()) {
   
        printf(" %d", curr->value);
    }
    printf(" ]\n");
}

int main() {
   
    List a;

    a.push_front(7);
    a.push_front(5);
    a.push_front(8);
    a.push_front(2);
    a.push_front(9);
    a.push_front(4);
    a.push_front(1);

    print(a);   // [ 1 4 9 2 8 5 7 ]

    a.at(2)->erase();

    print(a);   // [ 1 4 2 8 5 7 ]

    List b = a;

    a.at(3)->erase();

    print(a);   // [ 1 4 2 5 7 ]
    print(b);   // [ 1 4 2 8 5 7 ]

    b = {
   };
    a = {
   };

    return 0;
}

二 作业初始运行结果

三 抄的答案

改了很久,没成功,实在很烦,拖了2个月,还是抄答案吧。

/* 基于智能指针实现双向链表 
参考代码:https://github.com/parallel101/hw02/pull/53/files
*/
#include <cstdio>
#include <exception>
#include <memory>
#include <stdexcept>
#include <utility>
#include <type_traits>
#include <iterator>
#include <cstddef>

template <typename T>
struct Node {
   
    // 这两个指针会造成什么问题?请修复
    //     std::shared_ptr<Node> next;
    //     std::shared_ptr<Node> prev;
    // 对于双向链表,相邻项之间存在循环引用,引用计数无法归零释放空间。
    // 对于双向链表,可以看作前一项“拥有”后一项。
    // 而后一项保留前一项的Node*指针
    std::unique_ptr<Node> next;
    Node* prev;
    // 如果能改成 unique_ptr 就更好了!
    //int value;

     这个构造函数有什么可以改进的?
    //Node(int val) {
   
    //    value = val;
    //}

    //void insert(int val) {
   
    //    auto node = std::make_shared<Node>(val);
    //    node->next = next;
    //    node->prev = prev;
    //    if (prev)
    //        prev->next = node;
    //    if (next)
    //        next->prev = node;
    //}

    T value;

    // 这个构造函数有什么可以改进的?:value直接根据val构造而不是默认构造后赋值。
    Node(const T& val) : value(val), prev(nullptr) {
   }
    Node(const Node&) = default;
    Node& operator=(Node&&) = default;

    // insert会导致无法使用unique_ptr,因为会破环上面假设的“前一项拥有后一项”的前提
    /*
              +--- O ---+
        O ---x           x--- nextO
              +--- O ---+
        会变成上面这样, 双向链表中用不到该操作所以直接注掉了。
    */
    // void insert(int val) {
   
    //     auto node = std::make_unique<Node>(val);
    //     node->next = next;
    //     node->prev = prev;
    //     if (prev)
    //         prev->next = node;
    //     if (next)
    //         next->prev = node;
    // }


    void erase() {
   
        //if (prev)
        //    prev->next = next;
        if (next)
            next->prev = prev;
        if (prev)
            prev->next = std::move(next);
    }


    ~Node() {
   
        printf("~Node()\n");   // 应输出多少次?为什么少了?因为循环引用
    }
};

template<typename T>
struct List {
   
    //std::shared_ptr<Node> head;
    class iterator {
   
    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = std::ptrdiff_t;
        using value_type = Node<T>;
        using pointer = value_type*;  // or also value_type*
        using reference = value_type&;  // or also value_type&

        iterator(pointer ptr) : m_ptr(ptr) {
   }
        reference operator*() {
    return *m_ptr; }
        pointer operator->() {
    return m_ptr; }

        // Prefix increment
        iterator& operator++()
        {
   
            m_ptr = m_ptr->next.get(); return *this;
        }

        // Postfix increment
        iterator operator++(int)
        {
   
            iterator tmp = *this; ++(*this); return tmp;
        }

        friend bool operator== (const iterator& a, const iterator& b)
        {
   
            return a.m_ptr == b.m_ptr;
        };
        friend bool operator!= (const iterator& a, const iterator& b)
        {
   
            return a.m_ptr != b.m_ptr;
        };

    private:
        Node<T>* m_ptr;
    };

    std::unique_ptr<Node<T>> head;
    Node<T>* back = nullptr;

    List() = default;
    List(List const& other_) {
   
        List& other = const_cast<List&>(other_);//拷贝构造函数有特定的函数签名,不建议轻易改变。
        printf("List 被拷贝!\n");
        //head = other.head;  // 这是浅拷贝!
        // 请实现拷贝构造函数为 **深拷贝**
        for (auto it = other.begin(); it != other.end(); it++) {
   
            push_back(it->value);
        }
    }

    List &operator=(List const &) = delete;  // 为什么删除拷贝赋值函数也不出错?
    // 此处拷贝赋值 = 拷贝构造出右值+移动赋值

    List(List &&) = default;
    List &operator=(List &&) = default;

    //Node *front() const {
   
    Node<T>* front() const {
   
        return head.get();
    }

    //int pop_front() {
   
    //    int ret = head->value;
    //   head = head->next;
    T pop_front() {
   
        if (begin() == end()) {
   
            throw std::out_of_range("pop_front()");
        }
        T ret = head->value;
        if (head.get() == back)
            back = nullptr;
        head = std::move(head->next);
        return ret;
    }

    //void push_front(int value) {
   
    //    auto node = std::make_shared<Node>(value);
    //    node->next = head;
    void push_front(const T& value) {
   
        auto node = std::make_unique<Node<T>>(value);
        if (head)
     //       head->prev = node;
    //    head = node;
        head->prev = node.get();
        else
        back = node.get();
        node->next = std::move(head);
        head = std::move(node);
    }

   // Node *at(size_t index) const {
   
    void push_back(const T& value) {
   
        auto node = std::make_unique<Node<T>>(value);
        if (back) {
   
            node->prev = back;
            back->next = std::move(node);
            back = back->next.get();
        }
        else {
   
            head = std::move(node);
            back = head.get();
        }
    }

    Node<T>* at(size_t index) const {
   
        auto curr = front();
        for (size_t i = 0; i < index; i++) {
   
            curr = curr->next.get();
        }
        return curr;
    }

    iterator begin() {
    return iterator(head.get()); }
    iterator end() {
    return iterator(nullptr); }
};

void print(List<int> & lst) {
     // 有什么值得改进的?: 传入const引用,避免拷贝
    printf("[");
  //  for (auto curr = lst.front(); curr; curr = curr->next.get()) {
   
  //      printf(" %d", curr->value);
    for (auto &v : lst) {
   
        printf(" %d", v.value);
    }
    printf(" ]\n");
}

int main() {
   
    //List a;
    List<int> a;

    a.push_front(7);
    a.push_front(5);
    a.push_front(8);
    a.push_front(2);
    a.push_front(9);
    a.push_front(4);
    a.push_front(1);

    print(a);   // [ 1 4 9 2 8 5 7 ]

    a.at(2)->erase();

    print(a);   // [ 1 4 2 8 5 7 ]

    //List b = a;
    List<int> b = a;

    a.at(3)->erase();

    print(a);   // [ 1 4 2 5 7 ]

    b.push_back(1);
    b.push_back(2);
    b.push_back(3);
    print(b);   // [ 1 2 3 ]
    print(b);   // [ 1 4 2 8 5 7 ]

    b = {
   };
    a = {
   };

    return 0;
}

四 改进的运行结果

相关文章
|
11天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
8天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2522 17
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
8天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1522 15
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
4天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
10天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
581 14
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19283 30
|
10天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
485 49
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18841 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17530 13
Apache Paimon V0.9最新进展
|
2天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
366 4
叮咚!您有一份六大必做安全操作清单,请查收