【问题标题】:C - How to Iterate through sub-nodes in a linked list?C - 如何遍历链表中的子节点?
【发布时间】:2015-10-23 12:29:15
【问题描述】:

我正在开发以下程序:

...

typedef struct node_tag{
    int id;
    char pLabel[10];
    struct node_tag *pNext;
    struct node_tag *pInner;
    }NODE;

...

int main(int argc, char** argv)
    {

    NODE *list = create_node("start");
    add_node(&list, "MUSIC");
    add_node(&list, "VIDEOS");
    add_node(&list, "PICTURES");
    create_sub_node( &list, "2015" ); // sub node of PICTURES
    create_sub_node( &list, "Trip" ); // sub node of 2015
    print_nodes(list);

    return (EXIT_SUCCESS);
    }

输出:

|
|-> [ID:4] PICTURES
|   |-> [ID:40] 2015
|   |   |-> [ID:400] Trip
|
|-> [ID:3] VIDEOS
|
|-> [ID:2] MUSIC
|
|-> [ID:1] start
|

到目前为止,一切都按我的意愿进行,从输出中可以看出。但是我想实现创建子节点的不同方法。我现在所拥有的非常有限,因为它只能创建 2 个子节点的深度,但我想拥有无限的深度:

void create_sub_node( NODE **handle, char* label )
    {
    NODE *new = malloc( sizeof(NODE) );

    new->pNext = NULL;
    new->pInner = NULL;
    strncpy( new->pLabel , label , strlen(label) +1 );   

    if( (*handle)->pInner == NULL )
        {
        new->id = (*handle)->id * 10;
        (*handle)->pInner = new;        
        }
    else if( (*handle)->pInner->pInner == NULL ) 
        {
        new->id = (*handle)->pInner->id * 10;
        (*handle)->pInner->pInner = new;
        }
    }

我尝试实现 while 循环,它可以遍历内部节点,直到找到 NULL 一个,然后创建新节点。我遇到的问题是,当我遍历节点时,指针地址发生了变化,我留下了一个不再工作的大混乱。

我尝试复制内部节点的所有地址,但无法再次重新分配它们。

问题是我必须将子节点添加到列表中,因此我将更改各种指针地址但要做到这一点看起来我需要一个列表的副本,我可以在其中使用它,以便原始地址不变。

如何遍历子节点并创建新节点,这样我就不必对一堆 IF 语句进行硬编码?

【问题讨论】:

  • 你能告诉我们你尝试了什么吗?
  • 我认为您的问题是您尝试使用 (*handle) = (*handle)->pInner 之类的东西进行迭代,而您应该在循环中使用 tmp = *handle; 并使用 tmp = tmp->pInner。但我真的不能说,因为我还没有看到你的代码。
  • 你确定你的create_sub_node 函数应该像这样工作吗?如果您想在创建PICTURES 节点后创建MUSIC 的子节点怎么办?如果add_node 会返回一个节点,那么如果将那个 节点传递给create_sub_node,而不是让它遍历你的节点,那么 if 会更有意义。
  • @Groo 这是一个有趣的观察,但后来我想修改create_sub_node 函数,使其在我将传递其 ID 的节点下创建子节点。我认为如果我要朝那个方向发展,整个返回节点的业务将会在主代码中变得混乱。

标签: c pointers linked-list nodes


【解决方案1】:

我的 C 有点生锈了,但是:

NODE* tail(NODE* list) {
     if (list == NULL) return NULL;
     NODE* current = list;
     while (current->pInner != NULL) { current = current->pInner; }
     return current;              
}

那么你的函数就变成了:

void create_sub_node( NODE **handle, char* label )
{
    NODE *new = malloc( sizeof(NODE) );

    new->pNext = NULL;
    new->pInner = NULL;
    strncpy( new->pLabel , label , strlen(label) +1 );   

    NODE* last = tail((*handle));
    new->id = last->id * 10;
    last->pInner = new;        

}

