具有多个父节点和子节点的链接列表

时间:2013-08-09 05:21:47

标签: c linked-list doubly-linked-list

我正在尝试设计一个从文件中获取数据的程序,之后它为唯一数据编号,链表也包含父列表和子列表。

数据结构:

                   ____A
                  /    |     
                 B     C    
                 |  /    \  
                 E-->  F   G
                 |     |   |
                 I     J   K

节点可以有多个下一个节点(例如A和C),并且可以有多个以前的节点。

文本文件包含这样的数据,我将从文件中获取数据,将它们转换为链接列表

                    A
                    B
                    E
                    I

                    A
                    C
                    E
                    F
                    J

                    A
                    C
                    G
                    K

我的问题:是否可以创建包含多个下一个或多个先前节点的节点的链表,如果是这样,结构将如何显示?

我尝试了什么:

我创建了一个结构,其中包含父和子的4个整数数组。

struct abcd{
 char data;
 int nodeid;

 int parent[4];
 int child[4];

 struct abcd *next;

}

因此父数组保存大多数前一个节点的节点id(可以是多个,因为例如E(B& C指向它) - >(node-id - 1)。

子数组包含 instant next 节点的node-id(node-id +1)。

A或其他任何节点都没有重复的节点。

输出:

1 :  A <-- 
2 :  B <-- 1
3    E <-- 2,5
4 :  I <-- 3
5 :  C <-- 1
6 :  F <-- 3
7 :  J <-- 6
8 :  G <-- 5
9 :  K <-- 8

希望它清楚,请不要告诉我应该如何实施它。 问候。

6 个答案:

答案 0 :(得分:7)

是的,所以这被称为有向图。并且有大约一千种方法可以实现它。 “正确”的方式完全取决于你将如何使用它,你没有描述过。由于您似乎将此限制为链接列表或双向链接列表,因此我只使用双向链接列表。

转发声明您的数据类型

typedef struct ItemInfo_s ItemInfo;
typedef struct DoubleLinkedListNode_s DoubleLinkedListNode;

像往常一样创建ListNode:

struct DoubleLinkedListNode_s {
    DoubleLinkedListNode *next;
    DoubleLinkedListNode *prev;
    ItemInfo *data;
};

然后创建你的ItemInfo:

struct ItemInfo_s {
    DoubleLinkedListNode *children;
    DoubleLinkedListNode *parents;
    ...  /* other item data */
};

此外,为了理智,请创建所有已创建节点的列表:

DoubleLinkedListNode *items;

现在,我不打算编写所有链表管理功能,但我相信你可以搞清楚。按照惯例,我将(B)写为指向项目B的节点(node.data =&amp; B)。我还要指出任何两个节点用'='链接在一起,而' - '用作未链接(空值)节点链接。我将编写一系列元素[ - (1)=(2)=(3) - ],按惯例,指向项链的指针将始终指向链中的第一个节点(本例中为(1) )。您的给定图形在内存中看起来像这样:

items = [ -(A)=(B)=(C)=(E)=(F)=(G)=(I)=(J)=(K)- ]

A.children = [ -(B)=(C)- ]
A.parents = []

B.children = [ -(E)- ]
B.parents = [ -(A)- ]

C.children = [ -(E)=(G)- ]
C.parents = [ -(A)- ]

E.children = [ -(I)=(F)- ]
E.parents = [ -(B)=(C)- ]

F.children = [ -(J)- ]
F.parents = [ -(E)- ]

G.children = [ -(K)- ]
G.parents = [ -(C)- ]

I.children = []
I.parents = [ -(E)- ]

J.children = []
J.parents = [ -(F)- ]

K.children = []
K.parents = [ -(G)- ]

总共有9个ItemInfos和27个DoubleLinkedListNodes。我几乎没有理由想在实践中实现这一点,但它只使用双链表实现。它可能使列表管理更容易做双链接环(将列表的头部和尾部连接在一起),但是更难以文本形式显示。 :)

答案 1 :(得分:3)

你可以有这样的结构:

struct abcd{
 char data;
 struct abcd *next[10];  //array of next nodes
 struct abcd *prev[10];  //array of previous nodes
}

访问下一个节点时,您可以node->next[i]代替node->next 0<= i < 10。分配/创建节点时,将所有数组元素重置为NULL,以便您没有未初始化节点的垃圾。

因此,假设您为'A'添加了节点,那么您可以为'B''C'添加节点

int idx;
//find index for free element.
for(idx = 0; nodeA->next[idx] && idx < 10; idx++)
   ;
if(idx == 10)
   //dont have free space
nodeA->next[idx] = nodeB;
nodeB->prev[0] = nodeA;

//similarly add for C, you may have to check for appropriate idx!
nodeA->next[idx++]] = nodeC;
nodeC->prev[0] = nodeA;

基本上,您可以创建最多可包含10个下一个或上一个节点的节点。

