【问题标题】:Sorting linked list while inserting a node插入节点时对链表进行排序
【发布时间】:2016-07-05 06:39:49
【问题描述】:

我正在尝试创建已排序的链表 = 在创建时对其进行排序。想法很简单,插入一个节点 - 并检查上一个是否更小,如果是,则检查上一个的上一个,依此类推,直到找到它的位置。我已经创建了这段代码。

struct Node{
    Node *prev;
    Node *next;
    int value;
};

struct List{
    Node *head = nullptr;
    Node *tail = nullptr;
};

在这里我创建了一个节点和一个列表的“持有者” = 引用列表的第一项和最后一项。

void insertNode(Node *&head,Node *&tail, int value ){
    Node *tmp = new Node;
    tmp -> prev = nullptr;
    tmp -> next = nullptr;
    tmp -> value = value;
    head = tmp;
    tail = tmp;
}

此函数检查列表是否为空,如果是,则将节点插入头尾(例如头=尾=列表中只有一个节点);

困扰我的是插入节点的功能

void insertIt(Node *&head , Node *&tail , int value){
    if( head == nullptr){
        insertNode(head,tail,value);
    }
    else{
        Node *tmp = new Node;
        tmp -> value = value;

        if( value < tail -> value){    
            while(value < tail -> prev -> value){                
                tail = tail -> prev;
                if( tail -> prev == nullptr){                    
                    tmp -> next = head;
                    tmp -> prev = nullptr;
                    head -> prev = tmp;
                    head = tmp;
                    return;
                }
            }
            tail -> prev -> next = tmp;
            tmp -> prev =  tail -> prev;
            tmp -> next = tail;
            tail -> prev = tmp;
        }else{    
            tmp -> next = nullptr;
            tmp ->prev = tail;
            tail -> next = tmp;
            tail = tmp;
        }
    }
}

如果列表为空,则调用insertNode(),如果节点的值小于前一个节点的值,则爬取列表以找到它的位置。

只有当插入的第一个节点也是最小的节点时,这段代码才有效。例如

insertIt(list.head , list.tail , -1);
insertIt(list.head , list.tail , 0);
insertIt(list.head , list.tail , 7);
insertIt(list.head , list.tail , 1);
insertIt(list.head , list.tail , 2);
insertIt(list head , list.tail , 2);

有效,如果我打印列表,它的排序很好。但是

insertIt(list.head , list.tail , -2);
insertIt(list.head , list.tail , -1);
insertIt(list.head , list.tail , 7);
insertIt(list.head , list.tail , 1);
insertIt(list.head , list.tail , 2);
insertIt(list.head , list.tail , 2);

第一个节点不是最小的节点,它会使程序崩溃。我以为是我将一个值与 nullptr 进行比较,所以我添加了您可以在 insertIt() 函数中看到的一段代码,那就是

if( tail -> prev == nullptr){
    tmp -> next = head;
    tmp -> prev = nullptr;
    head -> prev = tmp;
    head = tmp;
    return;
}

这会检查节点是否为头,并与新节点交换头,使新节点成为新头。

为什么会导致代码崩溃?我没能找到一个合理的答案。另外,我该如何改进我的“算法”以使其更有效?

【问题讨论】:

  • 首先,您应该在调试器中运行以捕捉正在运行的崩溃并查看它在哪里以及所有相关变量的值是什么。如果没有帮助,则使用调试器逐行逐行执行代码,以查看它的作用以及所有变量如何变化。
  • "如果列表为空,则调用 insertNode(),如果节点的值小于前一个节点的值.." ??但是列表是空的..
  • 我正在使用代码块,并且调试器指出,在我添加的代码行(我写的最后一个代码),我对 c/c++ 世界有点陌生,没有使用过那么多调试器,我猜代码块调试器不是最好的。你能推荐一些好的吗?
  • 我可以看到的是,在insertIt 中,您在寻找插入新节点的正确位置时重置了tail,从而破坏了指向链表中最后一个节点的指针。正确的方法是使用您初始化到尾部的另一个指针并将其用于搜索。无论如何,您使用 tail-&gt;prev-&gt;value 取消引用 tail-&gt;prev 而不检查 tail-&gt;prev != nullptr
  • 当您发布代码时,请尝试确保您的缩进是一致且可读的,并且您没有过多的空格。另请注意:C != C++ 和 "c/c++" 是 C 和/或 C++ 的简写,尤其是当 one 仅表示其中之一时。

