递归类型真的是构建不连续的任意大小数据结构的唯一方法吗

Are recursive types really the only way to build noncontinuous arbitrary-size data structures?

本文关键字:数据结构 唯一 方法 任意大 递归 真的 构建 不连续 类型      更新时间:2023-10-16

我刚刚注意到一个问题,问在C++中递归数据类型("自引用类型")适合什么,我很想大胆地宣称

这是构建数据结构(更准确地说是容器)的唯一方法,该数据结构可以接受任意大型数据集合,而无需使用连续内存区域。

也就是说,如果没有随机访问数组,则需要对该类型中的某个类型进行某种引用(逻辑上)(显然,您可以说void* next,而不是MyClass* next成员,但它仍然指向MyClass对象或派生类型)。

然而,我对绝对的陈述很谨慎——仅仅因为我想不出一些东西并不意味着这是不可能的,所以我是否忽略了一些东西?是否存在既不是使用类似于链表/树的机制组织的数据结构,也不是专门使用连续序列的数据结构?


注意 :这被标记为c++和语言不可知论,因为我对c++语言特别感兴趣,但也对理论方面感兴趣

这是构建数据结构(更准确地说是容器)的唯一方法,该数据结构可以接受任意大型数据集合,而无需使用连续内存区域。

经过思考,这个说法似乎是正确的。事实上,这是不言而喻的。

假设我在非连续内存中有一个元素集合。还假设我当前处于元素e。现在的问题是,我如何知道集合中的下一个元素?有办法吗?

给定集合中的元素e只有两种方法可以计算下一个元素的位置:

  • 如果我假设它处于偏移sizeof(e),而不管e是什么,那么这意味着下一个元素从当前元素结束的地方开始。但这意味着集合位于连续的内存中,这在本讨论中是禁止的。

  • 元素e本身告诉我们下一个元素的位置。它可以存储地址本身,也可以存储偏移量。无论哪种方式,它都使用了自我参照的概念,这在本次讨论中也是被禁止的。

在我看来,这两种方法的基本思想完全相同:它们都实现了自引用。唯一的区别是,在前者中,自引用是隐式实现的,使用sizeof(e)作为偏移。这种隐式自引用由语言本身支持,并由编译器实现。在后者中,它是明确的,一切都由程序员自己完成,因为现在偏移(或指针)存储在元素本身中。

因此,我不认为有任何第三种方法可以实现自引用。如果不是自引用,那么术语将用于描述元素e的下一个元素的位置的计算。

所以我的结论是,你的说法是绝对正确的。

问题是动态分配器本身正在管理连续存储。想想用于图灵机的"磁带",或者冯·诺依曼体系结构。因此,要认真考虑这个问题,您可能需要开发一个新的计算模型和新的计算机体系结构。

如果你认为忽略底层机器的连续内存是可以的,我相信有很多解决方案是可能的。我首先想到的是,容器的每个节点都标记有一个标识符,该标识符与其在内存中的位置无关。然后,为了找到关联的节点,扫描所有内存,直到找到标识符。如果在并行机中给定足够的计算元素,这甚至不是特别低效。

这是一个证明的草图。

给定程序必须具有有限大小,程序中定义的所有类型必须仅包含有限多个成员,并且仅引用有限多个其他类型。这同样适用于任何程序入口点和程序初始化前定义的任何对象。

在没有连续数组的情况下(这是具有运行时自然数的类型的乘积,因此大小不受限制),所有类型都必须通过上述类型的组合来获得;类型的派生(指向-A的指针到指针)仍然受到程序大小的限制。除了连续数组之外,没有任何工具可以组成具有类型的运行时值。

这有点争议;如果例如映射被认为是基元的,那么可以用键是自然数的映射来近似数组。当然,映射的任何实现都必须使用自引用数据结构(B-树)或连续数组(哈希表)。

接下来,如果类型是非递归,则任何类型链(A引用B引用C…)都必须终止,并且长度不能大于程序中定义的类型数。因此,程序可引用的数据的总大小限制为每种类型的大小乘以程序中定义的名称的数量(在其入口点和静态数据中)的乘积。

即使函数是递归的(严格来说,这打破了对递归类型的禁止,因为函数是类型),这一点也成立;在程序中的任何一点上立即可见的数据量仍然限于每种类型的大小乘以该点上可见的名称数量的乘积。

一个例外是,如果您将"容器"存储在递归函数调用的堆栈中;然而,如果不展开堆栈并重新读取数据,这样的程序将无法随机遍历其数据,这有点不合格。

最后,如果可以动态创建类型,则上述证明不成立;例如,我们可以创建一个Lisp风格的列表结构,其中每个单元格都具有不同的类型:cons<4>('h', cons<3>('e', cons<2>('l', cons<1>('l', cons<0>('o', nil)))))。这在大多数静态类型语言中是不可能的,尽管在一些动态语言(如Python)中是可能的。

该语句不正确。一个简单的反例是C++中的std::deque。基本数据结构(对于语言不可知的部分)是指向数据数组的指针的连续数组。实际数据存储在ropes(非连续块)中,这些块通过连续数组进行链接。


这可能接近您的需求,这取决于而不使用连续内存区域的含义。我使用的解释是,存储的数据不是连续的,但这种数据结构取决于中间层的数组。

我认为更好的措辞是:

It's the only way to construct data structures (more precisely containers) that can accept 
arbitrary large data collections without using memory areas of determinable address.

我的意思是,普通数组使用addr(idx)=idx*size+inital_addr来获取元素的内存地址。但是,如果将其更改为类似addr(idx)=idx*idx*size+initial_addr的内容,则数据结构的元素不会存储在连续的内存区域中,相反,元素的存储位置之间存在很大的间隙。因此,它不是连续记忆。