【问题标题】:How to make a contiguous linked list?如何制作一个连续的链表?
【发布时间】:2021-08-23 04:09:40
【问题描述】:

我有一个用于循环链表的自定义数据结构。它的目的非常小众,所以想知道有没有办法让每个节点都指向一块连续的内存块中的一个地址,从而实现随机访问。该列表在初始填充后根本不会被修改(插入/删除),因此连续块不会妨碍我认为的任何事情。

我怎样才能做到这一点?

另外,我不使用向量的原因是因为我正在基于指针重新排列对列表执行旋转。但是,我正在按 O(n) 重新排列指针,而如果我使用连续的内存空间,我相信我可以通过指针算法按 O(1) 重新排列指针。

为了更清楚而编辑: 我的“名单”是这样的

// barebones circular list structure
class List
{
    private:
        struct Node
        {
            Node* next;
            int data;
        };

        // beginning of list
        Node* start;
        // end of list which POINTS to beginning as well
        Node* end;
        // number of list elements
        int size;

    public:
        List()
        {
            start = NULL;
            end = NULL;
            size = 0;
        }
        ~List();

        // populates the list using size parameter and uses stdin for the values
        void populate_list(const int &size);
        // moves the starting pointer of a list, effectively rotating it
        void move_start(int n, char d);
        // print contents of list
        void print();
};

正如我所说,列表的所有元素(节点)都是通过填充列表填充的。人口的约定是从 1 开始的顺序。因此,假设您的大小为 5,则列表填充为 {1,2,3,4,5}。然后,给定一个旋转列表的数量,我通过move_start()旋转

// moves starting node by n spaces in d direction
void List::move_start(int n, char d)
{
    // left rotation procedure
    if (d == 'L')
    {
        // move the start pointer the given rotation amount
        for (int i = 0; i < n; i++)
            start = start->next;
    }

    // right rotation procedure
    else if (d == 'R')
    {
        // move the start pointer by the size of the list minux the given rotation amount
        for (int i = 0; i < size-n; i++)
            start = start->next;
    }
    return;
}

也许我的理解是错误的,但我相信我在 O(n) 处“旋转”,因为我将 start 指针从 1 到 size-1 次(。但如果我有一个连续的块Nodes,然后我可以简单地将 start 分配到一个新位置,而不是走到那里 (O(1))。希望这能解决问题。

【问题讨论】:

  • 如果你想让你的节点是连续的,那么为什么不直接使用数组或向量呢?
  • @JamesNewman 仅供参考——在广泛使用使用指针的语言之前,链表是使用数据/下一个对的数组实现的,其中“下一个”将是下一个节点所在位置的索引位于链表中。我不知道这是否适合您,但它就是这样做的。至少,你会得到你正在寻找的连续性。
  • 要求不明确。理由尚不清楚。对我来说,可能类似于deque,但我不确定。
  • “我将指针重新排列为 O(n),而我相信我可以通过指针算法将指针重新排列 O(1)” 这是一个相当神秘的陈述。你能显示一些代码吗?
  • 看起来你需要一个循环缓冲区而不是链表。

标签: c++ memory-management linked-list


【解决方案1】:

我怎样才能做到这一点?

您可以通过自定义分配器使用标准列表容器。

但是,我将指针重新排列为 O(n),而我相信如果我使用连续的内存空间,我可以通过指针算法将指针重新排列 O(1)。

所有列表都可以使用splice 操作在恒定时间内进行旋转(只要您不需要搜索相关的迭代器)。连续的内存空间不会提高这种操作的渐近复杂度。


根据您的描述,似乎简单地使用带有自定义迭代器的std::vector 到“第一个”元素将是一个不错的数据结构选择。我看不到使用链表的好处。向量和迭代器结合起来本质上是一个循环缓冲区。

【讨论】:

    【解决方案2】:

    如您的问题中提到的,有许多更好的方法来处理您的要求。

    但是,如果您的要求非常严格,您可以使用列表,您可以使用placement new 来控制连续内存。但在使用 Placement new 之前,请了解所有复杂性。

    以下是如何实现这一目标的示例。

        // buffer with required size
        unsigned char buf[sizeof(int) * 4];
    
        // placement new in buffer
        int* pInt1 = new (buf) int(2);
        int* pInt2 = new (buf + sizeof(int) * 1) int(4);
        int* pInt3 = new (buf + sizeof(int) * 2) int(6);
        int* pInt4 = new (buf + sizeof(int) * 3) int(8);
    
        //Test
        std::cout << pInt1 << "  " << pInt2 << "  " << pInt3 << "  " << pInt4 << std::endl;
        std::cout << *pInt1 << "  " << *pInt2 << "  " << *pInt3 << "  " << *pInt4 << std::endl;
    
        //Push into the list
        std::list<int*> tList;
        tList.push_back(pInt1);
        tList.push_back(pInt2);
        tList.push_back(pInt3);
        tList.push_back(pInt4);
          
        //Test
        for (auto it = tList.begin(); it != tList.end(); ++it)
        {
            std::cout << *it << std::endl;
            std::cout << **it << std::endl;
        }
    

    【讨论】:

    • 也许我误解了这一点,但我不太确定如何将placement new 与Node 数据类型一起使用。
    • 就像“int”是一种类型,“Node”是一种类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-18
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    相关资源
    最近更新 更多