本节题目来自王道单科37页。说不定哪天就放弃了在电脑上敲代码了,好费时啊啊啊啊。
1、设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点
终止条件:F(L,x)不做任何事情; 若L为空表
递归主体:F(L,x)删除*L结点;并递归下一层F(L->next,x) 若L->data==x
递归下一层F(L->next,x) 其他情况
void R_delete(LinkList &L,int x){ //设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点 LNode *s; if(L==NULL)return; if(L->data==x){ s=L; L=L->next; free(s); R_delete(L,x); } else{ R_delete(L->next,x); } }
2、在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一。
void del_x2(LinkList &L,int x){ LNode *pre=L,*p=L->next; LNode *q; while(p!=NULL){ if(p->data==x){ q=p; pre->next=p->next; p=p->next; free(q); } else{ pre=p; p=p->next; } } }
删除操作大总结:
①这里的删除操作是定义了一个新指针来指向要删除的结点,然后释放这个指针就相当于释放了它指向的结点。
p是要删除的结点,pre是删除结点的前驱结点,定义一个新指针q指向要删除的结点。具体是下面这样:
q=p;
pre->next=p->next;
p=p->next; //这里只是p要继续往下遍历。r如果不用遍历可以不用到这句。
free(q);
②其实还有一种删除的方法,可以不用定义新指针。下面这段代码可以代替上面几行。且更常用。
pre->next=p->next;
free(p);
p=pre->next; //遍历也不会丢失地址,因为pre->next刚好有记到。如果不用遍历可以不用到这句。
③如果遍历的时候不在乎所删除结点的前驱结点要和后面连接起来,而可以边遍历边释放的话,甚至可以没有pre指针。
直接用q指向要删除的结点,p继续往后遍历,然后释放掉q就ok了。
即 q=p; p=p->next; free(q); 即可。这句和上面第一种只是少了前驱结点和后面的连接,即pre->next=p->next;
下面这段代码用到的就是这种情况。
void del_x3(LinkList &L,int x){ LNode *p=L->next,*r=L,*q; while(p!=NULL){ if(p->data!=x){ r->next=p; r=p; //尾插法的典型代码。r->next=p;r=p; 其中r=p可以用r=r->next代替,都表示r向后移动 p=p->next; //继续遍历 } else{ q=p; //删除p所指结点,释放空间 p=p->next; free(q); } } r->next=NULL; }
3、设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值
分析:①反向第一个想到栈,②进而想到递归。③当然也可以头插法,④或者改变链表指针的方向,即下面第5题。
void R_print(LinkList L){ if(L->next!=NULL){ R_print(L->next); } printf("%d ",L->data); }
4、在单链表L中删除一个最小值结点(假设最小值结点是唯一的)
分析:因为只删除一个结点,要考虑前驱结点,所以我们考虑上面的删除操作中的第二个,即pre->next=p->next; free(p);
找最小值类似于顺序表先定义一个min=第一个,然后往后遍历逐步比较,只要后面有就更新min值。因为此处是删除最小值结点,我们也用结点类型来定义,即LNode *minp;又因为删除的时候要注意前驱结点,则再设一个LNode *minpre,始终在minp前面。
void Min_Delete(LinkList &L){ LNode *pre,*p; LNode *minpre,*minp; while(L->next!=NULL){ pre=L;minpre=pre; p=L->next;minp=p; while(p!=NULL){ if(p->data<minp->data){ minpre=pre; //更新minp,使其指向新找到的更小的结点 minp=p; } pre=p; p=p->next; //往后遍历 } printf("%d ",minp->data); minpre->next=minp->next; free(minp); } }
这道题和王道第9题相似,区别只在于前者只删除一个结点,后者删除所有结点。所以后者在外层加上一个while(L->next!=NULL)
void Min_Delete(LinkList &L){ LNode *pre,*p; LNode *minpre,*minp; while(L->next!=NULL){ pre=L;minpre=pre; p=L->next;minp=p; while(p!=NULL){ if(p->data<minp->data){ minpre=pre; minp=p; } pre=p; p=p->next; } printf("%d ",minp->data); minpre->next=minp->next; free(minp); } }
5、编写算法将带头街的单链表“就地”逆置,即空间复杂度为O(1)
①头插法
LinkList Reverse1(LinkList &L){ LNode *p,*q; p=L->next; L->next=NULL; //容易忘记 ,要养成好习惯。 while(p!=NULL){ q=p->next; //用新的指针记住p的后继结点的位置,防止断链 p->next=L->next; L->next=p; //头插法的典型代码,要记住 p=q; } return L; }
②p每往后遍历就修改指针,将其next指向上一个指针,这个设一个新指针pre来记录,就像删除操作的时候一样。
LinkList Reverse2(LinkList L){ LNode *pre,*p=L->next,*r=p->next; p->next=NULL; //处理第一个结点,就像链表中的尾结点r->next=NULL。这是反转后的链表,也要这样处理。 while(r!=NULL){ //如果r为空,则说明p为最后一个结点。 pre=p; p=r; r=r->next; //可理解为三个指针都向后移动 p->next=pre; //指针反转 } L->next=p; //处理最后一个结点,把头结点指向最后结点,实现最终反转。 return L; }