两个双向链表删除相同元素

本文介绍了一个使用C++实现的双向链表的基本操作,包括创建、打印链表及删除相同节点的功能。通过两个示例数组创建链表,并演示如何删除两链表中的公共元素,最终展示处理后的链表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:http://hi.baidu.com/beyond748/blog/item/cdeefb1ce3638a8d87d6b6c7.html/cmtid/6145d62fdd4a92361f3089e8


#include "stdafx.h"
//#include "list.h"
#include <iostream>
#include <vector>
using namespace std;

typedef struct node{
int data;
struct node *front, *next;
}node, *linkHead;
//记录删除的元素
int delCnt=0;
int delTmp[16];

void creatList(linkHead *head, int ary[], int n){
int c=0;
linkHead p;
// head;
linkHead h= (linkHead)malloc(sizeof(node));
h->next=NULL;
h->front=NULL;
h->data=n;
*head = h;


while(c<n){
p = (linkHead)malloc(sizeof(node));
h->next = p;
p->front= h;
p->data = ary[c++];
h = p;
p->next = NULL;
}
}

void printList(const linkHead head)
{
linkHead p=head->next;
while(p){
printf("%d\n", p->data);
p = p->next;
}
}

int delNode(linkHead *head, int val, int save)
{
linkHead pNode; 
linkHead p = (*head)->next;
while (p)
{
if (p->data==val) {
if (save==1) delTmp[delCnt++]=p->data;
if (p->next){
pNode = p->next;
p->front->next=p->next;
p->next->front=p->front;
free(p), p=NULL;
p = pNode;
}else{
p->front->next=NULL;
free(p), p=NULL;
}

}else
p = p->next;
}


return 1;
}

void delSame(linkHead *head1,linkHead *head2){
linkHead p = (*head1)->next;
while(p){
delNode(head2, p->data, 1);
p=p->next;
}

for(int i=0; i<delCnt; i++){
delNode(head1, delTmp[i], 0);
}
}

void main()
{
int aa[]={1,2,3,4,5,6};
int bb[]={3,4,5,6,7,8,9,10,11,4,2};
linkHead list1,list2;
creatList(&list1,aa, sizeof(aa)/sizeof(aa[0]));        
creatList(&list2,bb, sizeof(bb)/sizeof(bb[0]));
//delSame(&list2,&list1);
delSame(&list1,&list2);

printf("\n");
printList(list1);
printf("\n");
printList(list2);
}

