题目
已知递增有序的单链表A、B (A、B中元素个数分别为m、n,且A、B都带有头结点)分别存储了一个集合,请设计算法,以求出两个集合A和B的差集A-B (仅由在A中出现而不在B中出现的元素所构成的集合)。将差集保存在单链表A中,并保持元素的递增有序性。
分析
只需从A中删去A与B中共有的元素即可。由于两个链表中的元素是递增有序的,因此可以这么做:设置两个指针p、q开始时分别指向A和B的开始结点。循环进行以下判断和操作:如果p所指结点的值小于q所指结点的值,则p后移一位:如果q所指结点的值小于p所指结点的值,则q后移一位:如果两者所指结点的值相同,则删除p所指结点。最后,p与q任一指针为NULL时算法结束。
其实这里我感觉比较难理解的就是为什么有pre了?由于要删除A链表和B链表中元素值的结点,在单链表的删除操作中就知道如果要删除一个结点,那么就要知道被删除结点的前一个结点,然后才能使得前一个结点与被删除结点的下一个结点连接起来形成一个完整的链表。而这里pre的作用就是保存被删除结点的前一个结点,但其实就是从开始结点(第一个结点)开始的,只是这里恰巧A表中的第二个结点是相等结点元素。
而在后面提供了一个方法就是用第三个链表C来存放差集A-B中的元素结点,但这样却违背了题目的要求。
代码
/*
* 题目:已知递增有序的单链表A、B (A、B中元素个数分别为m、n,
* 且A、B都带有头结点)分别存储了一个集合,请设计算法,
* 以求出两个集合A和B的差集A-B (仅由在A中出现而不在B中
* 出现的元素所构成的集合)。将差集保存在单链表A中,并保
* 持元素的递增有序性。
*/
#include <stdio.h>
#include <stdlib.h>
// 声明单链表的结构体
struct LNode {
int data;
struct LNode *next;
};
/* 使用尾插法建立单链表,即新添加的元素添加到链表的尾部 */
/* *&C指的是建立的单链表;a[]指的是批量添加的元素数组;n表示数组长度 */
void createListL(LNode *&C,int a[],int n) {
LNode *temp,*node;
// 创建一个带头节点的单链表
C=(LNode *)malloc(sizeof(LNode));
C->next=NULL;
temp=C;
// 循环遍历数组,批量为链表插入数据
for(int i=0; i<n; i++) { // 循环申请n个结点来接收a中的元素
node=(LNode *)malloc(sizeof(LNode)); // 创建新结点
node->data=a[i]; // 为新结点赋予数据
temp->next=node; // 将原尾结点的下一个结点指向新结点
temp=temp->next; // 指向终端结点,以便接收下一个结点
}
temp->next=NULL; // 所有元素已经填装完成,将终端结点置为NULL
}
/* 打印单链表 */
/* *list指的是要被打印输出的单链表 */
void printList(LNode *list) {
printf("\n");
LNode *temp=list->next;
while(temp!=NULL) { // 循环单链表
printf("%ld\t",temp->data); // 打印单链表中的data数据
temp=temp->next; // 遍历至下一个结点
}
printf("\n"); // 换行
}
/* 求递增链表A和递增链表B的差集A-B并保存在A链表中 */
/* *LA指的是链表A;*LB指的是链表B */
void difference(LNode *LA,LNode *LB) {
/* 为什么需要一个pre呢?由于要删除结点,而链表需要将被删除结点的前一个结点与后一个结点连接起来,如果没有这个pre,那么就不清楚被删除结点的前一个结点了,也就不能连接起来链表 */
LNode *la=LA->next,*lb=LB->next;// 将la指向LA的开始结点(即链表的第一个结点);将lb指向LB的开始结点
LNode *temp;// 临时指针变量,用来临时保存A链表中与B链表中相等的结点(也是要被删除的结点)
LNode *pre=LA;// 用来记录A链表的头结点
while(la!=NULL&&lb!=NULL) { // 循环A、B链表中的结点
if(la->data<lb->data) { // 当la所指结点的元素值小于lb所指结点的元素值时
pre=la;// 置为A表中的开始结点
la=la->next;// la指向下一个结点
} else if(la->data==lb->data) {// 当la所指结点的元素值等于lb所指结点的元素值时
pre->next=la->next; // 将开始结点的next指向要被删除结点的next
temp=la;// 临时保存被删除结点(也就是A表中与B表中元素值相等的结点 */
la=la->next;// la指向下一个结点
free(temp);// 是否结点资源
} else if(la->data>lb->data) {// 当la所指结点的元素值大于lb所指结点的元素值时
lb=lb->next;// lb指向下一个结点
}
}
}
int main() {
LNode *LA,*LB;
int a[]= {1,2,5};// 注意:在修改数组元素后,必须修改createListL()函数中的数组元素个数
int b[]= {2,4,5};// 注意:在修改数组元素后,必须修改createListL()函数中的数组元素个数
createListL(LA,a,3);// 创建A链表
createListL(LB,b,3);// 创建B链表
printList(LA);// 打印A链表
printList(LB);// 打印B链表
difference(LA,LB);// 求A-B
printList(LA);// 打印A-B的差集后的链表
return 0;
}
核心代码就是difference()方法体内的代码,其他代码都是为了辅助测试的。
运行效果如下:
下面展示下用第三个链表C来存放差集A-B的代码:
void difference(LNode *LA,LNode *LB,LNode *&LC){
LNode *la=LA->next,*lb=LB->next;
LNode *temp;
LC=(LNode *)malloc(sizeof(LNode));
LC->next=NULL;
temp=LC;
while(la!=NULL&&lb!=NULL){
if(la->data<lb->data){
temp->next=la;
temp=la;
la=la->next;
}else if(la->data==lb->data){
temp->next=la->next;
la=la->next;
lb=lb->next;
}else if(la->data>lb->data){
lb=lb->next;
}
}
}
最好画图来看代码比较便于理解,如果只看代码的话很难看出结果来。