是否可以使用指针链接列表实现?

时间:2010-06-09 02:32:14

标签: c++ pointers linked-list

我的问题很简单,可以使用C ++,实现链接列表数据结构而不使用指针(下一个节点)吗?为了进一步限定我的问题,我的意思是可以只使用类实例创建一个Linked-List数据结构。

常见的节点定义可能如此:

template<typename T>
struct node
{
   T t;
   node<T>* next;
   node<T>* prev;
};

我知道std::list等,我只是想知道它是否可能 - 如果是这样的话怎么样?代码示例将不胜感激。

更多说明:

  1. 插入应为O(1)。
  2. 遍历不应超过O(n)。
  3. 真节点和空节点应该是可区分的。
  4. 链接列表的大小应仅受可用内存量的限制。

14 个答案:

答案 0 :(得分:16)

当然,如果您不介意链接列表具有最大大小,您可以静态分配列表节点数组,然后将整数索引用作数组作为每个节点的“上一个”和“下一个”值而不是指针。我在过去做过这个以节省一点内存(因为整数可以是2或4个字节,而在64位系统上,指针将是8个字节)

答案 1 :(得分:10)

是的,这是可能的。使用数组索引而不是指针。

答案 2 :(得分:5)

来自Wikipedia

  

在计算机科学中,链表是   由a组成的数据结构   数据记录序列,以便在   每个记录都有一个字段   包含一个引用(即链接)   序列中的下一条记录。

该定义中没有任何内容指定存储或使用引用的方式。如果您不存储引用,则它不是链接列表 - 它是其他内容。

如果您的目标仅仅是避免使用指针(或对象引用),那么使用带索引的向量是一种常见的实现方式。 (使用向量/索引实现的原因之一是持久性:在活动内存空间之外正确地持久保存指针/对象引用非常困难。)

答案 3 :(得分:5)

是:

class node { 
  std::string filenameOfNextNode;
  std::string filenameOfPrevNode;
  std::string data;
  node nextNode() {
    node retVal;
    std::ifstream file(filenameOfNextNode.c_str());
    retVal.filenameOfNextNode = file.getline();
    retVal.filenameOfPrevNode = file.getline();
    retVal.data = file.getline();
    return retVal;
  }
};

受到关于链表来源的评论的启发

答案 4 :(得分:4)

可以使用临时,const引用和继承来创建cons-cells列表。但是你必须非常小心,不要在其生命周期之外保留任何引用。你可能无法逃脱任何可变的事情。

这大致基于这些列表的Scala实现(特别是使用继承和NilList子类而不是使用空指针的想法)。

template<class T>
struct ConsList{
   virtual T const & car() const=0;
   virtual ConsList<T> const & cdr() const=0;
}

template<class T>
struct ConsCell:ConsList{
   ConsCell(T const & data_, ConsList<T> const & next_):
        data(data_),next(next_){}
   virtual T const & car() const{return data;}
   virtual ConstList<T> const & cdr() const{return next;}

   private:
     T data;
     ConsList<T> const & next;
}

template<class T>
struct NilList:ConsList{  
   // replace std::exception with some other kind of exception class
   virtual T const & car() const{throw std::exception;}
   virtual ConstList<T> const & cdr() const{throw std::exception;}
}

void foo(ConsList<int> const & l){
   if(l != NilList<int>()){
      //...
      foo(NilList.cdr());
   }
}

foo(ConsList<int>(1,ConsList(2,ConsList<int>(3,NilList<int>()))));
// the whole list is destructed here, so you better not have
// any references to it stored away when you reach this comment.

答案 5 :(得分:3)

虽然我不确定你的问题背后的背景是什么,但如果你开箱即用,我相信你确定可以。

DVK建议的数组,这是非常正确的,但数组只是指针算术的简单包装。

完全不同的东西:使用文件系统为你做存储!

例如,文件 /linked-list/1 包含数据:

  

数据1!

     

5

/linked-list/5是列表中的下一个节点...

如果你愿意进行足够的攻击,一切皆有可能:-p

请注意,所述实现的复杂性/速度完全取决于您的文件系统(即,对于所有内容,它不一定是O(1))

答案 6 :(得分:3)

我认为使用引用是作弊的,从技术上讲,这会导致UB,但是在这里你去了:

// Beware, un-compiled code ahead!
template< typename T >
struct node;

template< typename T >
struct links {
  node<T>& prev;
  node<T>& next;
  link(node<T>* prv, node<T>* nxt); // omitted
};

template< typename T >
struct node {
  T data;
  links<T> linked_nodes;
  node(const T& d, node* prv, node* nxt); // omitted
};

// technically, this causes UB...
template< typename T >
void my_list<T>::link_nodes(node<T>* prev, node<T>* next)
{
  node<T>* prev_prev = prev.linked_nodes.prev;
  node<T>* next_next = next.linked_nodes.next;
  prev.linked_nodes.~links<T>();
  new (prev.linked_nodes) links<T>(prev_prev, next);
  next.linked_nodes.~links<T>();
  new (next.linked_nodes) links<T>(next, next_next);
}

template< typename T >
void my_list<T>::insert(node<T>* at, const T& data)
{
  node<T>* prev = at;
  node<T>* next = at.linked_nodes.next;
  node<T>* new_node = new node<T>(data, prev, next);

  link_nodes(prev, new_node);
  link_nodes(new_node, next);
}

答案 7 :(得分:3)

是的,你可以,没有必要使用链接列表的指针。可以在不使用指针的情况下链接列表。您可以为节点静态分配数组,而不是使用下一个和上一个指针,您可以只使用索引。你可以这样做来节省一些内存,如果你的链接列表不超过255,你可以使用'unsigned char'作为索引(引用C),并为下一个和前一个指示保存6个字节。

在嵌入式编程中可能需要这种数组,因为内存限制有时会很麻烦。

另请注意,您的链接列表节点在内存中不一定是连续的。

假设您的链接列表将包含60000个节点。使用线性搜索从阵列中分配空闲节点应该是低效的。相反,您可以每次都保留下一个空闲节点索引:

只需初始化数组,因为每个下一个索引显示当前数组索引+ 1,firstEmptyIndex = 0。

从数组中分配空闲节点时,抓取firstEmptyIndex节点,将firstEmptyIndex更新为当前数组索引的下一个索引(不要忘记将下一个索引更新为Null或空或之后的任何内容)。

解除分配时,将解除分配节点的下一个索引更新为firstEmptyIndex,然后执行firstEmptyIndex =解除分配节点索引。

通过这种方式,您可以创建一个从数组中分配空闲节点的快捷方式。

答案 8 :(得分:1)

您可以使用references创建一个链接列表,但这可能比必要的更复杂。你必须实现一个immutable链接列表,如果没有内置的垃圾收集器,它将很复杂。

答案 9 :(得分:1)

不支持任何类型引用的语言仍然可以通过用数组索引替换指针来创建链接。方法是保留一个记录数组,其中每个记录都有整数字段,指示数组中下一个(可能是前一个)节点的索引。并非需要使用阵列中的所有节点。如果也不支持记录,则通常可以使用并行数组。

作为示例,请考虑以下使用数组而不是指针的链接列表记录:

record Entry {
integer next; // index of next entry in array
string data; // can be anything a struct also. }

Creata一个数字很大的数组。并将listHead指向数组中的第一个indice元素

integer listHead;
Entry Records[10000];

检查维基页面:http://en.wikipedia.org/wiki/Linked_list了解详细信息,搜索“使用节点阵列链接列表”

答案 10 :(得分:0)

哇,不是吗?你们当然不认真吗?

所有链接列表需要的是一个链接。没有什么说它必须是一个指针。考虑想要在共享内存中存储链表的情况,其中基本地址是动态的?答案很简单,将链接从mem块的开头(或其他常量)存储为Offset,并重新定义迭代器以执行添加base addr的实际逻辑。显然,插入等也必须改变。

但相当琐碎!

阿伦

答案 11 :(得分:0)

一种可能的方法是使用Nodes数组,其中每个节点都存储prevnext Node的(数组)索引。因此,您的Node看起来像是:

struct Node 
{
    T data;
    int prev;    // index of the previous Node of the list
    int next;    // index of the next Node of the list
}

此外,您可能需要动态分配Node数组,对数组中的 get free 空间实施一些记账:例如{ {1}}数组,用于存储bool数组中未占用的索引,以及每次添加或删除新Node /索引时将更新它的两个函数(它将被分段为节点赢得不要总是连续的;在数组中找到Node的索引:例如,从数组的第一个地址中减去Node的地址。

以下是使用上述技术的双向链表的可能界面如何:

Node

您可以将其作为基准并自行实施。

template <typename T>                          // T type Node in your case
class DList
{
    T** head;                                  // array of pointers of type Node
    int first;                                 // index of first Node
    int last;                                  // index of last Node

    bool* available;                           // array of available indexes 
    int list_size;                             // size of list

    int get_index();                           // search for index with value true in bool available
    void return_index(int i);                  // mark index as free in bool available

    std::ptrdiff_t link_index(T*& l) const;    // get index of Node

    void init();                               // initialize data members
    void create();                             // allocate arrays: head and available

    void clear();                              // delete array elements
    void destroy();                            // delete head

public:
    DList();                                   // call create() and init()
    ~DList();                                  // call clear() and destroy()

    void push_back(T* l);
    void push_front(T* l);
    void insert(T*& ref, T* l);                // insert l before ref

    T* erase(T* l);                            // erase current (i.e. l) and return next
    T* advance(T* l, int n);                   // return n-th link before or after currnet

    std::size_t size() const;
    int capacity () const { return list_size; }
};

答案 12 :(得分:0)

除了使用previousnext向量/数组的现有答案之外,我们可以建立在动态性更高的之上调整大小结构,即失去调整大小操作的摊销额。

为什么我认为这合适?好吧,通过使用向量/数组,我们已经获得了一些优势,但是作为回报,我们得到了摊销后的调整大小。如果我们可以摆脱后者,那么我们可能完全使交易变得对我们有利!

具体地说,我指的是Resizable Arrays in Optimal Time and Space。这是一个非常有趣的数据结构,特别是作为我们正在讨论的其他数据结构的基础。

请注意,我已链接到该技术报告,该报告与常规论文不同,还包括(非常有趣的)解释如何实现“ 双重可缩放大小的数组”。

答案 13 :(得分:-9)

可以使用C ++,在不使用指针(下一个节点)的情况下实现链表数据结构吗?
否。