标签: c++ algorithm sorting linked-list


【解决方案1】:

你想做两件事:在列表中找到新节点所属的位置,并在某个位置插入新节点。所以,写两个函数,一个来做每个任务。然后您可以在集成之前分别测试和调试它们。这将更加直接。进一步推荐:在实现功能之前为每个功能编写单元测试。

/** Find node with largest value less than given 
    Assumes sorted list exist.  If empty, throws exception
*/
Node & FindLessThan( int value );

/** Inset new node after given with value */
InsertAfter( Node& n, int value );

如果列表为空,插入第一个节点的函数也会很方便,

/** Insert first node with value
    @return true if list empty */
bool InsertFirstNode( int value );

关键是你应该隐藏所有可以测试的函数中的指针,这样你就可以编写一个第一次工作的可理解的主线:

if( ! InsertFirstNode( value ) )
   InsertAfter( FindLessThan( value ), value );

由于您使用的是 C++,因此请将您的列表设为类和函数成员。

实现细节:您必须担心特殊情况:新值位于 head 之前或 tail 之后。所以我建议使用枚举来处理这些。

/** Special cases for placing a new node */
enum class eFind
{
    list_empty,         // the list was empty
    before_first,       // the new node goes before the first node in list
    before_node,        // the new node goes before the specified node
    after_last,         // the new node goes after the last node in the list
}; 
/** Find node with smallest value greater than given

    @param[out] place eFind enumeration, one of list_empty,before_first,before_node,after_last
    @param[in] value being inserted

    @return n node before which value should be placed

    Assumes sorted list exist.
*/
Node * FindSmallestGreaterThan( eFind & place, int value )

事实证明,执行 InsertBefore 比 InsertAfter 稍微容易一些(代码更少)。您可以在cpp.sh/4xitpgithub gist 看到运行的代码

【讨论】:

  • 感谢您的建议,我会尝试使用单独的函数重新创建它。
  • 如果新值小于列表中的所有值,Node &amp; FindLessThan( int value ) 应该返回什么引用?
  • 枚举值'before_first'。你看过 cpp.sh/4xitp 的运行代码吗
【解决方案2】:

当遍历列表以找到插入新节点的位置时,您可以:

  tail = tail -> prev;

但是tail变量是通过引用传递的,也就是说你修改了List对象的tail成员,从而破坏了它的一致性。

使用另一个名为 currentposition 的临时变量沿列表遍历,并且不要修改 tail,除非您在列表末尾添加新节点。

编辑示例方法

struct Node {
    Node(int val);

    Node *prev;
    Node *next;
    int value;
};

struct List{
    List() : head(nullptr), tail(nullptr) {}
    void insert(int value);

    Node *head;
    Node *tail;
};

Node::Node(int val) :
    value(val), next(nullptr), prev(nullptr)
{
}

void List::insert(int value) {
    Node *tmp = new Node(value);

    if(head == nullptr) {
        head = tmp;
        tail = tmp;
        return;
    }

    Node *pos;  // find the node greater or equal to 'value'
    for(pos = head; pos && pos->value < value; pos = pos->next)
        ;

    if(pos) {    // appropriate pos found - insert before
        tmp->next = pos;
        tmp->prev = pos->prev;
        tmp->next->prev = tmp;
        if(tmp->prev)       // there is some predecessor
            tmp->prev->next = tmp;
        else
            head = tmp;     // making a new first node
    } else {     // pos not found - append at the end
        tmp->next = nullptr;
        tmp->prev = tail;
        tail->next = tmp;
        tail = tmp;
    }
}

