C# 数据结构,可以动态调整大小到给定限制的列表,并允许快速访问任何索引
Posted
技术标签:
【中文标题】C# 数据结构,可以动态调整大小到给定限制的列表,并允许快速访问任何索引【英文标题】:C# data structure, list which can dynamically resize up to a given limit, and allows fast access to any index 【发布时间】:2016-11-24 18:56:46 【问题描述】:我正在为 AI 代理实现一个内存系统。它需要有一个内部状态转换列表,上限为某个数字,例如 10000。
如果容量已满,添加新内存应该会自动删除最旧的内存。
重要的是,我还需要能够快速访问此列表中的任何项目。
Queue 的包装起初看起来很明显,但 Queue 不允许快速访问任何元素。 (O(n))
同样,从 List 结构的开头删除一个项目需要 O(n)。
LinkedLists 允许快速添加和删除,但同样不允许快速访问每个索引。
数组将允许随机访问,但显然它不能动态调整大小并且删除是有问题的。
我已经看到有人建议使用 HashMap,但我确定如何实施。
建议?
【问题讨论】:
听起来你想要一个circular buffer
是的,循环缓冲区看起来是个不错的选择。
@Lee - 将此作为实际答案 - 获得一些分数! :)
您想通过键还是通过索引来访问项目?如果按键,您可能对我的MruDictionary 感兴趣
【参考方案1】:
如果您希望队列为固定长度,您可以使用circular buffer,它启用 O(1) 的入队、出队和索引操作,并在队列已满时自动覆盖旧条目。
【讨论】:
【参考方案2】:尝试使用Dictionary
和LinkedList
。 Dictionary
的键是LinkedList
节点的索引,Dictionary
的值是LinkedListNode
类型;即LinkedList
节点。
字典几乎会给你一个O(1)
,它的操作和删除/添加LinkedListNode(s)
到LinkedList
的开头或结尾也是O(1)
。
另一种选择是使用HashTable
。但是,在这种情况下,您必须事先知道表的容量(参见Hashtable.Add Method)才能获得O(1)
的性能:
如果 Count 小于 Hashtable 的容量,则此方法是 O(1) 操作。如果需要增加容量以容纳新元素,则此方法变为 O(n) 操作,其中 n 为 Count。
在第一个解决方案中,无论LinkedList
或Dictionary
的容量是多少,您仍然可以从Dictionary
和LinkedList
获得几乎一个O(1)
。当然,这将是O(3)
或O(4)
,具体取决于您在Dictionary
和LinkedList
上执行的在内存类中执行添加或删除操作的操作总数。搜索访问将始终为 O(1)
,因为您将仅使用 Dictionary
。
【讨论】:
我明白你的意思,这可能是一个愚蠢的问题,但是拥有 LinkedList 而不是仅使用整数作为键并保留最小和最大键有什么好处?每当您必须删除最旧的值时,只需删除 HashMap[min] 处的值并增加 min。每当您添加一个值时,递增 max 并将其添加到 HashMap[max]。有效键的范围始终为 [min, max]。【参考方案3】:HashMap 用于 Java,因此最接近的等价物是 Dictionary。 C# Java HashMap equivalent。但我不会说这是最终的答案。
如果您将其实现为字典,其中键 == 内容,那么您可以使用 O(1) 搜索内容。但是,您不能拥有相同的密钥。另外,因为没有排序,所以你可能不知道第一个内容是什么。 如果您将其实现为 Dictionary,其中 key == 索引,value == 内容,搜索内容仍然需要 O(n),因为您不知道内容的位置。 如果按索引reference 搜索内容,列表或数组将花费 O(1)。所以,请仔细检查你的陈述,它需要 O(n) 如果按索引搜索就足够了,那么@Lee 提到的循环数组/缓冲区就足够了。 否则,与 DB 类似,您可能希望在 2 个单独的数据中维护:1 个用于存储数据(循环数组),另一个用于搜索(哈希)。【讨论】:
【参考方案4】:编辑:@Lee 说得对。循环缓冲区似乎可以满足您的需求。答案留在原地。
我认为您想要的数据结构可能是一个优先级队列——这取决于您所说的“快速访问任何项目”是什么意思。如果您的意思是“能够枚举 O(N) 中的所有项目”,那么优先级队列就符合要求。如果您的意思是“按历史顺序枚举列表”,则不会。
假设你需要这些操作;
添加项目并与时间关联 删除最旧的项目 以任意顺序枚举所有现有项目然后你可以很容易地扩展我刚才写的这个优先队列实现。
您需要将 IEnumerable 实现为通过T[] data
数组从 0 到 cursor
的循环。这将为您提供枚举。
实现一个 GetItem(i) 函数,只要 i <= cursor
就返回 this.data[i]
。
通过将其放入 Push() 方法来实现自动大小限制;
if (queue.Size => 10000) 队列.Pop();
我认为这是 O(ln n) 用于推送和弹出,而 O(N) 用于枚举所有项目,或者 O(i) 用于查找任何项目,只要您不需要按顺序排列它们。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mindfire.DataStructures
public class PiorityQueue<T>
private int[] priorities;
private T[] data;
private int cursor;
private int capacity;
public int Size
get
return cursor+1;
public PiorityQueue(int capacity)
this.cursor = -1;
this.capacity = capacity;
this.priorities = new int[this.capacity];
this.data = new T[this.capacity];
public T Pop()
if (this.Size == 0)
throw new InvalidOperationException($"The this.GetType().Name is Empty");
var result = this.data[0];
this.data[0] = this.data[cursor];
this.priorities[0] = this.priorities[cursor];
this.cursor--;
var loc = 0;
while (true)
var l = loc * 2;
var r = loc * 2 + 1;
var leftIsBigger = l <= cursor && this.priorities[loc] < this.priorities[l];
var rightIsBigger = r <= cursor && this.priorities[loc] < this.priorities[r];
if (leftIsBigger)
Swap(loc, l);
loc = l;
else if (rightIsBigger)
Swap(loc, r);
loc = r;
else
break;
return result;
public void Push(int priority, T v)
this.cursor++;
if (this.cursor == this.capacity)
Resize(this.capacity * 2);
;
this.data[this.cursor] = v;
this.priorities[this.cursor] = priority;
var loc = (this.cursor -1)/ 2;
while (this.priorities[loc] < this.priorities[cursor])
// swap
this.Swap(loc, cursor);
private void Swap(int a, int b)
if (a == b) return;
var data = this.data[b];
var priority = this.priorities[b];
this.data[b] = this.data[a];
this.priorities[b] = this.priorities[a];
this.priorities[a] = priority;
this.data[a] = data;
private void Resize(int newCapacity)
var newPriorities = new int[newCapacity];
var newData = new T[newCapacity];
this.priorities.CopyTo(newPriorities, 0);
this.data.CopyTo(newData, 0);
this.data = newData;
this.priorities = newPriorities;
this.capacity = newCapacity;
public PiorityQueue() : this(1)
public T Peek()
if (this.cursor > 0)
return this.data[0];
else
return default(T);
public void Push(T item, int priority)
【讨论】:
以上是关于C# 数据结构,可以动态调整大小到给定限制的列表,并允许快速访问任何索引的主要内容,如果未能解决你的问题,请参考以下文章