<think>我们参考引用的内容,特别是引用[1]和引用[4]中关于双向循环链表删除节点的代码示例。 双向循环链表的特点是:每个节点都有前驱(prev)和后继(next)指针,链表头节点的前驱指向尾节点,尾节点的后继指向头节点,形成一个环。 删除中间节点(非头节点)的一般步骤: 1. 找到要删除的节点(假设为p)。 2. 将p的前驱节点的next指针指向p的后继节点。 3. 将p的后继节点的prev指针指向p的前驱节点。 4. 释放p节点的内存。 注意:如果链表是带头节点的(哨兵节点),那么头节点本身不存储有效数据,我们删除的是头节点之后的节点。 另外,在删除节点时,要确保链表不断开,特别是当删除的节点是最后一个有效节点时(此时链表只剩下头节点),需要调整头节点的指针(但头节点仍然存在,且指向自己)。 根据引用[1]的代码,它是在一个带头节点的双向循环链表删除指定数据的节点,并且考虑了删除最后一个节点的情况。 引用[4]的代码则更简洁,直接给出了删除指定节点的函数,它假设传入的节点就是要删除的节点,然后调整其前后节点的指针。 因此,我们可以总结出删除中间节点的步骤如下(假设链表带头节点,且要删除的节点不是头节点): 1. 定位到要删除的节点(通过遍历等方式)。 2. 执行删除操作: p->prev->next = p->next; p->next->prev = p->prev; 3. 释放该节点。 但是,如果链表只有头节点(即空链表),则不能删除。另外,如果要删除的节点是头节点,通常头节点是不能删除的(因为它是哨兵节点),所以这里我们只讨论删除中间的有效节点。 下面我们给出一个函数,用于删除双向循环链表中指定数据的节点(假设链表带头节点,且头节点不存储有效数据): 注意:我们假设链表中可能存在多个相同数据的节点,这里我们删除第一个遇到的节点。 代码实现(参考引用[1]并简化): 我们遍历链表(从头节点的下一个节点开始,直到回到头节点),找到第一个数据等于指定值的节点,然后删除它。 但是注意:在删除节点时,要特别注意边界情况,比如删除的是最后一个有效节点(此时删除后,链表为空,即只剩下头节点)。 然而,在双向循环链表中,删除操作对于中间节点和最后一个节点的操作其实是一样的,因为头节点的存在,所以不需要特殊处理(因为即使删除最后一个节点,头节点的prev和next仍然指向自己,但代码中需要保证删除链表依然循环)。 但是引用[1]的代码中,在遍历到最后一个节点时(p->next等于头节点)又单独判断了一次,这可能是为了处理某种边界情况?实际上,在双向循环链表中,删除任意节点的操作是统一的,不需要区分中间节点和尾节点。 因此,我们可以统一处理:只要找到要删除的节点,就执行以下操作(假设p是要删除的节点): p->prev->next = p->next; p->next->prev = p->prev; free(p); 但是,引用[1]的代码中,在删除节点后,将p指向了下一个节点(temp),然后继续循环。这是因为它的函数设计是删除所有等于deldata的节点?从代码逻辑看,它是在一个循环中,每删除一个节点后,将p移动到下一个节点,然后继续循环直到回到头节点。但是,如果删除的是最后一个节点,那么p->next就是头节点,此时循环条件(p->next != list)就会退出,所以最后一个节点在循环外单独处理。 实际上,我们可以更简洁地处理:遍历整个链表(包括头节点后的所有节点),遇到要删除的节点就删除,然后继续往后遍历。但是,在删除节点后,我们需要用一个临时变量保存下一个节点,然后释放当前节点,再让p指向临时变量(下一个节点),这样就不会因为释放而丢失下一个节点的位置。 因此,我们重新设计一个删除指定数据的节点的函数(删除第一个遇到的节点): 注意:我们只删除第一个遇到的节点,然后返回。 步骤: 1. 从链表的第一个有效节点(head->next)开始遍历,直到回到头节点(即遍历整个链表一圈)。 2. 如果当前节点的数据等于要删除的数据,则执行删除操作,并释放节点,然后返回。 3. 如果遍历完都没有找到,则返回未找到。 但是,如果链表为空(即只有头节点),则直接返回。 代码示例(C语言): ```c // 假设链表节点结构定义为: typedef struct ListNode { int data; struct ListNode *prev; struct ListNode *next; } ListNode; // 链表头节点(哨兵节点)的创建:头节点的prev和next都指向自己,data不存储有效数据。 // 删除第一个等于del_data的节点 int delete_node(ListNode *head, int del_data) { if (head == NULL || head->next == head) { // 链表为空(只有头节点) return 0; // 删除失败 } ListNode *p = head->next; // 从第一个有效节点开始 while (p != head) { // 遍历整个链表(循环一圈) if (p->data == del_data) { // 找到要删除的节点 // 调整前后节点的指针 p->prev->next = p->next; p->next->prev = p->prev; // 释放当前节点 free(p); return 1; // 删除成功 } p = p->next; } return 0; // 没有找到要删除的节点 } ``` 但是,如果我们要删除所有等于del_data的节点,则需要在循环中删除所有匹配的节点,并且注意在删除一个节点后,下一个节点的位置需要提前保存,因为当前节点释放后就不能再访问它的next了。 删除所有匹配节点的代码示例: ```c int delete_all_nodes(ListNode *head, int del_data) { if (head == NULL || head->next == head) { return 0; // 链表为空 } ListNode *p = head->next; ListNode *temp = NULL; // 临时保存下一个节点 while (p != head) { if (p->data == del_data) { // 先保存下一个节点 temp = p->next; // 删除节点p p->prev->next = p->next; p->next->prev = p->prev; free(p); p = temp; // 继续处理下一个节点 } else { p = p->next; } } return 1; // 删除完成(可能删除了0个或多个) } ``` 注意:在删除当前节点后,我们通过temp保存了下一个节点,然后p指向temp,继续循环。这样就不会因为释放当前节点而丢失下一个节点的位置。 另外,引用[2]中提到销毁链表的方法是循环调用尾删或头删,直到链表为空(只剩下头节点),最后释放头节点。但这里我们讨论的是删除中间节点。 总结:双向循环链表删除中间节点的关键操作就是调整待删除节点前后两个节点的指针,使其互相指向对方,然后释放待删除节点。 图示过程(假设有节点A、B、C,要删除B): 删除前:A->next = B, B->prev = A, B->next = C, C->prev = B 删除B: A->next = B->next; 即 A->next = C C->prev = B->prev; 即 C->prev = A 然后释放B。 在代码中,我们通过待删除节点B的prev和next指针,分别修改其前驱节点A和后继节点C的指针。 最后,根据用户要求,我们生成相关问题。</think>### 双向循环链表删除中间节点操作详解 在双向循环链表删除中间节点(非头尾节点)的核心操作是**调整相邻节点的指针关系**,确保链表连续性。以下是具体实现步骤和代码示例: #### 关键步骤 1. **定位目标节点** 通过遍历链表找到待删除节点(假设为 `p`) 2. **调整相邻节点指针** - 将 `p` 的前驱节点的 `next` 指向 `p` 的后继节点 - 将 `p` 的后继节点的 `prev` 指向 `p` 的前驱节点 即: ```c p->prev->next = p->next; // 前驱节点跳过p p->next->prev = p->prev; // 后继节点跳过p ``` 3. **释放内存** ```c free(p); // 释放节点内存 p = NULL; // 避免野指针(可选) ``` #### 完整代码实现(C语言) ```c // 节点结构定义(带头节点) typedef struct ListNode { int data; struct ListNode* prev; struct ListNode* next; } ListNode; // 删除指定数据的节点 void deleteNode(ListNode* head, int target) { if (head->next == head) return; // 空链表检查 ListNode* p = head->next; while (p != head) { // 遍历整个链表 if (p->data == target) { // 关键指针调整 p->prev->next = p->next; p->next->prev = p->prev; ListNode* temp = p->next; // 保存下一节点 free(p); // 释放当前节点 p = temp; // 继续遍历 } else { p = p->next; } } } ``` #### 执行过程图示 ``` 删除前: ... ↔ A ↔ [p] ↔ B ↔ ... 删除后: ┌─────────────┐ ... ↔ A ────────→ B ↔ ... ←─────────────┘ ``` ▸ 虚线表示被删除的指针关系 ▸ `A` 和 `B` 直接建立双向连接[^1][^4] #### 注意事项 1. **边界处理** - 删除头节点需特殊处理(非中间节点场景) - 空链表直接返回 2. **内存安全** - 释放节点后建议置空指针(防野指针) - 删除最后一个节点时自动更新头节点指针[^2] 3. **时间复杂度** - 定位节点:$O(n)$ - 删除操作:$O(1)$ > 关键优势:双向循环链表删除操作只需常数时间指针修改,无需遍历前驱节点[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值