【问题标题】:Linked List Reversal has errors链表反转有错误
【发布时间】:2021-07-16 05:31:43
【问题描述】:

现在在我们的课堂上,我们正在学习节点和链表,并且正在开发我们的第一个链表程序。

老师给了我们以下指导:

确保您的 main 函数将接受来自 STDIN 的 10 个字符并使用这些字符创建一个链表(这样您的节点将有一个 char 成员)。然后,添加一个名为reverse 的附加函数。 reverse 函数的目的是创建一个链表的副本,其中节点反转。最后,打印出原链表和反向链表。

我已经把它全部写出来了,而且我已经编译它没有错误 - 但程序没有按预期工作,我不完全确定为什么。我确信这与我如何设置指针以“遍历”节点有关 - 因为我放入的调试显示它每个用户输入字母循环两次。规范是我们只应该使用一个函数,我们将Node* 传递给函数,它返回相同的值。该函数无法打印出任何内容 - 只能创建与第一个相反的第二个列表。

任何帮助将不胜感激,我在这方面还不是很擅长,我确信我犯了一些相当愚蠢的错误。

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

//struct declaration with self-reference to make a linked list
struct charNode {
    char data;
    struct charNode *nextPtr;
    struct prevNode *prevPtr;
};

typedef struct charNode Node; //makes Node an alias for charNode
typedef Node *NodePtr; //makes NodePtr an alias for a pointer to Node (I think?)

//function declaration for a reverse function
Node* reverse(Node *stPtr);

int main(void)
{
    //main function takes 10 letters and puts them in a linked list
    //after that, it calls the reverse function to create a reversed list of those characters
    //lastly it prints both lists
    NodePtr newNode = NULL;
    char input;
    Node* revStart;
    unsigned int counter = 0;

    printf("Enter 10 letters to make a list: ");
    NodePtr currentPtr = NULL; //sets currentPointer to startNode.
    NodePtr previousPtr = NULL; //set previousPointer to null to start

    while(counter<= 10)
    {
        scanf("%c", &input); //gather next letter
        NodePtr newNode = malloc(sizeof(Node)); //creates a new node
        if (newNode != NULL) //checks to make sure the node was allocated correctly
        {
            newNode->data = input; //makes the new node's data == input
            newNode->nextPtr = NULL; //makes the nextPtr of the newNode NULL
        }
        currentPtr = newNode; //sets currentPtr to the address of the newNode
        if(previousPtr == NULL) { //first time around previousPtr == NULL
            newNode->nextPtr = newNode;
            previousPtr = newNode; //sets previousPtr to the address of the new node (1st time only)
        } else { //afterwards, currentPtr won't be NULL
            previousPtr->nextPtr = currentPtr; //last node's pointer points to the current node
            previousPtr = newNode; //update previous pointer to the current node
        }
        ++counter;
        //debug
        printf("\nLoop #%d\n", counter);
    }
    revStart = reverse(newNode);
    puts("The list is: ");
    while (newNode != NULL){
        printf("%c --> ", newNode->data);
        currentPtr = currentPtr->nextPtr;
    }
    puts("NULL\n");
}

//reversing the nodes
Node* reverse(Node *stPtr)
{
     //make a new node
    NodePtr currentPtr = stPtr->nextPtr; //get the next letter ready (this will point to #2)
    NodePtr prevRevPtr = NULL; //previous reverse node pointer
    Node* revStart;
    for(unsigned int counter = 1; counter <= 10; ++counter)
    {
        NodePtr revNode = malloc(sizeof(Node));
        if(revNode != NULL) //if reverseNode is allocated...
        {
            if(prevRevPtr = NULL) //if previousReversePointer = NULL it's the "first" letter
            {
                revNode->data = stPtr->data; //letter = current letter
                revNode->nextPtr = NULL; //this is the "last" letter, so NULL terminate
                prevRevPtr = revNode; //previousReversePointer is this one
            }else //after the first loop, the previous ReversePointer will be set
            {
                revNode->data = currentPtr->data; //set it's data to the pointer's data
                revNode->nextPtr = prevRevPtr; //reverseNode's pointer points to last node entered
                currentPtr = currentPtr->nextPtr; //moves to next letter
                prevRevPtr = revNode; //changes previous reverse node to current node
                if(counter == 10)//on the last loop...
                {
                    revStart = revNode; //set revStart as a pointer to the last reverse node
                    //which is technically the "first"
                }
            }
        }    
    }
    return revStart;
}