【讨论】:

  • 这比我所拥有的要少,但我已经尝试过你的代码(我已经将函数调用中的代码直接添加到 create_sub_node 中,它解决了我的问题!也许我的大脑有点融化了花太多时间在上面,我可能会遗漏一些东西。还有一个问题:current = current->pInner 为什么不修改列表的原始内容?我的意思是我知道它是指针的副本但它仍然指向到某事,看起来某事被修改但不在原始列表中。为什么会这样?
  • @ShadyProgrammer: current 只是一个访问列表中节点的局部变量,但不会修改它们。
  • 但是在我的功能结束时,我做了current->pInner = new;,它更新了我的原始列表,根据你说的不应该做?我很困惑。
  • @ShadyProgrammer 在 while 循环结束时,current 将包含链表中的最后一个元素。在这种情况下,current->pInner 将是 NULL,因此您正在使 current 将其内部指针指向您创建的 new 节点。还要注意 -> 运算符,它取消引用指针以修改内容(因为当前是 NODE*)
【解决方案2】:

你的链表更像一棵树:

|-> MUSIC
|   |-> Punk
|   |-> Funk
|-> VIDEOS
|   |-> Cats
|   |-> Cars
|   |-> Parties
|-> PICTURES
|   |-> 2014
|   |   |-> Skiing
|   |   |-> Trip
|   |   |-> Thksgvg
|   |-> 2015
|   |   |-> Birthday
|   |   |-> Wedding

您通过pNext 转到垂直线上的下一个节点(下一个最老的兄弟节点),然后通过pInner 更深一层(最年长的孩子)。节点的所有年轻兄弟姐妹和所有子节点都可以通过节点指针访问。你也可以保留一个指向parant的指针,这样你就可以向上走,而不仅仅是向下。

如果你有创建节点的功能并且子节点返回新节点,你可以很容易地构建这样的树:

int main(int argc, char **argv)
{
    NODE *list = NULL;         // list head
    NODE *p;                   // first-generation child 
    NODE *q;                   // second-generation child

    p = add_node(&list, "MUSIC");
        create_sub_node(p, "Punk");
        create_sub_node(p, "Funk");
    p = add_node(&list, "VIDEOS");
        create_sub_node(p, "Cats");
        create_sub_node(p, "Cars");
        create_sub_node(p, "Parties");
    p = add_node(&list, "PICTURES");
    q = create_sub_node(p, "2014");
        create_sub_node(q, "Skiing");
        create_sub_node(q, "Trip");
        create_sub_node(q, "Thksgvg");
    q = create_sub_node(p, "2015");
        create_sub_node(q, "Birthday");
        create_sub_node(q, "Wedding");

    print_nodes(list);

    return (EXIT_SUCCESS);
}

构建树时,必须将最小兄弟节点的pNext 更改为新节点,其pNext 为NULL。确保仅在附加第一个节点时才更改列表的头部。同样,仅在添加第一个子节点时更改节点的pInner。

您通常必须区分两种情况:第一个节点和后续节点。组合这些情况的一种技术是使用指向节点指针的指针遍历列表。该指针首先指向列表或子列表的头部,然后指向节点的pNext,f 有任何节点,即。

这里有两个函数可以和上面的main一起工作:

NODE *add_node(NODE **head, const char *label)
{
    NODE **p = head;

    // walk to the end
    while (*p) p = &(*p)->pNext;    

    // append new node
    *p = malloc(sizeof(**p));
    (*p)->pInner = NULL;
    (*p)->pNext = NULL;
    snprintf((*p)->pLabel, sizeof((*p)->pLabel), "%s", label);        

    return (*p);
}

NODE *create_sub_node(NODE *node, const char *label)
{
    NODE **p = &node->pInner;

    // walk to the end
    while (*p) p = &(*p)->pNext;

    // append new node
    *p = malloc(sizeof(**p));    
    (*p)->pInner = NULL;
    (*p)->pNext = NULL;
    snprintf((*p)->pLabel, sizeof((*p)->pLabel), "%s", label);

    return *p;
}

【讨论】:

  • 感谢以上建议。我明白你在说什么,但你不认为它不是为子节点创建和返回变量而是为函数create_sub_node 提供要在其下创建子节点的节点的 ID 更简洁吗?
  • 当然,您可以为 id 和节点保留一个单独的查找表。 (它应该是一个哈希或类似的东西以提高效率。)如果你有一个函数node_from_id(),可以使用相同的函数插入:create_sub_node(node_from_is(99), "Mexico")。使用最后一个节点的好处是不用查找;你已经有了。
  • 仅供参考,我已经放弃了将列表视为一个整体并使用复杂的循环遍历它的原始方法,转而使用您的方法来查看头部,第一代,第二代等等作为相互链接的单独列表,看起来我将不得不使用某种动态指针数组来跟踪新一代和新列表。哦,男孩,这会变得毛茸茸的......再次感谢您的建议:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-08
  • 2019-11-20
  • 2019-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多