具有最小内存的任意图的广度优先遍历
Breadth first traversal of arbitrary graph with minimal memory
我有一个巨大的有向图,我需要遍历它以搜索从给定起点到特定节点的最短路径。所讨论的图表并不明确存在;子节点是根据父节点通过算法确定的。
(举个例子:想象一个国际象棋位置图。每个节点都是一个国际象棋位置,它的子节点是从该位置开始的所有合法移动。)
所以我有一个开放节点队列,每次我处理队列中的下一个节点时,我都会将其所有子节点排入队列。但是由于图表可以有循环,我还需要维护所有访问过的节点的哈希集,这样我就可以检查我之前是否访问过一个节点。
这工作正常,但由于该图太大,我 运行 遇到了内存问题。队列中的所有节点也存储在哈希集中,在我的例子中,它往往占总数或实际访问节点总数的 50% 左右。
有没有什么神奇的方法可以在保持哈希集速度的同时摆脱这种冗余? (显然,我可以通过不散列和只进行线性搜索来摆脱冗余,但这是不可能的。)
我通过编写 class 解决了这个问题,它将键存储在列表中并将键的索引存储在哈希表中。 “队列中”的下一个节点始终是列表中的下一个节点,直到您找到要查找的内容或遍历整个图。
class IndexMap<T>
{
private List<T> values;
private LinkedList<int>[] buckets;
public int Count { get; private set; } = 0;
public IndexMap(int capacity)
{
values = new List<T>(capacity);
buckets = new LinkedList<int>[NextPowerOfTwo(capacity)];
for (int i = 0; i < buckets.Length; ++i)
buckets[i] = new LinkedList<int>();
}
public void Add(T item) //assumes item is not yet in map
{
if (Count == buckets.Length)
ReHash();
int bucketIndex = item.GetHashCode() & (buckets.Length - 1);
buckets[bucketIndex].AddFirst(Count++);
values.Add(item);
}
public bool Contains(T item)
{
int bucketIndex = item.GetHashCode() & (buckets.Length - 1);
foreach(int i in buckets[bucketIndex])
{
if (values[i].Equals(item))
return true;
}
return false;
}
public T this[int index]
{
get => values[index];
}
private void ReHash()
{
LinkedList<int>[] newBuckets = new LinkedList<int>[2 * buckets.Length];
for (int i = 0; i < newBuckets.Length; ++i)
newBuckets[i] = new LinkedList<int>();
for (int i = 0; i < buckets.Length; ++i)
{
foreach (int index in buckets[i])
{
int bucketIndex = values[index].GetHashCode() & (newBuckets.Length - 1);
newBuckets[bucketIndex].AddFirst(index);
}
buckets[i] = null;
}
buckets = newBuckets;
}
private int NextPowerOfTwo(int n)
{
if ((n & n-1) == 0)
return n;
int output = 0;
while (n > output)
{
output <<= 1;
}
return output;
}
}
维护开放节点数组和访问节点哈希表的旧方法需要 n*(1+a)*size(T) space,其中 a 是 nodes_in_the_queue 超过 total_nodes_found 并且 size(T) 是节点的大小。
此方法需要 n*(size(T) + size(int))。如果您的节点明显大于 int,这可以节省很多。
我有一个巨大的有向图,我需要遍历它以搜索从给定起点到特定节点的最短路径。所讨论的图表并不明确存在;子节点是根据父节点通过算法确定的。
(举个例子:想象一个国际象棋位置图。每个节点都是一个国际象棋位置,它的子节点是从该位置开始的所有合法移动。)
所以我有一个开放节点队列,每次我处理队列中的下一个节点时,我都会将其所有子节点排入队列。但是由于图表可以有循环,我还需要维护所有访问过的节点的哈希集,这样我就可以检查我之前是否访问过一个节点。
这工作正常,但由于该图太大,我 运行 遇到了内存问题。队列中的所有节点也存储在哈希集中,在我的例子中,它往往占总数或实际访问节点总数的 50% 左右。
有没有什么神奇的方法可以在保持哈希集速度的同时摆脱这种冗余? (显然,我可以通过不散列和只进行线性搜索来摆脱冗余,但这是不可能的。)
我通过编写 class 解决了这个问题,它将键存储在列表中并将键的索引存储在哈希表中。 “队列中”的下一个节点始终是列表中的下一个节点,直到您找到要查找的内容或遍历整个图。
class IndexMap<T>
{
private List<T> values;
private LinkedList<int>[] buckets;
public int Count { get; private set; } = 0;
public IndexMap(int capacity)
{
values = new List<T>(capacity);
buckets = new LinkedList<int>[NextPowerOfTwo(capacity)];
for (int i = 0; i < buckets.Length; ++i)
buckets[i] = new LinkedList<int>();
}
public void Add(T item) //assumes item is not yet in map
{
if (Count == buckets.Length)
ReHash();
int bucketIndex = item.GetHashCode() & (buckets.Length - 1);
buckets[bucketIndex].AddFirst(Count++);
values.Add(item);
}
public bool Contains(T item)
{
int bucketIndex = item.GetHashCode() & (buckets.Length - 1);
foreach(int i in buckets[bucketIndex])
{
if (values[i].Equals(item))
return true;
}
return false;
}
public T this[int index]
{
get => values[index];
}
private void ReHash()
{
LinkedList<int>[] newBuckets = new LinkedList<int>[2 * buckets.Length];
for (int i = 0; i < newBuckets.Length; ++i)
newBuckets[i] = new LinkedList<int>();
for (int i = 0; i < buckets.Length; ++i)
{
foreach (int index in buckets[i])
{
int bucketIndex = values[index].GetHashCode() & (newBuckets.Length - 1);
newBuckets[bucketIndex].AddFirst(index);
}
buckets[i] = null;
}
buckets = newBuckets;
}
private int NextPowerOfTwo(int n)
{
if ((n & n-1) == 0)
return n;
int output = 0;
while (n > output)
{
output <<= 1;
}
return output;
}
}
维护开放节点数组和访问节点哈希表的旧方法需要 n*(1+a)*size(T) space,其中 a 是 nodes_in_the_queue 超过 total_nodes_found 并且 size(T) 是节点的大小。
此方法需要 n*(size(T) + size(int))。如果您的节点明显大于 int,这可以节省很多。