单链表的算法设计方法
一、以查找为基础的算法设计
1、按照条件进行节点查找
2、进行插入或者删除操作
例一:找到一个单链表除表头外,其内部的最大值并将它删除(假设单链表内的最大值唯一)
分析:这个例子中我们要做的有两件事,一件事是找到单链表内的最大值,另一件是删除最大值。
那么接下来我们分别去考虑如何实现这两件事情,再将它们合并起来,就能解决问题。
首先是删除,我们知道,要在链表内删除一个数据,需要知道这个数据的前一个链节。
其次是找最大值,我们知道,找最大值,最简单的方法是比较,所以我们需要比较链节本身,也就是我们最后会知道,这个数据所在的链节。
综上所述,我们可以知道我们最后知道的是最大值所在的链结,以及最大值所在链结的前一个链节。
那么实现方法可以是这样:
1、设定两个指针p、q,去遍历整个链表
2、还要设置两指针,(pre、max)在p、q遍历整个链表的时侯去做比较
3、在结束后,让pre=max->next,删除最大值
void delete_max(struct chain*head){
struct chain *p=head,*q=head->next,*pre=head,*max=head->next;
while(q!=NULL){ //一般来说单链表的尾是指向空的
if(max->data<q->data){
max=q;
pre=p;
} //完成比较
p=p->next;
q=q->next; //p、q去遍历这个链表
}
pre->next=max->next; //将最大值从链表里面剔除
free(max); //释放掉最大值的空间
}
差不多这样就能完成找到最大值,并删除最大值的方法。它的时间复杂度是O(n)。
例二:有一个带头节点的单链表L(至少有一个数据节点),设计一个算法使其元素递增有序排列
分析:当我们要将一个单链表进行排序的时候,我们要做的事有两件,一是比较,二是排序,也就是把数据放到,数据该在的地方。
那么我们依旧将两部分拆开来实现,当然,比较要在排序之前。
比较部分我们这样来想,我们把链表拆成两部分,第一部分是它的头链节,第二部分是剩余链节,我们比较头链节和剩余链节的第一节,如果头链节比剩余链节的第一节小,那么就将剩余链节的第一节放在头链节之后,形成新的两部分,然后再将剩余链节的新的第一节与,新的头链节的那一部分里的所有数据比较,把它插入到正确的位置上就可以了。
void sort(LinkList *&L){
LinkList*p,*pre,*q;
p=L->next;
L->next=NULL; //将链表拆成两部分
while(p!=NULL){
q=p->next;
pre=L; //从表头开始找
while(pre->next!=NULL&&pre->next->data<p->data){
pre=pre->next; //查找插入节点的前趋节点
}
p->next=pre->next;
pre->next=p;
p=q; //完成插入,直到整个链表排序完成。
}
}
二、以建表算法为基础的算法设计
1、单链表有尾插法和头插法两种建表算法
2、很多算法是以这两个建表算法为基础进行设计的
例一:将一个链表逆序。
分析:这个实际上,我们可以用排序里的拆开的想法,其实就是头插法和尾插法,我们把链表的头链节拿出来作为一部分,将剩余链节,一节一节的插在这一部分的头部,一直这样重复下去,直到插完,即可将整个链表逆序。
struct chain* turn(struct chain*head){
struct chain*p=head->next,*q=head->next,*pre=head;
head->next=NULL; //将头链节取下来作为一部分
while(q!=NULL){
q=q->next;
p->next=pre;
pre=p;
p=q;
} //不断地把剩余部分的第一个链节取下来头插到头链节的那一部分
return pre; //返回新的链表的头指针
}
跟据以上的程序,即可完成一个链表的逆序。
总结
单链表的算法设计里的方法大致可以先划出这两类:
1、链表你要知道是可以拆的,拆了之后再重新组合(有点像原子的重组,组成新的物质,还要知道,不止可以拆成两部分,根据需要可以拆卸成多组)。
2、拆了之后重组有两种方式,一种是头插,一种是尾插,如果你要逆序就头插,顺序就尾插。
3、单链表中,你想处理某个数据时,就一定需要操作他的前置数据,所以要注意记录前置数据。
4、如果你的链表被拆成了好多部分,记得拿指针去记录这几个部分,然后几个部分的交流,需要新的指针去交流,就像是信使一样,以后干脆叫信使指针(自己取地名字,不是专有名词)好了。
总而言之,单链表给我的感觉目前像是织毛衣,链表本身是毛线,指针是毛衣针。