【问题讨论】:

  • 首先,您不需要在您的反向函数中有counter 循环或执行任何malloc 调用。除非您还想制作列表的副本,否则列表反转不需要额外的内存。您需要做的就是重新链接指针。另请注意,在您当前的代码中,revStart 仅在您的计数器达到 10 时才设置。这非常危险,因为否则它未初始化并用于返回。更不用说您假设列表具有特定长度。您的测试prevRevPtr = NULL 是一项作业。使用== 运算符进行比较。
  • 您的代码过于复杂。您不需要双链接来反转列表。忽略“prev”指针并像这样反向:Node *rev = NULL; while (list) { /* pop from list */ Node *elt = list; list = list-&gt;next; /* push onto rev */ elt-&gt;next = rev; rev = elt; } // rev now points to head of reversed list 现在您可以遍历反向列表一次并修复“prev”指针,如果您真的需要它们。还有几行。
  • 如果你想争辩说,列表反转函数有一个前提条件是它只反转长度为 10 的列表,在较短的列表上有未定义的行为,并且只部分反转较长的列表,我我不会坐在这里和你在 cmets 争论。你已经下定决心了。
  • while (list) 等价于while (list != NULL)。与 Python 无关。
  • 我的意思是执行列表反转的函数应该是通用的。它不需要知道您的列表是否有 10 个节点、一百万个节点、1 个节点或为空。无论如何它都应该工作。这样也会更简单。

标签: c linked-list nodes


【解决方案1】:

假设您的列表从一开始就正确连接,反转双链表基本上是这样的:

Node *reverse(Node *stPtr)
{
    Node *lst = stPtr, *cur = stPtr;
    while (cur)
    {
        Node *tmp = cur->nextPtr;
        cur->nextPtr = cur->prevPtr;
        cur->prevPtr = tmp;
        lst = cur;
        cur = tmp;
    }
    return lst;
}

