《2014年 数据结构联考复习指导》第二章,第二大题,第19小题:
题目:设带有一个头结点的循环单链表,其结点均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。
我这里主要是给出代码的解释,,和如何在编程环境下看到效果。(下面纯属个人理解,如有错误,恳请指正)
代码如下:
void Del_All(LinkList &L)
{
LNode *p,*pre,*minp,*minpre;
while(L->next!=L)
{
p=L->next;//p指向当前的结点
pre=L;//pre指向当前结点的前一个结点
minp=p;//minp为要删除的最小值的结点
minpre=pre;//minpre为要删除结点的前一个结点
while(p!=L)
{
if(p->data<minp->data)
{
minp=p;
minpre=pre;
}
pre=p;
p=p->next;
}
printf("%d ",minp->data);
minpre->next=minp->next;//从链表中删除结点
free(minp);//销毁结点
}
free(L);
}
1、我们学习数据结构时一般对于循环单链表,都是头指针指向尾结点的。但是在咱们上面的算法是采用头指针指向头结点的。即代码中的L指向链表的头结点。为什么代码会定义头指针指向头结点而不是尾结点呢?主要是因为,如果我们定义头指针指向尾结点,由于尾结点包含有数据,即假如当在某一次遍历中尾结点包含的数据是最小值,那么我们就会将尾结点删除,然后修改头指针指向新的尾结点,这样就无缘无故的增加的算法的复杂度,如果我们我们定义头指针指向头结点则不存在这样的问题,我们不需要处理头结点中的数据,因为我们一般认为头结点是没有数据的结点。
2、上述代码传递的是函数的引用,其实对于上述代码传递引用和非引用没有本质的区别,因为我们处理的是指针所指向的数据,而不是指针变量本身。
3、那我们会想如何在链表定义为头指针指向尾结点的工程中,利用上述的算法呢?我们或许会想,直接把L->next传递给上述函数不就可以了吗?事实并非如此,如果我们传递L->next 给上述函数,由于是引用类型,我们先举例链表为:5 2 10 4 8 1,我们第一次遍历找到最小值为1,那么我们要删除它,而这个点是外围(调用该函数的地方)L头指针指向的对象,那么就会销毁L所指向的这个对象,那么当我们再次调用while(L->next!=NULL),此时的外围的L为悬垂指针,而我们传入的引用&L->next就无法解析,即函数内部L是一个无效的指针,则会报错。对于这个算法,我们直接传递非引用类型就可以了,此时传递给为一个副本。这个过程也告诉了我们,如果我们传递一个引用类型,该类型为另外一个对象的子对象,就如上面的,我们传入&L->next(前面加上&只是表示引用,并非取地址),L->next为由L引申出来的子对象,如果我们在函数处理过程中将L销毁,那么L->next就是一个无定义的对象,那么后面的对其操作就会报错。
代码如下(头文包含的头文件代码都是严蔚敏老师的数据解构书中的代码,随处可以下载到):
//为了显示效果,我们程序并没有销毁头结点。如果销毁了,我们就不能通过链表的相关操作进行访问了,而只能得到指针的值了。
#include"c1.h"
typedef int ElemType;
#include"c2-2.h"
#include"bo2-4.hpp"
void Del_All(LinkList &L2)
{
LNode *L=L2->next;
LNode *p,*pre,*minp,*minpre;
while(L->next!=L)
{
p=L->next;
pre=L;
minp=p;
minpre=pre;
while(p!=L)
{
if(p->data<minp->data)
{
minp=p;
minpre=pre;
}
pre=p;
p=p->next;
}
printf("%d ",minp->data);
minpre->next=minp->next;
free(minp);
}
//free(L);
L2=L;
}
void visit(ElemType c)
{
printf("%d ",c);
}
void main()
{
LinkList L;
Status s;
s=InitList_CL(L); // 初始化单循环链表L
const int NUM=6;
const int val[NUM]={5,2,10,4,8,1};
for(int i=0;i<NUM;i++)
{
ListInsert_CL(L,i+1,val[i]);
}
cout<<"链表如下:"<<endl;
ListTraverse_CL(L,visit);
cout<<"链表的长度:"<< ListLength_CL(L)<<endl;
cout<<"每次从链表中删除最小的,并将其逐个打印出来: "<<endl;
Del_All(L);
s=ListEmpty_CL(L);
cout<<endl;
cout<<"删除后链表的长度: "<< ListLength_CL(L)<<endl;
}
执行效果: