一、练习题
01题
在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
【解法1】用p从头至尾扫描单链表,pre指向*p结点的前驱。若p所指结点的值为x,则删除,并让p移向下一个结点,否则让pre、p指针同步后移一个结点。
void Del_X_1(Linklist &L,ElemType x){
LNode *p=L->next,*pre=L,*q;//置p和pre的初始值
while(p!=NULL){
if(p->data==x){
q=p; //q指向被删结点
p=p->next;
pre->next=p; //将*q结点从链表中断开;
free(q); //释放*q结点的空间
}
else{ //否则,pre和p同步后移
pre=p;
p=p->next;
}//else
}//while
}
本算法是在无序单链表中删除满足某种条件的所有结点,条件是结点的值为x。条件是可以任意指定的,只要修改if条件即可。比如,要求删除介于mink和maxk之间的所有结点,只需将if语句修改为if(p->data>mink&&p->data->maxk)。
【解法2】采用尾插法建立单链表。用p指针扫描L的所有结点,当其值不为x时,将其链接到L之后,否则将其释放。
void Del_X_2(Linklist &L,ElemType x){
LNode *p=L->next,*r=L,*q; //r指向尾结点,其初值为头结点
while(p!=NULL){
if(p->data!=x){ //*p结点值不为x时将其链接到L尾部
r->next=p;
r=p;
p=p->next; //继续扫描
}
else{ //*p结点值为x时将其释放
q=p;
p=p->next; //继续扫描
free(q); //释放空间
}
}//while
r->next=NULL; //插入结束后置尾结点指针为NULL
}
上述两个算法扫描一遍链表,时间复杂度为O(n),空间复杂度为O(1)。
二、统考真题
【2019年统考真题】
设线性表采用带头结点的单链表保存,链表中的结点定义如下:
typedef struct node
{ int data;
struct node*next;
}NODE;
请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你所设计的算法的时间复杂度。
【解答】
1)算法的基本设计思想
先观察和,发现L'是由L摘取第一个元素元素,再摘取倒数第一个元素......依次合并而成的。为方便链表后半段取元素,需要将L后半段原地逆置[题目要求空间复杂度为O(1),不能借助栈],否则每取最后一个结点都需要遍历一次链表。a.先找出链表L的中间结点,为此设置两个指针p和q,指针p每次走一步,指针q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;b.然后将L的后半段结点原地逆置;c.从单链表前后两段中依次各取一个结点,按要求重排。
2)算法实现
void change_list(NODE*h){
NODE *p,*q,*r,*s;
p=q=h;
while(q->next!=NULL){ //寻找中间结点
p=p->next; //p走一步
q=q->next;
if(q->next!=NULL) q=q->next; //q走两步
}
q=p->next; //p所指结点为中间结点,q为后半段链表的首结点
p->next=NULL;
while(q!=NULL){ //将链表后半段逆置
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
s=h->next; //s指向前半段的第一个数据结点,即插入点
q=p->next; //q指向后半段的第一个数据结点
p->next=NULL;
while(q!=NULL){ //将链表后半段的结点插入到指定位置
r=q->next; //r指向后半段的下一个结点
q->next=s-next; //将q所指结点插入到s所指结点之后
s-next=q;
s=q->next; //s指向前半段的下一个插入点
q=r;
}
}
3)第一步找中间结点的时间复杂度为O(n),第二步逆置的时间复杂度为O(n),第三步合并链表的时间复杂度为O(n),所以算法的时间复杂度为O(n)。