Data structures 不可变(双)LinkedList的高效实现

Data structures 不可变(双)LinkedList的高效实现,data-structures,linked-list,immutability,Data Structures,Linked List,Immutability,在阅读了这个问题和我之前关于不可变性问题的答案之后,我仍然对简单的不可变LinkedList的有效实现感到困惑。就数组而言,复制数组并基于该副本返回新结构似乎很容易 假设我们有一个节点的一般类: class Node{ private Object value; private Node next; } 并基于上面的类LinkedList允许用户添加、删除等。现在,我们如何确保不变性?当我们插入一个元素时,是否应该递归地复制对列表的所有引用 我也很好奇关于二叉树的帮助下导致lo

在阅读了这个问题和我之前关于不可变性问题的答案之后,我仍然对简单的不可变LinkedList的有效实现感到困惑。就数组而言,复制数组并基于该副本返回新结构似乎很容易

假设我们有一个节点的一般类:

class Node{
    private Object value;
    private Node next;
}
并基于上面的类LinkedList允许用户添加、删除等。现在,我们如何确保不变性?当我们插入一个元素时,是否应该递归地复制对列表的所有引用

我也很好奇关于二叉树的帮助下导致log(n)时间和空间的优化的答案。另外,我在某个地方读到,在前面添加元素也是0(1)。这让我非常困惑,因为如果我们不提供引用的副本,那么实际上我们正在修改两个不同源中的相同数据结构,这打破了不变性

你的任何答案都适用于双链接列表吗?我期待任何其他问题/解决方案的回复/指点。提前感谢你的帮助

当我们插入一个元素时,是否应该递归地复制对列表的所有引用

您应该递归地复制列表的前缀,直到插入点

这意味着插入到不可变链表中的是O(n)。(就像在数组中插入(而不是覆盖)元素一样)

因此,通常不赞成插入(以及附加和连接)

对不可变链表的通常操作是“cons”,即在开始处追加一个元素,即O(1)

您可以清楚地看到Haskell实现中的复杂性。给定定义为递归类型的链表:

data List a = Empty | Node a (List a)
我们可以直接将“cons”(在前面插入一个元素)定义为:

cons a xs = Node a xs
显然是O(1)操作。而插入必须递归定义——通过找到插入点。将列表拆分为前缀(已复制),并与新节点共享前缀和对(不可变)尾部的引用

关于链表,需要记住的重要一点是:

  • 线性存取
对于不可变列表,这意味着:

  • 复制列表的前缀
  • 分享尾巴
如果经常插入新元素,则首选基于日志的结构,例如树

假设我们有一个基于上面的节点和类LinkedList的通用类,允许用户添加、删除等。现在,我们如何确保不变性

通过使对象的每个字段都是只读的,并确保其中一个只读字段引用的每个对象也是不可变的对象,可以确保不可变。如果这些字段都是只读的,并且只引用其他不可变的数据,那么对象显然是不可变的

当我们插入一个元素时,是否应该递归地复制对列表的所有引用

你可以。这里的区别是不可变和持久的区别。无法更改不可变的数据结构。持久性数据结构利用了数据结构是不可变的这一事实,以便重用其部分

持久不变的链表特别容易:

抽象类不可变列表
{
public static readonly ImmutableList Empty=new EmptyList();
私有不可变列表(){}
公共抽象int头{get;}
公共抽象不可变列表尾部{get;}
公共抽象bool是空的{get;}
公共摘要不可变列表添加(int head);
私有密封类EmptyList:ImmutableList
{
公共重写int Head{get{throw new Exception();}}
公共重写不可变列表尾部{get{throw new Exception();}}
公共重写bool为空{get{return true;}}
公共覆盖不可变列表添加(int head)
{
返回新列表(标题,此);
}
}
私有密封类列表:不可变列表
{
专用只读int头;
私有只读不可变列表尾;
公共重写int Head{get{return Head;}}
公共重写不可变列表尾部{get{return Tail;}}
公共重写bool IsEmpty{get{return false;}
公共覆盖不可变列表添加(int head)
{
返回新列表(标题,此);
}
}
}
...
ImmutableList list1=ImmutableList.Empty;
ImmutableList list2=list1.Add(100);
ImmutableList list3=list2.Add(400);
就这样。当然,您希望添加更好的异常处理和更多方法,如
IEnumerable
方法。但是有一个持久不变的列表。每次创建新列表时,都会重复使用现有不可变列表的内容;list3重新使用list2的内容,这是安全的,因为list2永远不会改变

你的答案是否也适用于双链接列表

当然,您可以轻松地创建一个双链接列表,该列表每次都会完整复制整个数据结构,但这将是愚蠢的;您最好只使用一个数组并复制整个数组

制作一个持久的双链接列表是相当困难的,但是有很多方法可以做到这一点。我要做的是从另一个方向来处理这个问题。不要说“我可以创建一个持久的双链接列表吗?”而是问问自己“我觉得有吸引力的双链接列表的属性是什么?”列出这些属性,然后看看是否可以创建一个具有这些属性的持久数据结构

例如,如果您喜欢的特性是双链接列表可以从任意一端廉价地扩展,廉价地分成两个列表,并且两个列表可以廉价地连接在一起,那么您想要的持久化结构是一个不可变的可链接的deque,而不是doub
10 ----> 20 ----> 30 -----> 40 -----> Empty
10 ----> 20 ----> 25 -----> 30 -----> 40 -----> Empty
| 10 ----> 20 --------------> 30 -----> 40 -----> Empty
|                        /
| 10 ----> 20 ----> 25 -/ 
def insertAfter(l:MyList, e:ListItem)={ 
     val beforeE=l.getElementBefore(e)
     val afterE=l.getElementAfter(e)
     val eToInsert=e.copy(nextID=afterE.nextID)
     val beforeE_new=beforeE.copy(nextID=e.nextID)
     val l_tmp=l.update(beforeE.id,beforeE_new)
     return l_tmp.add(eToInsert)
}