【问题标题】:Adding a node to the middle of a singly linked list将节点添加到单链表的中间
【发布时间】:2018-09-25 00:27:04
【问题描述】:

所以我有我将在下面显示的代码

struct GraphicElement {
char* fileName;
struct GraphicElement* pNext;
    };
struct RasterGraphic {
struct GraphicElement* GraphicElements;
    };

还有

void InsertGraphicElement(struct RasterGraphic* pA)
{
int counter = 1;
int response = 0;
char tempString[256];
struct GraphicElement *newNode = malloc(sizeof(*newNode));
if (newNode == NULL) return;
newNode->fileName = malloc(256 * sizeof(char));
if (newNode->fileName == NULL) return;
newNode->pNext = NULL;


printf("Insert a GraphicElement in the RasterGraphic\nPlease enter the GraphicElement filename: ");
scanf("%s", newNode->fileName);


if (pA->GraphicElements == NULL)
{
    pA->GraphicElements = newNode;
    printf("This is the first GraphicElement in the list\n");
}
else
{

    struct GraphicElement *tempHead = pA->GraphicElements;
    while (tempHead->pNext != NULL)
    {
        tempHead = tempHead->pNext;
        counter++;
    }

    printf("There are %d GraphicElement(s) in the list. Please specify the position (<= %d) to insert at :", counter, counter);
    scanf("%d", &response);

    if (response == counter) {
        tempHead->pNext = newNode;
        return;

    }
}

return;
}

如您所见,我有一个结构定义,然后是一个将节点插入列表的函数。我有它,如果它是它插入的第一个节点并告诉用户它是列表中的第一个元素。我遇到的问题是添加更多元素。现在,代码按顺序添加新节点没有问题。询问用户他们想在列表中插入的位置。现在只要他们选择按顺序添加它就可以完美地工作。我已经尝试了很多事情,我只是无法弄清楚循环的逻辑并能够将节点添加到列表的中间,因此示例输出将是这样的......

列表包含 1、2、3 用户添加了第四个元素 4,但希望将其添加到 2 所在的位置 1,因此新的更新列表看起来像 1、4、2、3。

【问题讨论】:

  • 使用指向指针的指针,遍历列表的实际指针成员。当您点击插入点(或列表中的最后一个指针)时停止。你到家了。

标签: c pointers struct singly-linked-list


【解决方案1】:

在链表中插入任何地方都没有什么神奇之处,唯一真正的挑战是在处理三个条件时保持指针笔直:

  1. 插入第一个节点(或新的第一个节点);
  2. 在现有第一个和最后一个节点之间的某个位置插入一个节点;和
  3. 在列表末尾插入。

第一种情况,插入第一个(或新的第一个)节点只需要分配一个新节点并且插入它是列表中的第一个节点,或者如果头节点已经存在,设置newnode-&gt;next = head;head = newnode;

在最后插入没有太大区别,你只是迭代到最后一个节点并设置last-&gt;next = newnode;

在两者之间插入节点的情况需要更多考虑。保持指针笔直的最简单方法是拿出铅笔和纸,画出列表的指针图,然后在需要插入新节点的地方将列表分成两部分,并找出所需的步骤 在你拿起键盘之前(那样会容易得多)

不需要任何花哨的东西,只需一个框图,其中您的next(或您的pNext)指针连接节点。一个简单的列表将有两个节点(AB)就可以了,例如

       A           B
    +------+    +------+
    | node |    | node |
    | next |--> | next |-->NULL
    +------+    +------+

然后在新节点将被插入的地方打破列表,例如

       A               B
    +------+    |   +------+
    | node |    /   | node |
    | next |--> \   | next |-->NULL
    +------+    /   +------+
                |
               new
            +------+
            | node |
            | next |-->
            +------+

这使您可以可视化所需的步骤。定位node A,设置new-&gt;next = B;,设置A-&gt;next = new;注意:你必须停在节点之前你想插入新节点的位置)结果然后将是:

       A           new         B
    +------+    +------+    +------+
    | node |    | node |    | node |
    | next |--> | next |--> | next |-->NULL
    +------+    +------+    +------+

当您确切了解处理插入所需的操作后,现在拿起键盘并实现该逻辑。

由于您将有一个插入函数来处理所有三种情况,正如@WhozCraig 评论的那样,在处理列表操作(或通常的指针时,您可能需要在函数中分配一个新的内存块来更改指针),它有助于将指针的 地址 传递给您的函数(以便您的函数接收指针本身——而不是指针的副本)。

在您的情况下,您是否需要最初为列表包装器分配,或者在一个简单的列表中,您可以分配一个新节点作为列表中的第一个节点,传递指针的地址允许在函数内进行更改,而无需必须返回并将返回值作为新地址分配给调用函数。

为您的struct GraphicElementrgraphic_tstruct RasterGraphic 类型添加几个typedef gelement_t,以减少键入并删除一些MixedCase 风格的尴尬,您可以考虑一下您的@987654339 @函数的方式如下。

