【问题标题】:Error in Linked List Insertion at last position在最后一个位置插入链接列表时出错
【发布时间】:2021-06-06 02:24:44
【问题描述】:

我用 C++ 编写了一个链接列表插入程序。当我添加一个函数来在链表的最后一个位置插入一个节点时,我得到了一个奇怪的输出。似乎正在输出地址。这是我的代码。

#include <iostream>
using namespace std;

class Node
{
public:
   int data;
   Node *next;
   Node(int d)
   {
     data=d;
   }
};

class operations
{
public:
    Node *head;
    Node *ptr;

    void insertfirst(int d)
    {
        Node *newnode = new Node(d);
        newnode->next=NULL;
        if(head==NULL)
        {
            head=newnode;
        }
        else
        {
            newnode->next=head;
            head=newnode;
        }
    }

    void display()
    {
        Node *ptr;
        ptr=head;
        while(ptr!=NULL)
        {
            cout<<ptr->data<<" ";
            ptr=ptr->next;
        }
    }

    void insertafter(int key, int d)
    {
        Node *ptr;
        ptr=head;
        while(ptr->data!=key)
        {
            if(ptr->next==NULL)
            {
                cout<<"Key not found";
                return;
            }
            ptr=ptr->next;
        }
        Node *newnode=new Node(d);
        newnode->next=ptr->next;
        ptr->next=newnode;
    }

    void insertlast(int d)
    {
        Node *ptr;
        ptr=head;
        Node *newnode = new Node(d);
        newnode->next=NULL;
        while(ptr->next!=NULL)
        {
            ptr=ptr->next;
        }
        ptr->next=newnode;
    }
};

int main()
{
    operations o;
    o.insertfirst(4);
    o.insertfirst(3);
    o.insertafter(3,5);
    o.insertlast(1);
    o.display();
    return 0;
}

我得到的输出是:

3 5 4 1577825 1

我的预期输出是:

3 5 4 1 

我应该怎么做才能获得预期的输出?