【讨论】:

    【解决方案3】:

    这可能是您正在寻找的代码;-) 您可以在 VS2013 中按原样运行它。它将您的插入功能简化为几个 if 语句。这可以通过使用头部和尾部的终端元素来进一步简化。

    我希望这会有所帮助:-)

    struct Node
    {
        int value; Node *prev, *next;
    };
    
    struct DoublyLinkedSortedList
    {
        Node *head = nullptr, *tail = nullptr;
    
        void insert(int value)
        {
            // Find first node bigger then the new element, or get to the end of the list
            Node* node = head;
            while (node && node->value <= value) { node = node->next; }
    
            // Once found, insert your new element before the currently pointed node, or at the end of the list
            node = new Node{ value, node?node->prev:tail, node };
            if (node->prev) node->prev->next = node; else head = node;
            if (node->next) node->next->prev = node; else tail = node;
        }
    };
    
    #include <climits>
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
        cout << "This is a DoublyLinkedList test." << endl << endl;
    
        // test the list
        DoublyLinkedSortedList list;
        list.insert(234);
        list.insert(INT_MIN);
        list.insert(17);
        list.insert(1);
        list.insert(INT_MAX);
        list.insert(-34);
        list.insert(3);
        list.insert(INT_MAX);
        list.insert(INT_MIN);
        list.insert(9);
        list.insert(7);
    
        // print nodes in order;
        cout << "This are the contents of the linked list front to back" << endl << endl;
        for (Node* curr = list.head; curr != nullptr; curr = curr->next) { cout << curr->value << "; "; }
        cout << endl << endl << "This are the contents of the linked list back to front" << endl << endl;
        for (Node* curr = list.tail; curr != nullptr; curr = curr->prev) { cout << curr->value << "; "; }
        cout << endl << endl;
    
        system("pause");
    }
    

    【讨论】:

      【解决方案4】:

      问题在于while 循环头中的检查value &lt; tail-&gt;prev-&gt;value。这不会检查tail-&gt;prev != nullptr 是否为真。对于head == tailvalue &lt; head-&gt;value 的情况,这是一个问题。如果head != tail,您的代码确实可以工作,因为第一次评估value &lt; tail-&gt;prev-&gt;valuetail-&gt;prev != nullptr 为真,而head-&gt;next == tail 的情况将被循环体中的代码捕获。 正确的检查是tail-&gt;prev != nullptr &amp;&amp; value &lt; tail-&gt;prev-&gt;value。这首先检查tail-&gt;prev 是否可以解除引用。

      那么你可以在完成while 循环后以tail-&gt;prev == nullptr 结束(由于新的条件)。检查可以移出循环,导致以下代码:

      while (tail->prev != nullptr && value < tail->prev->value) {
          tail = tail->prev;
      }
      if (tail->prev == nullptr) {
          // Prepend node to the list
          return;
      }
      // Insert node in front of tail
      

      编辑:您仍然可以在循环中检查条件tail-&gt;prev == nullptr;循环之后的检查只会对捕获head == tail &amp;&amp; value &lt; head-&gt;value 的情况有用。不在循环中进行检查具有更短且(在我看来)模式可读的代码的好处。

      【讨论】:

        【解决方案5】:

        1.您不能在结构中初始化成员:

        struct List
        {
            Node *head;
            Node *tail;
        };
        

        2.(a) 函数insertItinsertNode的原型是错误的。你通过引用传递headtail。应该如下:

        void insertIt(Node * head ,Node * tail ,int value)

        void insertNode(Node * head,Node * tail,int value)

        2.(b)else 部分创建节点时,应将新节点的nextprev 指针设置为NULL

        tmp->prev=NULL;
        tmp->next=NULL; 
        

        2.(c) 当您通过引用传递tail 时,您在tail 上的while 循环中所做的任何更改都会反映在程序中。因此使用@987654335 类型的临时指针@。

        3.你使用的设计也不好。因此我建议你改变它。这是我的链表实现:

        main()
        {
            struct List Q;
            Initialize_list(&Q);
            Insert_it(&Q,12);
        }
        void Initialize_list(struct List *L)
        {
            L->head=NULL;
            L->tail=NULL;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-01
          相关资源
          最近更新 更多