首先,您的InsertGraphicElement 函数将成功失败,因此选择一个有意义的返回类型,可以指示成功失败。在处理列表时,返回指向新插入节点的指针很有帮助,并且即使失败也允许返回 NULL,例如

gelement_t *InsertGraphicElement (rgraphic_t **pA)

由于您传递了指向 RasterGraphic 结构的指针的指针,因此您可以将数据添加到结构中,使其作为实际列表的包装器更有用。添加节点计数器是一种方便的方法来跟踪单链表中的节点数,而不必每次都遍历列表,例如

typedef struct RasterGraphic {
    size_t nelements;
    gelement_t *GraphicElements;
} rgraphic_t;

在你的函数中,你应该验证你有一个分配的指针,如果没有,然后分配给RasterGraphic结构,例如

    int position = 0;

    if (!*pA) {                         /* if list NULL - allocate new list */
        puts ("allocating new list.");
        *pA = malloc (sizeof **pA);
        if (!*pA) {                     /* validate every allocation */
            perror ("malloc-*list");
            exit (EXIT_FAILURE);
        }
        (*pA)->nelements = 0;           /* initialize values */
        (*pA)->GraphicElements = NULL;
    }

接下来,您可以分配和验证新节点以添加为第一个节点或位置,例如

    gelement_t *node = malloc (sizeof *node);   /* allocate node */
    if (!node) {    /* validate */
        perror ("malloc-node");
        exit (EXIT_FAILURE);
    }

接下来收集您的文件名和位置信息,并将node-&gt;fileName 设置为一个新分配的块,该块保存用户输入的文件名(几个帮助函数使这更容易),例如

    node->fileName = get_filename_stdin();  /* request filename */
    if (!node->fileName) {  /* validate */
        free (node);
        return NULL;
    }
    node->pNext = NULL; /* set next pointer NULL */

    position = get_int_stdin (*pA);    /* request position */

您现在处于函数中处理答案开头确定的 3 种情况的位置。如果还没有(*pA)-&gt;GraphicElements 节点,您将添加第一个节点(因此您不需要询问位置)。如果它不是第一个节点,并且请求的位置是0,则作为新的第一个节点插入。 (两者都可以单独处理)

如果请求的位置大于零,则迭代到插入点之前的节点,并如上图所示插入并在那里插入节点。

一种方法是:

    gelement_t *p = (*pA)->GraphicElements;

    if (!p || position == 0) {  /* insert as new head */
        node->pNext = p;
        (*pA)->GraphicElements = node;
    }
    else {  /* insert at position (default end) */
        int n = 0;
        while (n < position - 1 && p->pNext) {  /* locate node before */
            p = p->pNext;
            n++;
        }
        node->pNext = p->pNext; /* set node->pNext to current pNext */
        p->pNext = node;        /* set current pNext to node */
    }

    (*pA)->nelements++; /* increment number of elements in list */

它将根据用户输入处理您的插入。剩下的就是:

    return node;    /* meaningful return to indicate success/failure */
}