那就是 。所有这一切都是遍历列表,交换指针,并保留最后处理的任何节点。正确完成后,列表仍将终止(第一个节点 'prev' 为 null,最后一个节点 'next' 为 null,并且之间正确连接。

强烈建议在调试器中通过此函数遍历列表枚举。每次迭代时,观察cur 在列表中向下移动时会发生什么,在活动节点的nextPtrprevPtr 值交换时会发生什么,以及lst,它始终保留处理的最后一个节点。完成后就是新的列表头。

【讨论】:

    【解决方案2】:

    好的,所以我们不需要在评论中没有换行符:

    Node *reverse(Node *list) {
      Node *rev = NULL; 
      while (list) {
        Node *elt = list;    // pop from the list
        list = list->next;   
        elt->next = rev;     // push onto reversed list.
        rev = elt; 
      }
      return rev;
    }
    

    【讨论】:

    • 是的,我才意识到我的列表是双链接的。这不是必需的,所以我去掉了节点顶部结构中的 prevPtr。
    • 我相信我有你在函数中正确放置的内容。现在我只需要弄清楚为什么从 STDIN 获取 10 个字符的循环会触发两次。我认为这与我上面可怕的实现有关,所以我要重新开始,因为它看起来相当臃肿。我真的觉得很多事情都归结于我,只是完全没有掌握这些指针和此类工作的原理。
    • C 中的标准输入有点繁琐,因为 1) 通常在您点击 Enter 之前程序什么都看不到(称为行缓冲),以及 2) Enter 添加一个换行符 \n。这很容易忘记。一种解决方案是将整行输入读入字符数组。像char buf[1002]; fgets(buf, sizeof buf, stdin); 这样的东西。这将获取所有键入的字符(最多 1000 个)、换行符和“字符串结尾”终止符,即 0。因此循环变为 for (int i = 0; i &lt; 10; ++i) { if (buf[i] == 0) { printf("Not enough characters!\n"); return 1; } input = buf[i];
    【解决方案3】:

    正如您在 cmets 中所写的,您可能不需要前一个指针;你可以只创建一个单链表。

    您的代码中有几个问题,包括:

    • malloc 返回NULL 时,您仍会继续循环——只有部分代码受后面的if 保护,其余部分不受保护。发生这种情况时,您可能应该退出程序。

    • 您的算法不维护对第一个节点的引用,即链表开始的地方。当您打印列表时,您应该从列表的 first 节点开始,而是从 newNode 开始,这是您创建的 last 节点,所以显然不是除了最后一个节点之外,将打印很多内容。此外,当循环结束时,您拥有的其他两个指针也将指向最后一个节点(currentPtrpreviousPtr)。

    • newNode-&gt;nextPtr = newNode; 在您的列表中临时创建一个无限循环。这在循环的下一次迭代中得到解决,但没有必要让列表循环。

    • 在反向函数中你有if(prevRevPtr = NULL)...你应该得到一个警告,因为这是一个赋值,而不是一个比较。

    其他一些评论:

    • reverse 函数不必要地区分处理第一个节点和其他节点。

    • 它期望列表有 10 个节点也不好。最好只依靠最后一个节点的NULLnextPtr

    • 将链表的第一个节点称为它的头是一个常见的习惯。所以这样命名你的变量是个好习惯。

    • 由于您需要打印初始列表和反向列表,因此最好创建一个打印列表的函数。

    • 由于您需要为初始列表和反向列表创建新节点,因此最好创建一个为您创建节点的函数。

      (我知道您的老师要求只创建 一个 函数,但这只是最佳实践。如果这不适合作业,那么您将不得不使用 @987654331 @相关的代码重复,这是一个坑)。

    • 当您定义类型 NodePtr 时,在您的代码中看到 Node*NodePtr 的混合会令人困惑。

    您的问题首先不清楚是否应该就地反转列表,还是应该在不篡改初始列表的情况下建立一个新列表。 cmets 清楚地表明您需要一个新列表。

    我可能没有涵盖代码的所有问题。这是一个更正的版本:

    #include <stdio.h>
    #include <stdlib.h>
    
    struct charNode {
        char data;
        struct charNode *nextPtr;
    };
    
    typedef struct charNode Node;
    typedef Node *NodePtr;
    
    NodePtr reverse(Node *stPtr);
    void printList(Node *headPtr);
    NodePtr createNode(int data, NodePtr nextPtr);
    
    int main(void) {
        NodePtr headPtr = NULL; // You need a pointer to the very first node
        NodePtr tailPtr = NULL; // Maybe a better name for currentPtr
    
        printf("Enter 10 letters to make a list: ");
        for (int counter = 0; counter < 10; counter++) {
            char input;
            scanf("%c", &input);
    
            NodePtr newNode = createNode(input, NULL);
    
            if (headPtr == NULL) {
                headPtr = newNode;
            } else {
                tailPtr->nextPtr = newNode;
            }
            tailPtr = newNode;
        }
    
        NodePtr revHeadPtr = reverse(headPtr);
        puts("The list is:\n");
        printList(headPtr);
        puts("The reversed list is:\n");
        printList(revHeadPtr);
    }
    
    void printList(NodePtr headPtr) {
        while (headPtr != NULL) {
            printf("%c --> ", headPtr->data);
            // You can just move the head pointer: it is a variable local to this function
            headPtr = headPtr->nextPtr;
        }
        puts("NULL\n");
    }
    
    NodePtr createNode(int data, NodePtr nextPtr) {
        NodePtr newNode = malloc(sizeof(Node));
        if (newNode == NULL) { // If malloc fails, exit the program
            puts("Cannot allocate memory\n");
            exit(1);
        }
        newNode->data = data;
        newNode->nextPtr = nextPtr;
        return newNode;
    }
    
    NodePtr reverse(NodePtr headPtr) {
        NodePtr revHeadPtr = NULL;
    
        while (headPtr != NULL) {
            revHeadPtr = createNode(headPtr->data, revHeadPtr);
            // You can just move the head pointer: it is a variable local to this function
            headPtr = headPtr->nextPtr;
        }
        return revHeadPtr;
    }
    

    【讨论】:

    • 您能否就这个答案提供一些反馈?
    • 有什么反应吗?
    猜你喜欢
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 2014-04-21
    • 1970-01-01
    • 1970-01-01
    • 2021-05-21
    • 2016-09-29
    相关资源
    最近更新 更多