【问题标题】:Concatenate two linked lists in reverse order以相反的顺序连接两个链表
【发布时间】:2020-01-07 22:03:56
【问题描述】:

我有两个列表,我目前正在使用std::list < size_t >。 我们称它们为列表 A 和列表 B。 假设列表 A 有元素 [1, 2, 3, 4],列表 B 有元素 [5, 6, 7, 8]。

我想连接两个列表。但是,在我的应用程序中,有八种连接方式。我将描述两种保持最小化的方法:

  1. 直接连接 A 和 B。所以列表 C = [1, 2, 3, 4, 5, 6, 7, 8]。
  2. 反转列表 A 和 B,然后连接。所以列表 C = [4, 3, 2, 1, 8, 7, 6, 5]。

对于第一种情况,我可以使用以下代码 (source: cppreference):

std::list <size_t> C; 
C.splice(C.begin(), A); 
C.splice(C.end(), B);

这提供了一个很好的常量时间实现。

对于第二种情况,我必须反转列表,我可以使用 reverse() 来达到目的 (source: cppreference):

A.reverse();
B.reverse();
C_reversed.splice(C_reversed.begin(), A);
C_reversed.splice(C_reversed.end(), B);
std::cout << "C_reversed: " << C_reversed << std::endl;

这很好用,但是它具有线性时间复杂度。有一个更好的方法吗?我通读了Stack Overflow: Why does std::list::reverse have O(n) complexity?,并了解到使用reverse() 无法完成。有没有其他方法可以在恒定时间内完成此任务?


我的后备选项是维护四个列表,A 和 B 各两个,以便我可以在恒定时间内连接。然而,这会导致额外的空间复杂度。示例如下:

std::list <size_t> A1 = {1, 2, 3, 4};
std::list <size_t> A1_reversed = {4, 3, 2, 1};
std::list <size_t> B1 = {5, 6, 7, 8};
std::list <size_t> B1_reversed = {8, 7, 6, 5};
std::list <size_t> C1;
C1.splice(C1.begin(), A1);
C1.splice(C1.end(), B1);
std::list <size_t> C1_reversed;
std::cout << "C1: "<< C1 <<std::endl;
C1_reversed.splice(C1_reversed.begin(), A1_reversed);
C1_reversed.splice(C1_reversed.end(), B1_reversed);
std::cout << "C1_reversed: "<< C1_reversed <<std::endl;

实验代码如下:

  /** Concatenation of two lists **/
#include <iostream>
#include <list>

std::ostream& operator<<(std::ostream& ostr, const std::list< size_t >& list) {
    for (auto &i : list) {
        ostr << " " << i;
    }
    return ostr;
}

int main(int argc, char **argv) {

    /** Initialize **/
    std::list < size_t > A = {1, 2, 3, 4};
    std::list < size_t > B = {5, 6, 7, 8};

    /** A + B **/
    std::list < size_t > C;
    C.splice(C.begin(), A);
    C.splice(C.end(), B);
    std::cout << C << std::endl;

    /** Reverse A + reverse B **/
    A = {1, 2, 3, 4};
    B = {5, 6, 7, 8};
    std::list < size_t > C_reversed;
    A.reverse();
    B.reverse();
    C_reversed.splice(C_reversed.begin(), A);
    C_reversed.splice(C_reversed.end(), B);
    std::cout << C_reversed << std::endl;

    /** Maintaining four lists **/
    std::list <size_t> A1 = {1, 2, 3, 4};
    std::list <size_t> A1_reversed = {4, 3, 2, 1};
    std::list <size_t> B1 = {5, 6, 7, 8};
    std::list <size_t> B1_reversed = {8, 7, 6, 5};
    std::list <size_t> C1;
    C1.splice(C1.begin(), A1);
    C1.splice(C1.end(), B1);
    std::list <size_t> C1_reversed;
    std::cout << "C1: "<< C1 <<std::endl;
    C1_reversed.splice(C1_reversed.begin(), A1_reversed);
    C1_reversed.splice(C1_reversed.end(), B1_reversed);
    std::cout << "C1_reversed: "<< C1_reversed <<std::endl;

    return 0;
}

【问题讨论】:

  • 您可以使用rbeginrend 向后迭代列表,从而避免实际的反转。
  • 迭代会导致线性时间复杂度,而splice()不取reverse_iterators,即rbeginrend
  • 当你打印列表时,它已经是 O(n)... 你可以最小化反转的次数。如果列表很短,那真的没有多大关系。

标签: c++ c++14


【解决方案1】:

为了使其 O(1),我们可以扩展 stl::list 类或以其他方式实现类似的接口。下面是一个开始的例子。 声明: stl::list 的每个功能都可以为 ReversedConCatList 实现,与原始 stl::list 相比,复杂性保持相同甚至更好。

template <typename T> 
class ReversedConCatList {  
  public:
    typedef value_type& reference;
    typedef /*implementation-defined: special_iterator*/ iterator; // special_iterator(l1, l2);
    void push_back(T value) {
      b_.emplace_front(value);
    }
    void push_front(T value) {
      a_.push_back(value);
    }
    reference front() {
      return a_.empty() ? b_.back(): a_.back();
    }
    reference back() {
      return b_.empty() ? a_.front() : b_.front();
    }
    iterator begin() noexcept {
      return special_iterator(a_, b_);
    }
    iterator end() noexcept {
      return a.begin().base(); // end of special iterator
    }
    size_type size() const noexcept {
      return a_.size() + b_.size();
    }
    bool empty() const noexcept {
      return a_.empty() && b_.empty();
    }
    // ...
  private:
    std::list<T> a_; // Takes ownership
    std::list<T> b_; // Takes ownership
};

基本上,在 A + reversed(B) 或 reversed(A) + B 等其他情况下,列表的所有操作都可以使用 ReversedConCatList 实现,只需保留列表是否反转的布尔标志。更好地使用模板在编译时摆脱标志。

洞察力是列表 API 从头部和尾部具有相同的功能,因此我们能够做到这一点,std::list 可以实现具有 O(1) 复杂度的 reverse(),但这会产生合并列表复杂度不平凡(目前它的 O(1))。

【讨论】:

  • 如果你能添加一些描述,那真的很有帮助。我很困惑,特别是如何获得其他合并组合(例如,A + reverse(B) 或 reverse(B) + reverse(A))。如前所述,有 8 种这样的组合。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
  • 2012-12-20
相关资源
最近更新 更多