一、初步实现:
以前在写单项链表重复值删除的时候,用的是先排序然后进行重复值剔除的方法。重复值剔除完毕之后,得到的元素也是有序的,然后顺利的写完了,没有出现任何bug。也就没有去尝试新的方法去做,代码如下:
# include<stdio.h>
# include<stdlib.h>
# include<time.h>
typedef struct Node{
int data;
struct Node* pNext;
}NODE,*PNODE;
PNODE Create(int n);//采用rand()函数生成10~14的随机数来建链
void Print(PNODE pHead);//遍历
void DeleteRepeatKey_Link(PNODE pHead);//有序删除
void SortOne_Link(PNODE pHead);//排序
int main (void)
{
int n;
PNODE pHead = NULL;
printf("请输入您要生成的随机数个数\n");//为观察出效果,n在20~50个为宜
scanf("%d",&n);
pHead = Create(n);
Print(pHead);
DeleteRepeatKey_Link_One(pHead);
Print(pHead);
return 0;
}
PNODE Create(int n)
{
int i;
PNODE pHead = NULL,pNew = NULL;
srand(time(NULL));
for(i = 0; i<n; i++)
{
pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew){
printf("内存分配失败!\n");
exit(EXIT_FAILURE);
}
pNew->data = rand()%5 + 10;
pNew->pNext = pHead;
pHead = pNew;
}
return pHead;
}
void Print(PNODE pHead)
{
PNODE p = pHead;
for(p;p;p=p->pNext)
printf("%-4d",p->data);
printf("\n");
}
void DeleteRepeatKey_Link(PNODE pHead)
{
PNODE p = pHead,q;
q = p->pNext;
Sort_Link(pHead);
if(pHead == NULL){
printf("链表为空,无需删除所谓重复项!\n");
exit(EXIT_FAILURE);
}
for(;q;q = p->pNext)
if(p->data == q->data){
p->pNext = q->pNext;
free(q);
}else
p = q;
}
void Sort_Link(PNODE pHead)
{
PNODE p = pHead,q;
for(; p; p=p->pNext)
for(q=p; q; q=q->pNext)
if(p->data > q->data){
int temp = p->data;
p->data = q->data;
q->data = temp;
}
}
二、新思路编写代码出现BUG
但是今天在重新写该程序的时候,由于没有提前排序。而是采用了pOne指向链表的头节点的指针,然后依次用后面的节点数据域与pOne指向的节点数据域来比较,当相等时,删除后面相等的那个节点。遍历一遍之后,pOne指向的节点数据域就是无重复值的,然后pOne后移,再次重复以上步骤。双层循环跑完之后就完成了剔除所有重复值的操作。但是依照下面的代码,却出现了一个让我苦恼了好一阵的Bug。代码如下:
void DeleteRepeatKey_Link(PNODE pHead)
{
PNODE pOne = pHead,pFind = NULL,qFind = NULL;
if(!pHead){
printf("链表为空!\n");
exit(EXIT_FAILURE);
}
for(pOne;pOne->pNext;pOne = pOne->pNext)//bug的产生之处
{
qFind = pOne;
pFind = qFind->pNext;
while(pFind){
if(pOne->data - pFind->data){
qFind = pFind;
pFind = pFind->pNext;
}
else{
qFind->pNext = pFind->pNext;
free(pFind);
pFind = qFind->pNext;
}
}
}
}
首先,pOne指向pHead,由于pFind=qFind->pNext=pOne->pNext,所以退出外层循环的条件是pFind不为空,也就是pOne->pNext不为空。(但是此时我就给自己把坑挖娃好了,并且完全没有怀疑地认为这毫无破绽,十分完美!实际上我因为太过自信而跳进了自己的坑)。
其次,内层循环退出的条件是pFind不为空(到这时我依旧没有发现我跳进了自己挖的坑中)。并运行了一遍程序“完全正确”(如图1所示),但是当我给朋友演示该代码的时候,BUG出现了(如图二所示):
图1:
图2:
然而当我把以上代码稍作修改之后,就消除了该Bug。代码如下:
void DeleteRepeatKey_Link(PNODE pHead)
{
PNODE pOne = pHead,pFind = NULL,qFind = NULL;
if(!pHead){
printf("链表为空!\n");
exit(EXIT_FAILURE);
}
for(pOne;pOne;pOne = pOne->pNext)//修改的唯一一处
{
qFind = pOne;
pFind = qFind->pNext;
while(pFind){
if(pOne->data-pFind->data){
qFind = pFind;
pFind = pFind->pNext;
}
else{
qFind->pNext = pFind->pNext;
free(pFind);
pFind = qFind->pNext;
}
}
}
}
运行结果:
BUG分析:
一直只是在内循环中判断内循环执行条件是否满足,并且认为外循环pOne->pNext不为空就是pFind不为空。BUG会不会表现出来的条件是:最后一次进入内循环时,剩余的数是否有重复值。
(1)、
第一次测试中,13,14,11一次剔除重复值之后,pOne指向数据域为12的节点,退出内循环,此时pOne并不在尾节点之上,pOne->pNext也不为空,外循环pOne后移使得pOne指向数据域为11的节点,此时pOne->pNext已经为空,直接就结束循环,最后一组数不用剔除重复值,因为最后一组已经是没有重复的了,不会出现进入内层循环之后pOne->pNext才为空的情况。
(2)、
而第二次测试中,11,13,12,14四组依次剔除重复值之后,最后一组4个元素:连续的4个10使得pOne最终跳出内循环时指向pFind的前一个节点上,即pFind为空时,pOne指向尾节点,跳出内循环,再回到外循环时BUG出现了:
由于pOne=pOne->pNext;执行之后,pOne就为空了,再对外循环条件判断就成了矛盾的了:pOne为空,则pOne没有pNext,而外循环条件为pOne->pNext != NULL;判断时,系统找不到pOne的pNext,所以就崩溃了。
三、总结:
修改之后的程序,虽然外循环只考虑了pOne不为空,要有数据域;没有考虑pFind是否为空,但是即使pFind为空进入外层循环,在进内循环之前也会被识别出来。外循环替内循环考虑太多,却导致了该BUG的产生,有越俎代庖之嫌,值得深思!