【问题标题】:Inserting node in linked list在链表中插入节点
【发布时间】:2018-07-28 13:29:52
【问题描述】:

我正在尝试在两个索引之间动态插入一个节点,这些索引包含节点类型的结构。数组的第一个元素是头指针,第二个元素是尾。

我正在尝试在数组的两个索引之间动态增长双链表。以下是我到目前为止尝试过的代码。

我也可以动态创建 head 和 tail 作为节点,但根据要求我必须这样做。

保证要插入的节点data值在qllentry[0].dataqllentry[1].data的值之间

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

struct Node {
    int data;
    struct Node *qprev;
    struct Node *qnext;
}Node;

struct Node qllentry[2];


int main()
{

struct Node head, tail;
    head.data = INT_MAX;
    tail.data = INT_MIN;

    head.qnext = &tail;
    tail.qprev = &head;
    head.qprev = NULL;
    tail.qnext = NULL;


    qllentry[0] = head;
    qllentry[1] = tail;
    int key = 20;
    struct Node *curr ;
    struct Node *prev;
    curr= &qllentry[0];

    while(curr->qnext != NULL && curr->data >= key) {
                curr = curr->qnext;
        }
    prev = curr->qprev;

    struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));
        new_node->data = key;
        new_node->qnext = prev->qnext;
        prev->qnext = new_node;
        new_node->qprev = prev;
        if (new_node->qnext != NULL)
            new_node->qnext->qprev = new_node;


    return 0;
}

新节点的插入没有按预期发生在头和尾索引之间。我添加了一些用于调试的打印语句

感谢任何帮助。