【问题讨论】:

  • 如果目标是链表,为什么类叫操作?这与 DSA 有什么关系?
  • 提供的代码对我来说只是因分段错误而崩溃。
  • 如果首先调用insertlast(),请确保您正确处理head 节点(提示,您确实需要一个初始化head = nullptr;operations 的构造函数,并且您需要检查@987654328 是否@ 在您尝试通过访问 -&gt;next 指针来取消引用 head 之前。如果添加 tail 指针,您将避免在 insertlast() 中进行迭代。

标签: c++ algorithm pointers data-structures dsa


【解决方案1】:

有两种方法可以处理此类问题。

  • 在调试器中运行您的程序。这通常是解决复杂问题的最快方法,因此您应该计划尽快熟悉调试器的使用。由于调试器是一个非常复杂的工具,而且许多流行的调试器使用起来并不那么简单,因此您可能不想为这个特定问题使用一个。但从长远来看:了解如何使用调试器。
  • 在纸上运行您的程序。特别是对于链表,这种方法是纯金的。在纸上画出节点,并画出箭头来跟踪哪个指针指向什么。然后逐行浏览您的程序并在您进行时更新绘图。重要的是,您不要在这里偷工减料,而是要真正逐行执行,一丝不苟地追溯程序的各个步骤,即使一开始看起来很乏味。问题应该以这种方式很快地暴露出来。如果您仍然卡住,请在代码中添加额外的调试输出,以检查您的纸上模拟是否确实准确。如果您确实在纸上犯了错误,那么额外的输出应该可以帮助您发现它。

由于第二种方法需要一些体力劳动,因此尽可能减少表现出不良行为的程序通常是值得的。例如,如果从main 中删除行o.insertfirst(3);,您是否仍然看到问题?你能在问题消失之前删除更多的行吗?

以这种方式缩小错误范围后,抓起一张纸试一试。不要害怕这项工作,以这种方式修复错误是软件工程的基础,而这个特殊问题是提高您在这方面技能的绝佳练习。

寻找错误快乐!

【讨论】:

    【解决方案2】:

    你的问题在于你的函数insertfirst()。您不会始终如一地初始化节点的所有成员,因此当您添加值为 4 的第一个节点时,head 并不总是 NULL(在 C++ 中使用 nullptr),因此您的节点值为 4 点到垃圾指针。

    正如另一个答案中提到的,使用调试器单步执行代码会很快发现这一点。

    C++ 已经走过了漫长的道路,不要再像 C 那样对待它了。使用构造函数(以及默认成员初始化),将列表类命名为 List 而不是操作。实际上封装您的数据。了解 3/5/0 规则。了解迭代器。 C++ 中正确的链表是大量 原则和模式的结晶。

    === 可选 ===

    #include <iostream>
    #include <memory>
    
    /*
     * List Declaration
     */
    class List {
     public:
      List() = default;         // Default constructor
      List(const List& other);  // Copy ctor
      List(List&& other);       // Move ctor
      ~List();                  // Destructor
    
      // Iterators are an invaluable design pattern; they allow your class to
      // interact with the Standard Library
      class iterator;
      iterator begin();
      iterator end();
    
      // Naming changed to better match Standard Library behavior
      void push_front(int val);
      void push_back(int val);
      iterator insert(iterator pos, int val);
      iterator find(int val);
    
      void clear();
    
      List& operator=(List rhs);
      friend void swap(List& lhs, List& rhs);
    
     private:
      struct Node {
        int data;
        Node* next = nullptr;
    
        Node(int val) : data(val) {}
      };
    
      Node* m_head = nullptr;
      Node* m_tail = nullptr;
    
      // Helper functions
      void make_first_node(int val);
    };
    
    /*
     * List Iterator Declaration
     */
    class List::iterator {
     public:
      iterator(Node* pos);
      int& operator*();
      iterator& operator++();
      bool operator==(iterator other);
      bool operator!=(iterator other);
    
     private:
      Node* m_pos;
    };
    
    /*
     * List Implementation
     */
    List::List(const List& other) {
      make_first_node((other.m_head)->data);
    
      Node* walker = (other.m_head)->next;
      while (walker) {
        push_back(walker->data);
      }
    }
    
    List::List(List&& other) : List() { swap(*this, other); }
    
    List::~List() { clear(); }
    
    typename List::iterator List::begin() { return iterator(m_head); }
    
    typename List::iterator List::end() { return iterator(nullptr); }
    
    void List::push_front(int val) {
      if (!m_head) {
        make_first_node(val);
        return;
      }
    
      Node* tmp = new Node(val);
      tmp->next = m_head;
      m_head = tmp;
    }
    
    void List::push_back(int val) {
      if (!m_head) {
        make_first_node(val);
        return;
      }
    
      m_tail->next = new Node(val);
      m_tail = m_tail->next;
    }
    
    typename List::iterator List::insert(iterator pos, int val) {
      Node* walker = m_head;
      while (walker->next->data != *pos) {
        walker = walker->next;
      }
    
      Node* marker = walker->next;
      walker->next = new Node(val);
      walker->next->next = marker;
    
      return iterator(walker->next);
    }
    
    typename List::iterator List::find(int val) {
      Node* walker = m_head;
      while (walker) {
        if (walker->data == val) {
          return iterator(walker);
        } else {
          walker = walker->next;
        }
      }
    
      return iterator(walker);
    }
    
    void List::clear() {
      Node* tmp = m_head;
      while (tmp) {
        m_head = m_head->next;
        delete tmp;
        tmp = m_head;
      }
      m_tail = nullptr;
    }
    
    List& List::operator=(List rhs) {
      swap(*this, rhs);
    
      return *this;
    }
    
    void List::make_first_node(int val) {
      m_head = new Node(val);
      m_tail = m_head;
    }
    
    // List Friend
    void swap(List& lhs, List& rhs) {
      using std::swap;
    
      swap(lhs.m_head, rhs.m_head);
      swap(lhs.m_tail, rhs.m_tail);
    }
    
    /*
     * List Iterator Implementation
     */
    List::iterator::iterator(List::Node* pos) : m_pos(pos) {}
    
    int& List::iterator::operator*() { return m_pos->data; }
    
    typename List::iterator& List::iterator::operator++() {
      m_pos = m_pos->next;
    
      return *this;
    }
    
    bool List::iterator::operator==(typename List::iterator other) {
      return m_pos == other.m_pos;
    }
    
    bool List::iterator::operator!=(typename List::iterator other) {
      return !(*this == other);
    }
    
    int main() {
      List list;
      list.push_front(4);
      list.push_front(3);
      // My insert() behaves like the Standard Library's, meaning before & not after
      list.insert(list.find(4), 5);
      list.push_back(1);
    
      // The iterator implemented provides *just* enough functionality to allow
      // the class to be used in a range-based for loop
      for (auto i : list) {
        std::cout << i << ' ';
      }
      std::cout << '\n';
    }
    

    【讨论】:

      【解决方案3】:

      我认为逻辑没有任何问题,我认为您的代码应该通过删除公共“Node *ptr;”来修复变量定义。换句话说,您可能只需要在所有函数中本地添加此变量。

      如果您在 Node 构造函数中初始化 node->next =null ,那将是一个更好的做法。 这是您讨论过的修改的代码。

      #include<iostream>
      using namespace std;
      
      class Node
      {
         public:
         int data;
         Node *next;
         Node()
         {
           data=INT_MAX;
           next= NULL;
         }
         Node(int d)
         {
           data=d;
           next= NULL;
         }
      };
          class operations
          {
              public:
              Node *head;
              operations() {
                  head =NULL;
              }
              void insertfirst(int d)
              {
                  Node *newnode = new Node(d);
              
              if(head==NULL)
              {
                  head=newnode;
              }
              else
              {
                  newnode->next=head;
                  head=newnode;
              }
              
          }
          void display()
          {
              
              Node *ptr;
              ptr=head;
              
              while(ptr)
              {
                  cout<<ptr->data<<" ";
                  ptr=ptr->next;
              }
              
          }
          void insertafter(int key, int d)
          {
              Node *ptr;
              ptr=head;
              while(ptr->data!=key)
              {
                  if(ptr->next==NULL)
                  {
                      cout<<"Key not found";
                      return;
                  }
                  ptr=ptr->next;
              }
              Node *newnode=new Node(d);
              newnode->next=ptr->next;
              ptr->next=newnode;
          }
          void insertlast(int d)
          {
              Node *ptr;
              ptr=head;
              Node *newnode = new Node(d);
              while(ptr->next!=NULL)
              {
                  ptr=ptr->next;
              }
              ptr->next=newnode;
          }
      };
      int main() {
          operations o;
          o.insertfirst(4);
          o.insertfirst(3);
          o.insertafter(3,5);
          o.insertlast(1);
          o.display();
          return 0;
      }
      

      【讨论】:

      • 在节点创建后总是初始化newnode-&gt;next = nullptr;——这样会省很多事...
      • @David C. Rankin,同意,但如前所述,如果这个过程在 Node 类的构造函数中完成会更好。
      • 你做的不对。至少,使用 ctor 的初始化部分,最好是默认成员初始化。
      • @Ashkanxy 谢谢。你的回答很有帮助。
      • 我看不出逻辑有什么问题”——真的吗?因为我看到了一些。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-25
      • 2019-12-19
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      相关资源
      最近更新 更多