问题描述
我正在编写一个用于对有向图进行拓扑排序的程序。
设顶点数为V ≤ 2000
,边数等于E ≤ V * (V - 1) / 2
,则
程序使用的内存必须少于 10 * V + 2 * E + 4Mb
。
我尝试将图形存储为邻接矩阵和邻接列表,但在这两种情况下,当 V = 2000
和 E = 1999000
时,我的程序内存不足。
告诉我如何最好地存储图形,以便将内存使用量降至最低。
implementation via adjacency lists
implementation via adjacency matrix
解决方法
根据您的内存使用公式,每个边使用的字节数不应超过 2 个字节,每个顶点使用的字节数不应超过 10 个字节(可能会有一些适合 4Mb 溢出空间的小开销除外)。
您的邻接表实现将邻接表存储为一个指针数组。这是不可行的,因为指针是 4 个或更可能是 8 个字节。 索引您的顶点,并用 uint16_t(2 个字节)替换指向顶点的指针。
详情:
- 将所有边存储在一个数组中:
uint16_t edges[E];
特定顶点之外的所有边将连续存储在此数组中。首先从第一个顶点开始边缘,然后从第二个顶点开始边缘,依此类推。 - 有一个
uint32_t vertices[V][2];
数组作为顶点。存储的两个值是边数组的索引,以及该顶点外的边数。
很容易看出这使用 2E+8V 字节来表示您的图形。
当您运行拓扑排序时,您需要对每个顶点进行计数(描述顶点剩余的入边数)。为此使用数组 uint16_t in_count[V];
。 uint16_t
又足够了,因为当 V<=2000
时,没有顶点可以有超过 1999 条入边。
这会为每个顶点使用额外的 2 个字节,根据需要总共使用 2E+10V
。
请注意,通过不存储边的数量,您可以轻松地为每个顶点节省 4 个字节,而是依靠特定顶点的边的结束索引作为下一个顶点的开始索引。您必须在最后一个顶点之后添加一个额外的索引来标记最后一条边的结束。我注意到这很聪明,但在 V
(原则上你也可以通过更紧密地打包数据来节省更多。2000 uint16_t。代码访问存储的数据将开始变得非常难看)。
,我想我找到了解决问题的最简单的方法。 我刚开始将图形存储为位数组
Graph* createGraph(int V) {
Graph* graph = malloc(sizeof(struct Graph));
graph->V = V;
graph->visited = (int*) calloc(V,sizeof(int));
graph->adj = (unsigned char*) calloc(((V * V) / 8 + 1),sizeof(unsigned char));
return graph;
}
void addEdge(Graph* graph,int src,int dest) {
int newNode = src * graph->V + dest;
graph->adj[newNode/8] |= (1 << newNode % 8);
}
void DFS(Graph* graph,int v,Stack* stack,Data data) {
if (graph->visited[v] == 1) {
fprintf(data.out,"impossible to sort");
freeGraph(graph);
freeStack(stack);
shutdown(data);
}
if (graph->visited[v] == 0) {
graph->visited[v] = 1;
for(int u = 0; u < graph->V; u++) {
int n = v * graph->V + u;
if (graph->adj[n / 8] & (1 << n % 8))
DFS(graph,u,stack,data);
}
graph->visited[v] = 2;
push(stack,v);
}
}