【问题标题】:How to delete all nodes of same value from linklist/Queue如何从链接列表/队列中删除所有相同值的节点
【发布时间】:2013-05-07 03:10:42
【问题描述】:

我一直在努力解决这个问题,但没有成功我有如下数据结构(这实际上非常复杂,我只是为了讨论而简化了):

typedef struct node{
 struct node* next;
  void* arg;
}node_t;

typedef struct queue{
node_t* head;
node_t* tail;
}queue_t;

addQ(queue_t*ptr , int data)
{
    queue_t* q = ptr;
    node_t * n = malloc(sizeof(*n));

    n->arg = data;
    n->next = NULL;

    if(NULL == q->head){
       q->head = q->tail = n;
       return ;
    }
    q->tail->next = n;
    q->tail = q->tail->next;
}

现在我想删除相同值的节点(我尝试了几种方法但没有成功),请考虑这个序列以供参考:

addQ(q, 12);
addQ(q, 12);
addQ(q,  4);
addQ(q, 12);
addQ(q, 12);
addQ(q, 14);
addQ(q, 12);
addQ(q, 12);

我想删除所有值为 12 的节点。

【问题讨论】:

  • 展示您尝试过的一种或两种方式总是有帮助的。
  • 只是一个简单的查询它是一个队列还是一个通用链表?如果它真的是一个队列,你甚至不应该尝试删除你选择的任何节点,dequeue 是你必须使用的选项 i假设,它是一个链表吗?
  • @duke,当然我也可以发布我的版本,但是它还没有工作。
  • @udy ,它是一个队列,我想从中删除所有具有相同值的节点。
  • 如何从头到尾迭代,杀死与您的查询匹配的条目?因为您的数据结构本质上是一个链表,所以这应该不是什么大问题。

标签: c algorithm linked-list queue


【解决方案1】:

这个解决方案对双指针有点麻烦,但我仍然喜欢它,因为它不需要特殊情况下正在检查哪个节点(第一个与其余节点)。我试图放入足够多的 cmets 来描述正在发生的事情,但即使是我,第一眼也很难理解。

伪代码..

Queue * q;
VALUE = 12;

// double pointer so we can treat the queue head and subsequent nodes the same.
//   because they are both pointers to Node.  
// Otherwise you'd have to have code that says if the one you're removing is the 
// first element of the queue, adjust q->head, otherwise adjust node->next.  
// This lets you not special case the deletion.
Node ** node_ptr = &(q->head)

while (*node_ptr != null) {
    if ((**node_ptr).arg == VALUE) {
        // store off the matching node to be freed because otherwise we'd orphan
        // it when we move the thing pointing to it and we'd never be able to free it
        Node * matched_node = *node_ptr;

        // when we find a match, don't move where node_ptr points, just change the value it
        // points to to skip the matched node and point to the one after it (or null)
        *node_ptr = matched_node->next; 
        free(matched_node);
    } else {
        // otherwise, nothing was deleted, so skip over that node to the next one.
        // remember, **node_ptr is a double dereference, so we're at the node
        // now, so then we grab the address of the non-matching node's next value so it can be
        // potentially changed in the next iteration
        node_ptr = &((**node_ptr).next);
    }
}

【讨论】:

  • +1 这比最初看起来要干净得多。它本质上是扫描列表,保留指针的地址,该指针可能引用node_ptr 中的潜在节点匹配。在匹配时,该节点被取消链接,并且通过将其“下一个”存储在首先引用它的指针中来填充它留下的空洞。唯一的潜在问题是看到是一个尾部修复,但这个想法应该是明确的,并且是非常内存效率,不需要任何节点重复。
  • 我还在清理它。确实,尽管我已经这样做了很长时间,但这么多指针的东西让我转过身来循环。
  • 这就是我的想法,感谢您的详细解释:)
【解决方案2】:

这是一个不使用双指针的内联解决方案。由于要调整的指针从队列结构变为节点结构,因此它必须区别对待第一个元素和后续元素。

此外,对于后续节点,您必须跟踪尾随节点,因为您必须在删除匹配节点时进行调整。

Queue * q;
VALUE = 12;


// handle the case where the first node matches.
// you have to adjust the q's head pointer
// delete from the head and set a new head node until a non-matching head is found
while (q->head != NULL && q->head->arg == VALUE) {
  Node * matching_node = q->head;
  q->head = q->head->next;
  free(matching_node);
}

// if there is more than one node left, need to check the subsequent nodes
if (q->head != NULL && q->head->next != NULL) {

    Node * node_ptr = q->head->next;
    Node * prev_node_ptr = q->head;

    while (node_ptr != NULL) {
        if (node_ptr->arg == VALUE) {
            Node * matched_node = node_ptr; // don't orphan it before it's freed

            // You don't move the prev_node pointer since that doesn't change when a match
            //   is found.  Only the node_ptr, which skips to the next one. 
            node_ptr = node_ptr->next;
            free(matched_node);            
        } else {
            prev_node_ptr = node_ptr;
            node_ptr = node_ptr->next;
        }
    }
}

【讨论】:

  • 啊,再次感谢你。刚刚找到我搞砸的地方:)
  • 去掉勾号的答案有问题吗?
  • 刚试过,因为我的版本更像这样,看起来有bug,但是其他版本工作正常。
  • Hrmm.. 我没有看到任何问题,但这当然不是实际代码。是崩溃了还是只是没有删除东西或删除太多?
  • 它正在跳过前两个节点并删除它认为不应该执行的其他两个节点。我会在早上调试这个。再次感谢您的意见。
【解决方案3】:

假设你已经有一个函数来获取和删除队列中的下一个项目,我们称之为getQ(q),那么你甚至不需要知道队列的内部结构就可以实现你的目标,只需使用操作你已经有了,例如类似的东西(这不起作用,因为argvoid,但逻辑应该很清楚):

node_t *n;
queue_t *q2 = initialiseQ();
while (n = getQ(q)) {
    if (n->arg != 12) {
        addQ(q2,n);
    }
}
free(q);
q = q2;

【讨论】:

  • 是的,我有从头部移除的功能,可以添加到队列中,也可以出队,顺便说一句,你建议另一个队列吗?当您期望进行数十万次操作时,这不是很昂贵吗??
  • 移动指针并不是什么大问题。如果您要完全重新分配节点然后释放旧节点,那将非常糟糕。我的意思是,无论如何,你都在遍历所有节点的指针,所以让它四处移动指针并不是什么大不了的事。但它可以内联完成。
  • 我认为@xaxxon 的观点和Select Call 的观点都是正确的。 addQ() 的当前实现每次都分配一个节点,如果执行数十万次,这可能是一个问题。但是,addQ() 的替代实现将现有的node 作为参数并只是移动指针(如 xaxxon 建议的那样)将很容易编写(实际上,当前的addQ() 可以分配节点,然后只需使用新节点作为参数调用新的addQ_node())并且运行速度非常快。
  • 我刚刚添加了一个解决方案,其中节点被就地删除。目前还不清楚到底发生了什么,但我认为它朝着你所想的方向发展。
猜你喜欢
  • 2019-09-11
  • 1970-01-01
  • 2015-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-05
  • 1970-01-01
  • 2015-11-14
相关资源
最近更新 更多