(注意:当您对列表操作逻辑感到满意时,将这个函数分解为几个单独处理操作的函数会有所帮助,例如 create_list()create_node() 和 @987654356 @.(您在其中创建 RasterGraphic 列表、GraphicElement 节点,最后将该节点添加到列表中的给定位置 - 留给您)

把它放在一起并添加辅助函数,一个简短的例子是:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>     /* for PATH_MAX */

typedef struct GraphicElement {
    char* fileName;
    struct GraphicElement* pNext;
} gelement_t;

typedef struct RasterGraphic {
    size_t nelements;
    gelement_t *GraphicElements;
} rgraphic_t;

char *get_filename_stdin (void)
{
    char filename[PATH_MAX] = "";
    char *newname = NULL;

    fputs ("enter filename : ", stdout);

    if (fgets (filename, PATH_MAX, stdin)) {
        size_t len = strlen (filename);
        if (len && filename[len-1] == '\n') {
            filename[--len] = 0;
            if (!len) {
                fputs ("error: filename empty.\n", stderr);
                return NULL;
            }
        }
        else if (len == PATH_MAX) {
            fputs ("error: filename exceeds PATH_MAX\n", stderr);
            return NULL;
        }
        newname = malloc (len + 1);
        if (!newname) {
            perror ("malloc-newname");
            exit (EXIT_FAILURE);
        }
        memcpy (newname, filename, len + 1);
    }

    return newname;
}

int get_int_stdin (rgraphic_t *list)
{
    char buf[PATH_MAX];
    int pos = 0;

    if (!list->nelements) {
        puts ("inserting as head.");
        return 0;
    }
    fputs ("index to insert: ", stdout);

    if (fgets (buf, PATH_MAX, stdin))
        if (sscanf (buf, "%d", &pos) != 1 || pos < 0 || 
            pos > (long)list->nelements)
            return list->nelements;

    return pos;
}

gelement_t *InsertGraphicElement (rgraphic_t **pA)
{
    int position = 0;

    if (!*pA) {                         /* if list NULL - allocate new list */
        puts ("allocating new list.");
        *pA = malloc (sizeof **pA);
        if (!*pA) {                     /* validate every allocation */
            perror ("malloc-*list");
            exit (EXIT_FAILURE);
        }
        (*pA)->nelements = 0;           /* initialize values */
        (*pA)->GraphicElements = NULL;
    }

    gelement_t *node = malloc (sizeof *node);   /* allocate node */
    if (!node) {    /* validate */
        perror ("malloc-node");
        exit (EXIT_FAILURE);
    }

    node->fileName = get_filename_stdin();  /* request filename */
    if (!node->fileName) {  /* validate */
        free (node);
        return NULL;
    }
    node->pNext = NULL; /* set next pointer NULL */

    position = get_int_stdin (*pA);     /* request position */

    gelement_t *p = (*pA)->GraphicElements;

    if (!p || position == 0) {  /* insert as new head */
        node->pNext = p;
        (*pA)->GraphicElements = node;
    }
    else {  /* insert at position (default end) */
        int n = 0;
        while (n < position - 1 && p->pNext) {  /* locate node before */
            p = p->pNext;
            n++;
        }
        node->pNext = p->pNext; /* set node->pNext to current pNext */
        p->pNext = node;        /* set current pNext to node */
    }

    (*pA)->nelements++; /* increment number of elements in list */

    return node;    /* meaningful return to indicate success/failure */
}

/* loop over list printing values */
void prn_list (rgraphic_t *list)
{
    size_t n = 0;
    gelement_t *node = list->GraphicElements;

    printf ("\n\n%zu nodes in list\n", list->nelements);

    for (; node; node = node->pNext)
        printf ("%2zu: %s\n", 1 + n++, node->fileName);    
}

/* loop over list freeing memory (pay attention to victim) */
void free_list (rgraphic_t *list)
{
    gelement_t *node = list->GraphicElements;

    while (node) {
        gelement_t *victim = node;
        node = node->pNext;
        free (victim->fileName);
        free (victim);
    }

    free (list);
}

int main (void) {

    rgraphic_t *list = NULL;
    puts ("\nNOTE: pressing [Enter] for index - inserts at end!\n"
        "      [Ctrl+d] at \"filename: \" prompt to end input.\n");

    while (InsertGraphicElement(&list)) {}  /* create list/insert nodes */

    prn_list (list);    /* print list */
    free_list (list);   /* free list */
}

使用/输出示例

$ ./bin/ll_single_insert_ptp

NOTE: pressing [Enter] for index - inserts at end!
      [Ctrl+d] at "filename: " prompt to end input.

allocating new list.
enter filename : one
inserting as head.
enter filename : four
index to insert: 1
enter filename : two
index to insert: 1
enter filename : three
index to insert: 2
enter filename : five
index to insert:
enter filename :

5 nodes in list
 1: one
 2: two
 3: three
 4: four
 5: five

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

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

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

$ valgrind ./bin/ll_single_insert_ptp
==9747== Memcheck, a memory error detector
==9747== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9747== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==9747== Command: ./bin/ll_single_insert_ptp
==9747==

NOTE: pressing [Enter] for index - inserts at end!
      [Ctrl+d] at "filename: " prompt to end input.

allocating new list.
enter filename : one
inserting as head.
enter filename : four
index to insert: 1
enter filename : two
index to insert: 1
enter filename : three
index to insert: 2
enter filename : five
index to insert:
enter filename :

5 nodes in list
 1: one
 2: two
 3: three
 4: four
 5: five
==9747==
==9747== HEAP SUMMARY:
==9747==     in use at exit: 0 bytes in 0 blocks
==9747==   total heap usage: 12 allocs, 12 frees, 136 bytes allocated
==9747==
==9747== All heap blocks were freed -- no leaks are possible
==9747==
==9747== For counts of detected and suppressed errors, rerun with: -v
==9747== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

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

【讨论】:

  • 不错的答案。我从来没有时间做那样的 ascii 艺术。值得一提。我要修改两点。 (1) 当前面提到list 自动变量时,插入逻辑显然更容易。头部插入、位置插入和尾部插入都可以在同一个循环逻辑中使用所提供的出色的指针对指针进行。 (2)。总的小问题,但是一旦有人通过strlen 获得大小以进行动态分配,就不需要strcpy;只需使用大小和memcpy 的东西。您已经支付了 piper 或 nullchar 扫描费用;无需再做一次。无论如何,干得好。
  • 谢谢,伟大的 cmets。我一直认为使用list 自动变量更容易避免运算符优先级讨论(*pA)-&gt;member。我将删除自动变量并结合插入逻辑。我把它分开来或多或少地跟踪点。我将添加一个综合案例,并很好地向吹笛者支付两次费用。
  • @WhozCraig - 我是不是在不影响你思考过程的情况下解决了所有问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-27
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
相关资源
最近更新 更多