【问题标题】:How can I understand linked lists?我如何理解链表?
【发布时间】:2012-11-28 11:57:14
【问题描述】:

我试图了解它们的工作原理,但我遇到了很多困难。有没有人愿意直观地解释它们,或者提供他们认为对刚开始接触该主题的人有用的资源?

假设我有这个:

struct node
{
   int nodeNum;
   nodes *next;
}

要创建“头”节点,我将执行以下操作:node *head = new node;,这样我的链接列表现在看起来像 。分配后:

head->nodeNum = 10;
head->next = NULL;

我们有。现在,如果我想写一个插入节点的函数,我可以写:

void insert(node *previousNode, int num)
{
    previousNode = new node;
    previousNode->nodeNum = num;
    previousNode->next = NULL;
}

如果我要这样做,比如说insert(head, 20);,我的新列表看起来像?

如果一切正确,我如何使用此信息从列表中搜索和/或删除节点?例如,如head = head->next; 所述,遍历节点并不是很直观。这是如何工作的?

您可以提供任何使该主题更易于理解的建议都会很棒。感谢大家的帮助!

【问题讨论】:

  • 你为什么不拿一个示例代码来玩玩呢?
  • 我发现了解数据结构的最佳方法是使用实​​现,对其执行操作,并在操作后使用调试器查看数据。
  • 您的insert 函数实际上并未将新节点链接到列表中。它只是创建一个新节点,当函数退出时,指向该节点的指针将被丢弃。
  • 我觉得你的插图真的不是很“列表式”。也许它们在某些技术意义上是有意义的,但是如果您对数据结构的可视化不代表直观的功能,您可能会感到困惑。制作看起来像列表的插图,并开始为您的指针绘制箭头。
  • 从房子周围拿一堆东西,收集几根绳子。事物是节点,字符串是指针(箭头)。使用松散的字符串作为变量和参数。然后用它建立你的列表。或者使用乐高,或者铅笔和纸——但远离代码。

标签: c++ linked-list


【解决方案1】:

您的插入功能无法正常工作;它只是创建一个新节点而不将其添加到列表中,并在函数返回时丢失它(导致内存泄漏):

head -> 10 -> NULL     becomes    head -> 10 -> NULL
                                  (lost)  20 -> NULL

相反,它应该将列表的旧尾部链接到新节点,并将新节点插入旧节点之后:

void insert(node * prev, int num) {
    node * new_node = new node;
    new_node->nodeNum = num;
    new_node->next = prev->next;  // add the old tail after the new node
    prev->next = new_node;        // add the new node after the old node
}

insert(head, 20); // insert 20 after the head
// head -> 10 -> NULL   becomes    head -> 20 -> 10 -> NULL

如何使用此信息从列表中搜索和/或删除节点?

要进行迭代,您需要维护自己的指向正在查看的元素的指针;这从head 开始,然后跟随next 指针直到它到达末尾(即next 为空):

for (node * n = head; n; n = n->next) {
    if (n->nodeNum == 20) {
        std::cout << "Found node 20!\n";
        break;
    }
}

要从单链表中删除一个节点,你需要一个指向它之前的节点的指针,以便更新它的next指针:

void remove_next(node * prev) {
    if (prev->next) {
        node * next = prev->next->next;  // Get the tail after the removed node
        delete prev->next;
        prev->next = next;               // Add the tail after the remaining node
    }
}

remove_next(head);
// head -> 20 -> 10 -> NULL    becomes    head -> 10 -> NULL

【讨论】:

  • insert 根本不修改列表,它只是创建一个与任何内容都没有链接的节点,并在函数结束时泄漏。如果previousNode 指针是通过引用传递的,那么它将修改列表;在这里,只有previousNode 的本地副本被分配了一个新值。
  • @LucTouraille:确实如此;我没有仔细阅读。
【解决方案2】:

你的问题在这里:

void insert(node *previousNode, int num)
{
previousNode = new node;
previousNode->nodeNum = num;
previousNode->next = NULL;
}

insert(head, 20);

以下是这段代码的作用: previousNode = new node; 创建一个指向节点的指针并将该指针分配给 previousNode。 PreviousNode 最初是一个复制头,现在它指向新的东西。您现在将值分配给新节点。换句话说,这个插入的实现并没有插入。

你想做的更像是:

void better_insert(node *previousNode, int num)
{
    node *post_node = new node;    #create a brand new pointer to a brand new node
    post_node->nodeNum = num;      #give it a number
    post_node->next = previousNode->next; #we want previousNode to be behind new node
    previousNode->next = post_node;     
}

它的作用是:在创建一个新节点并用新指针指向它之后,我们给它一个数字。接下来就是理清指针指向的地方……

让我们假设我们在一个链表中挥动一些节点。所有小写​​字母都是指针,好吗?

a->next = b

现在说我们希望节点 x 出现在 a 之后,并且有数字 10... 我们称之为 `better_insert(a, 10)

post_node 指向一个新节点(我们的节点 x),并被分配了 10.cool...

我们想要:

a->next = x
x->next = b

我们有:

a->next = b
x->next = null

函数的最后两行只是随机播放内容,直到符合要求

所以更详细...

我们有:

a->next = b
x->next = null

所以我们称之为:

post_node->next = previousNode->next; #we want previousNode to be behind new node

现在我们有: a->下一个 = b x->下一个 = b

现在我们调用:

previousNode->next = post_node;

我们最终得到:

a->next = x
x->next = b

或者换一种说法:

a->next = x
a->next->next = b

【讨论】:

  • 您介意详细说明执行 post_node->next = previousNode->next; 时会发生什么吗?和 previousNode->next = post_node;我很难概念化它是如何工作的。
  • 您对insert 当前行为的解释不太正确:previousNode 不是head,它是head副本。所以insert根本没有修改原来的链表,只是创建了一个函数结束时丢失的节点。
【解决方案3】:

您需要在代码中使用更多变量。 insert 操作修改 两个 节点。需要将前一个节点改为指向新节点,并且需要创建新节点并使其指向前一个节点之后的节点(可以为NULL也可以不为NULL)。

void insert(node *previousNode, int num)
{
    node *newnode = new node;
    newnode->nodeNum = num;
    newnode->next = previousNode->next;
    previousNode->next = newnode;
}

要遍历列表,您需要跟踪“当前节点”,您可以将其从一个节点更改为下一个节点:

while (currentNode != 0) {
    do_something_with(currentNode);
    currentNode = currentNode->next;
}

当然,如果do_something_with 删除了该节点,那么之后您就不能再继续前进了。此外,要从单链表中删除节点,您需要一个指向节点 before 的指针。因此,在这种情况下,您的循环可能会跟踪两个节点,当前节点和先前节点,而不仅仅是一个。

【讨论】:

    【解决方案4】:

    您使用的术语让您感到困惑。希望这个类比不会让你更加困惑。从本质上讲,将链表想象成一组可怕的门口,一旦你进入一个门口,它就会在你身后关闭,你只能看到那个房间里的东西或去下一个房间。从这个走廊的外面,你只知道入口在哪里,不知道里面有什么。

    所以,从你的链表结构之外,你只知道入口,一些指针ll_node *head;head 内部是一些数据和指向链表中下一个节点的指针。遍历该链表就像从走廊的入口head 开始,一次进入一个走廊,直到找到您要查找的节点一样简单。

    ll_node *current_location = head;
    while (current_location != NULL)
    {
       // if we are at the node that you were hoping to reach, exit.
       if (current_location->nodeNum == target_data_im_looking_for)
       {
          break;
       }
       // this isn't the node you're looking for, go to the next one.
       current_location = current_location->next;
    }
    

    同样,插入一个节点应该遍历到链表的末尾(直到current_location-&gt;next == NULL),并将最后一个元素的下一个指针替换为您创建的新ll_node 的内存位置。我不会为你实现它,所以你有机会学习,但这里有足够的东西可以到达你想去的地方。

    【讨论】:

      【解决方案5】:

      通常链表的节点不包含其编号。它通常由一个数据和一个指向下一个节点的指针组成。此外,您应该让您的代码没有拼写错误。

      typedef Data int;
      
      struct node
      {
         Data data; // some Data
         node *next;   // pointer to the next node
      }
      

      一般来说,您也不希望在指定节点之后再添加新节点,而是希望添加到列表中。 void insert(node *previousNode, int num) 签名建议您在某个指定的前一个节点之后想要新节点。

      从技术上讲,您可以通过三种方式将新节点添加到列表中。到列表的开头或结尾或中间的某个位置。添加到开头是最快最简单的。

      void insert(Data num)
      {
          node* tmp = new node;  //make a new node
          tmp->data = num;       //fill in data
          tmp->next = head;      //set the next element of new one to be the current head
          head = tmp;            //set new element as the head
      }
      

      这样你就可以在列表前面放一个新元素。 你总是从头到尾遍历一个单向链表。

      void print_all()
      {
         node* current = head;      // start at head
         while(current != NULL){    // while element exists
           cout << current->data << ' ';   //print its data
           current = current->next; //move to the next one
         }
      }
      

      当你的图片带有数据框和node* next 的箭头时,这很容易理解。

      这是初学者。您需要修改insert 函数来处理列表最初为空的特殊情况。 IE。 head == NULL.

      此外,我强烈建议您尝试以面向对象的方式实现它。 使用struct node 编写class List

      class List {
        private:
          node* head;
        public:
          List();
          void insert(data);
          void print_all();
      }
      

      尝试实现这些功能。根据我的经验,当你以这种方式做事时,它有助于组织你对数据结构和容器的思考,而且这样做更像是 C++ 方式。

      【讨论】:

        【解决方案6】:

        在您上面提到的列表中,您只有很少的信息,即 nodeNum。你应该在里面保留更多的离子。例如,在值为 nodeNum = 10 的节点上,应该有一些您想从该节点获取的其他信息。所以当用户调用时

        node* search(node *head,int num){
              node *temp = head;//copy node reference into temp variable
              while (temp != NULL){
                    if (temp->nodeNum == num){
                       return temp;
                    }
                    temp = temp->next;
              }
        
        } 
        

        你可以通过node获取该节点中的数据

         res = search(head,10);
        

        删除

        bool delete(node *head,int num){
              node *temp = head;//copy node reference into template
              node *prev = NULL;
              bool isDelete = false;
              while (temp != NULL){
                    if (temp->nodeNum == num){
                       break;
                    }else
                       prev = temp;
                       temp = temp->next;
                    }
              }
              if (temp != NULL){
                  prev-> next = temp->next;//maintain list
                  delete temp;
                  isDelete = true;
              }
              return  isDelete;
        } 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多