【问题讨论】:

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


    【解决方案1】:

    虽然保留一个指向列表头部和尾部的数组(或就此而言是一个指针)没有任何问题,但如果您使用数组,则在分配地址后保持您的数组引用您的列表操作。将&amp;array[x] 与您的列表操作混合只会造成混乱。使用列表时,将其视为列表并忘记数组。

    您的主要问题是您将一个节点迭代到远处寻找插入new_node 的位置,导致您在停止之前迭代到tail。在插入new_node 之前的节点上停止迭代。你可以通过测试来做到这一点:

        /* test curr->qnext->data > key to stop before tail */
        while (curr->qnext && curr->qnext->data > key)
                    curr = curr->qnext;
    

    (注意: 使用变量屏蔽间接级别,就像您接下来使用 prev = curr-&gt;qprev; 所做的那样,只是隐藏了细节——这可能会在以后增加混乱。这是完全合法的,但请谨慎使用。 ..)

    现在您可以集中精力在&amp;head&amp;tail 之间插入new_node 需要插入的位置。

    在任何列表插入中,您只需重新连接当前节点的指针->next 指向new_node 和下一个节点的指针->prev 指向new_node。要完成插入,您的new_node-&gt;qprev 指向currnew_node-&gt;qnext 指向curr-&gt;next,例如

        new_node->qprev = curr;         /* rewire pointers */
        new_node->qnext = curr->qnext;
        curr->qnext->qprev = new_node;
        curr->qnext = new_node;
    

    (注意:最简单的方法是拿出一张纸和一支 2 号铅笔,然后画一个方块 curr 一个方块new_nodetail 的块,然后为 prev/next 指针画线(对于没有 new_node 的列表和带有它的列表)。然后,按照逻辑,坐下来键盘和啄出来。)

    此外,您必须始终验证您的分配,例如

        /* allocate and VALIDATE! */
        if (!(new_node = malloc (sizeof *new_node))) {
            perror ("malloc - new_node");
            exit (EXIT_FAILURE);
        }
    

    在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放。因此,如果您分配它,请跟踪指向该块的指针和free,当您完成它时。例如,当完成输出列表值(或在专用循环中)时,您可以释放分配的内存,类似于:

        curr = &head;                   /* output list */
        while (curr) {
            printf ("%d\n", curr->data);
            struct Node *victim = curr; /* self-explanatory */
            curr = curr->qnext;
            /* do not forget to free allocated memory */
            if (victim != &head && victim != &tail) {
                free (victim);
            }
        }
    

    总而言之,您可以执行以下操作:

    #include <stdio.h>
    #include <stdlib.h>
    #include <limits.h>
    
    struct Node {
        int data;
        struct Node *qprev;
        struct Node *qnext;
    } Node;
    
    struct Node qllentry[2];
    
    
    int main (void) {
    
        struct Node head = { .data = INT_MAX }, 
                    tail = { .data = INT_MIN },
                    *curr,
                    *new_node;
    
        qllentry[0] = head;     /* keep your array and list operations separate */
        qllentry[1] = tail;
    
        head.qnext = &tail;     /* begin list operations */
        tail.qprev = &head;
    
        int key = 20;
    
        curr = &head;
    
        /* test curr->qnext->data > key to stop before tail */
        while (curr->qnext && curr->qnext->data > key)
                    curr = curr->qnext;
    
        /* allocate and VALIDATE! */
        if (!(new_node = malloc (sizeof *new_node))) {
            perror ("malloc - new_node");
            exit (EXIT_FAILURE);
        }
    
        new_node->data = key;           /* assign value to new_node */
    
        new_node->qprev = curr;         /* rewire pointers */
        new_node->qnext = curr->qnext;
        curr->qnext->qprev = new_node;
        curr->qnext = new_node;
    
        curr = &head;                   /* output list */
        while (curr) {
            printf ("%d\n", curr->data);
            struct Node *victim = curr; /* self-explanatory */
            curr = curr->qnext;
            /* do not forget to free allocated memory */
            if (victim != &head && victim != &tail) {
                free (victim);
            }
        }
    
        return 0;
    }
    

    使用/输出示例

    $ ./bin/llarray
    2147483647
    20
    -2147483648
    

    内存使用/错误检查

    您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

    对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

    $ valgrind ./bin/llarray
    ==8665== Memcheck, a memory error detector
    ==8665== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==8665== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==8665== Command: ./bin/llarray
    ==8665==
    2147483647
    20
    -2147483648
    ==8665==
    ==8665== HEAP SUMMARY:
    ==8665==     in use at exit: 0 bytes in 0 blocks
    ==8665==   total heap usage: 1 allocs, 1 frees, 24 bytes allocated
    ==8665==
    ==8665== All heap blocks were freed -- no leaks are possible
    ==8665==
    ==8665== For counts of detected and suppressed errors, rerun with: -v
    ==8665== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    始终确认您已释放已分配的所有内存并且没有内存错误。

    简单的指针转储/检查

    最后,除了使用调试器单步执行地址之外,您还可以随时编写简短的调试路由来帮助您找出指针处理是否以及在何处出现问题。 (你根本不需要输出任何东西,如果你愿意,你可以检查地址是否相等)这让你可以一次查看所有指针。输出节点指针的简单路由通常很有帮助。你所需要的只是,例如

    void debugptrs (struct Node *list)
    {
        printf ("list pointers:\n\n");
        for (struct Node *iter = list; iter; iter = iter->qnext)
            printf ("prev: %16p    curr: %16p    next: %16p\n", 
                    (void*)iter->qprev, (void*)iter, (void*)iter->qnext);
        putchar ('\n');
    }
    

    这将提供类似于以下内容的输出:

    $ ./bin/llarray
    list pointers:
    
    prev:            (nil)    curr:   0x7ffd56371910    next:        0x1038010
    prev:   0x7ffd56371910    curr:        0x1038010    next:   0x7ffd56371930
    prev:        0x1038010    curr:   0x7ffd56371930    next:            (nil)
    

    我总是发现从头到尾从视觉上遍历地址是很有帮助的。如果某个节点的任何 prev 或 next 不是上一行(或下一行)该节点的地址输出,那么您就知道问题出在哪里。

    查看一下,如果您还有其他问题,请告诉我。

    【讨论】:

      【解决方案2】:

      以下是根据问题中的代码进行了一些修改的代码,我猜它会按预期打印结果:

      dlink.c:

      #include <stdio.h>
      #include <stdlib.h>
      #include <limits.h>
      
      struct Node {
          int data;
          struct Node *qprev;
          struct Node *qnext;
      } Snode;
      
      int main() {
          struct Node *head = (struct Node*)malloc(sizeof(struct Node));
          struct Node *tail = (struct Node*)malloc(sizeof(struct Node));
      
          // init head,
          head->data = INT_MAX;
          head->qnext = tail;
          head->qprev = NULL;
      
          // init tail,
          tail->data = INT_MIN;
          tail->qprev = head;
          tail->qnext = NULL;
      
          int key = 20;
          struct Node *curr = head;
          struct Node *prev;
      
          //get the pointer of the process which has less priority than the current process
          while(curr->data >= key && curr->qnext != NULL) {
              curr = curr->qnext;
          }
          prev = curr->qprev;
      
          printf("head %p, data is %d, next is %p, prev is %p\n", head, head->data, (void *)head->qnext, (void *)head->qprev);
          printf("tail %p, data is %d, next is %p, prev is %p\n", tail, tail->data, (void *)tail->qnext, (void *)tail->qprev);
          printf("prev of new node %p, data is %d, next is %p, prev is %p\n", prev, prev->data, (void *)prev->qnext, (void *) prev->qprev);
          printf("--------------------\n\n");
      
          struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));
          new_node->data = key;
          new_node->qnext = prev->qnext;
          prev->qnext = new_node;
          new_node->qprev = prev;
      
          if (new_node->qnext != NULL)
              new_node->qnext->qprev = new_node;
          else
              tail = new_node;
      
          printf("head %p, data is %d, next is %p, prev is %p\n", head, head->data, (void *)head->qnext, (void *)head->qprev);
          printf("new_node %p, data is %d, next is %p, prev is %p\n", new_node, new_node->data, (void *)new_node->qnext, (void *)new_node->qprev);
          printf("tail %p, data is %d, next is %p, prev is %p\n", tail, tail->data, (void *)tail->qnext, (void *)tail->qprev);
      
          return 0;
      }
      

      运行结果:

      head 0x2380010, data is 2147483647, next is 0x2380030, prev is (nil)
      tail 0x2380030, data is -2147483648, next is (nil), prev is 0x2380010
      prev of new node 0x2380010, data is 2147483647, next is 0x2380030, prev is (nil) // this is same as head,
      --------------------
      
      head 0x2380010, data is 2147483647, next is 0x2380460, prev is (nil)
      new_node 0x2380460, data is 20, next is 0x2380030, prev is 0x2380010
      tail 0x2380030, data is -2147483648, next is (nil), prev is 0x2380460
      

      建议

      • 不要混用 struct (head, tail) 和 struct pointer (new_node),这样容易混淆,容易出错。
      • 单链表就足以完成这样的插入,有一种棘手的方法可以在单链表中插入元素。
      • 为了获得良好的性能,您可以分配一个大缓存,然后从缓存中创建新节点。
      • 编译你的c代码时,添加-Wall选项,这会给你更多的警告。

      【讨论】:

      • 感谢您的建议,但使用数组的主要原因是在给定索引数组的情况下,O(1) 有时间访问尾节点。并且代码不适用于数组的实现。这就是我发布它的原因。如果您能帮助我,那将非常有帮助
      • 如果您尝试将 headtail 指针添加到数组并检查程序,它会得到与我们预期不同的结果。
      • @naveenmarri 您的代码中有一个tail 变量,在您的情况下它的值是INT_MIN,这意味着只有当您插入一个数据= INT_MIN 的新节点时,尾部才会更改,以避免简单地将while(curr-&gt;qnext != NULL &amp;&amp; curr-&gt;data &gt;= key) { 更改为while(curr-&gt;qnext != NULL &amp;&amp; curr-&gt;data &gt; key) {,区别在于使用&gt; 替换&gt;=。那么你的尾节点永远不会改变,你总是可以通过tail变量来引用它,即O(1),因此你根本不需要那个数组。
      • 但我想以优先级队列的方式插入它,这样如果元素碰巧具有相同的优先级,传入节点应该插入到最后。
      • 我也试图实现这个逻辑来构建一个操作系统,它有多个这样的队列。因此尾指针的跟踪将是麻烦的。所以我在一个数组中索引
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-07
      • 2018-04-19
      • 1970-01-01
      • 1970-01-01
      • 2019-04-26
      相关资源
      最近更新 更多