将节点添加到单链列表的中间

时间:2018-09-25 00:27:04

标签: c pointers struct singly-linked-list

所以我有下面要显示的代码

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,但希望将其添加到位置1,这是2所在的位置,因此新的更新列表看起来像1、4、2、3。

1 个答案:

答案 0 :(得分:1)

在链接列表中的任何地方插入都是不可思议的,唯一真正的挑战是在处理三个条件时保持指针笔直:

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

第一种情况,插入第一个(或新的第一个)节点仅需要分配一个新节点,然后将其插入列表中的第一个节点,或者如果头节点已经存在,则设置newnode->next = head;和{ {1}}

最后插入并没有太大不同,您只需迭代到最后一个节点并设置head = newnode;

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

不需要任何花哨的内容,只需使用一个last->next = newnode;(或next)指针连接节点的框图即可。一个简单的列表将使两个节点(pNextA)起作用,例如

B

然后在要插入新节点的位置断开列表,例如

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

这使您可以可视化所需的步骤。找到 A B +------+ | +------+ | node | / | node | | next |--> \ | next |-->NULL +------+ / +------+ | new +------+ | node | | next |--> +------+ ,设置node A,设置new->next = B;注意:,您必须在要插入的位置之前处停在节点上新节点)的结果将是:

A->next = new;

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

由于@WhozCraig指出,在处理列表操作(或通常需要在函数内分配新内存块的指针,通常需要更改函数地址)的情况下,您将拥有一个插入函数来处理所有三种情况指针),这有助于将指针的地址传递给函数(以便函数接收指针本身-而不是指针的副本)。

无论您是需要初始分配给列表包装器,还是在一个简单的列表中(可以在其中分配一个新节点作为列表中的第一个节点),传递指针的地址都可以在函数中进行更改而无需必须返回并在调用函数中将返回值指定为新地址。

为您的 A new B +------+ +------+ +------+ | node | | node | | node | | next |--> | next |--> | next |-->NULL +------+ +------+ +------+ 添加几个typedef gelement_t和为struct GraphicElement类型添加rgraphic_t,都减少了键入操作,从而删除了一些struct RasterGraphic风格上的尴尬,您可以通过以下方式考虑您的MixedCase函数。

首先,您的InsertGraphicElement函数将成功失败,因此,请选择一个有意义的返回类型以表明成功失败。在处理列表时,返回指向新插入的节点的指针很有帮助,并且即使失败也可以返回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->fileName

现在您位于函数中处理答案开头标识的3种情况的位置。如果还没有 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 */ 节点,则将添加第一个节点(因此您不需要询问位置)。如果不是第一个节点,并且请求的位置是(*pA)->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 */ } ,{{1} }和create_list()。(在其中创建create_node()列表的地方,您的add_node()节点,最后将该节点添加到列表中的给定位置-留给您)

将其全部放入并添加辅助函数,一个简短的例子是:

RasterGraphic

使用/输出示例

GraphicElement

内存使用/错误检查

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

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,#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

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

仔细检查一下,如果还有其他问题,请告诉我。