数组是为了简单起见,您也可以执行struct abcd **next;,其中您可以拥有动态数量的next / prev节点。你必须适当地分配内存。

答案 2 :(得分:3)

链接和双向链接列表是特定种类的有向图,可以优化为您熟悉的head/taildata/next/prev结构。由于您正在扩展其功能,您将失去这种特异性,并希望回到通用有向图结构并从那里开始工作。

使用邻接列表最容易描述有向图: image of graphs with adjacency lists

您可以将其作为列表列表,列表数组或锯齿状数组实现,或者您喜欢。现在,在右边,我以有向图形式绘制了一个双向链表。由于next指针与prev指针不同,因此您的邻接列表需要将它们分开。所以它实际上是双列表的列表:

typedef struct _BPNode { // "Back-Pointing Node"
    void *data;
    struct _BPNode *nexts[];
    struct _BPNode *prevs[];
} Node;

typedef struct _BPGraph { // "Back-Pointing Graph"
    Node **allNodes;
} BPGraph;

或类似的东西。免责声明:我没有在编译器中测试它。以防万一,here's a guide on how to read some of the declarations in there

或者,您可以创建两个有向图,一个向前运行,一个向后运行。但是,这将比这个“后向”图形占用更多内存。它也会运行得更慢(更多的cpu缓存未命中),更不直观,并且释放内存会更麻烦。

答案 3 :(得分:1)

  

是否可以使用具有多个下一个或多个先前节点的节点创建链接列表,如果是这样,结构将如何显示?

是的,这是可能的 - 你必须问自己的问题是“我如何存储大量数据?”,简短的答案是“你必须使用ADT “。回想一下 ADT 数据集合的数学模型

您可以使用任何ADT实现它,特定ADT的选择取决于您计划最常使用的操作。对于我的例子,我将使用动态数组。结构将声明如下(省略节点的特定字段):

struct llnode {
  int item;
  struct llnode *children;
  int length;
  int capacity;
};

...其中项是'A','B','C'等的ASCII码,而children是指向struct llnode数组的指针。但是,您可以为动态数组创建一个单独的结构,使其不那么混乱,但这完全取决于您。同样的想法将适用于父节点。

答案 4 :(得分:1)

您可以尝试通过实现指向数据对象的指针列表来将数据与数据结构分开:

struct data_item {
    unsigned char data;
    unsigned char id;
    unsigned int  count;
    // Whatever other data you want.
};

struct list_node {
    struct data_item *item;
    struct list_node *next;
}

现在,当我们遇到文件中的字符时,我们将它们插入到“存储库”数据结构中。对于这个例子,我将使用一个简单的表,但如果你想节省空间,你可以使用一个列表,如果你想节省空间,同时保持快速搜索速度等。

data_item data_table[UCHAR_MAX + 1] = {0};
...

unsigned char data = read_character_from_file();
struct data_item *di = data_table[data];

if (di == NULL)
    di = new_data_item(data);
else
    ++di->count;

并将它们附加到当前列表中:

struct list_node *list;
if (first_item_in_list())
    list = new_list(di)
else
    list - add_list(list, di);

现在你可以拥有任意数量的这样的列表(如果你事先不知道列表的数量,甚至是列表列表)。

答案 5 :(得分:1)

您所描述的是Graph

一个(双)链接列表实际上只是一个一维的列表,对于你想要的东西来说是一个不恰当的术语。

实施图表有两种主要方式:

  • 邻接列表。每个节点/顶点都有一个传入和传出边的列表。就像你描述的一样。
  • 邻接矩阵。 nn矩阵(其中n是节点/顶点的数量),如果节点[a][b]具有a的边,则b处的条目为char adj_matrix[0x80][0x80]; // I am assuming that you only have ASCII letters memset(adj_matrix, 0, sizeof(adj_matrix)); // initialise empty 1}}。

使用哪个取决于您的使用案例。根据经验法则:如果你有许多顶点(数万个)并且你可以平均用每个顶点限制每个顶点的边数量,那么你应该使用列表。在其他用例中,你最好使用矩阵(主要是因为易于实现)。

我假设你的用例仅限于ASCII字母,所以我实际上在这里使用矩阵。通过适当的优化(位域等),您可以非常快速地浏览它。

您的实施可能如下所示:

adj_matrix['A']['C'] = 1; // edge from A --> C

插入元素会像:

for (i = 'A'; i <= 'Z'; i++)
    if (adj_matrix[i]['A'])
        // A has an incoming edge from i

要确定“A”的所有传入边缘,您必须迭代矩阵:

for (i = 'A'; i <= 'Z'; i++)
    if (adj_matrix['E'][i])
        // E has an outgoing edge to i

以相反的方式传出

__builtin_clzll

如上所述,使用位域和位扫描指令(例如gcc _bit_scan_reverse,icc {{1}})可以显着提高空间和时间性能。