数据结构2.3.课后题
2.3
一
B 链式储存结构比顺序储存结构更方便地表示各种逻辑结构(√)
B 散列储存只不过是提供了一个“关键字”到物理空间的映射方法,无法反映逻辑关系
CA 链式储存中各个不同结点的储存空间可以不连续,但结点内的储存单元地址必须连续D设有头指针和尾指针的单链表中,删除表尾元素的时间复杂度与表长无关(×)
删除表尾结点时,必须从头开始找到表尾元素的前驱,其时间和表厂有关。
A
C
BD给定n个元素的一维数组,建立一个有序链表的最低时间复杂度?可能流程1.提供数组—>按数组顺序直接插入排序,(就是直接插入排序O(n2))2提供数组->数组排序->按序建立起单链表 数组排序最小时间复杂度O(nlog2n) 建立单链表时间复杂度O(n) 相加为O(nlog2n)
AC 将长度为n的单链表链接到长度为m的单链表后面,求时间复杂度?先遍历长度为m的单链表,找到单链表的表尾结点,然后将next域指向另一个单链表的首结点,其空间复杂度为O(m)
C
B
B A
D
B
BCD
A
BAC
B
D
DC 对于一个带头节点的循环单链表L,判断该表为空表的条件是()L->next==L 头结点的指针域与L的值相等(√)头结点的指针域与L的地址相等(×)D
A
C
B
A
D
D
D
B
B
[16统考]D
[16统考]D
[21统考]D
[23统考]C
二
带头结点单链表L,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一。
做题
void delete_x(struct &L){ //错误应该是Linklist 未传入x LNode *p=L,q; while (p->next!=NULL){ if(p->next->data==x){ q=p->next; p->next=P->next->next; free(q) } p=p->next; } }
答案
解法一:用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; p=p->next; pre->next=p; free(q); }else{ pre=p; p=p->next; } } }
解法二采用尾插法建立单链表。用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){ r->next=p; r=p; p=p->next; } else{ q=p; p=p->next; free(q); } } r->next=NULL; }
上述俩个算法扫描一遍链表,时间复杂度O(n),空间复杂度O(1)
试编写在带头节点的单链表L中删除一个最小值结点的高效算法(假设该结点唯一)
做题
void delete_min(Linklist L) { LNode *p=L->next,*q=L->next; ElemType min=L->next->data; while (p->next!=NULL){ if (p->next->data<min) { q=p; min=p->next->data } p=p->next; } p=q; p->next=p->next->next; free(q); }
答案
用p从头至尾扫描单链表,pre指向*p结点的前驱,用minp保存值最小的结点指针(初值为p),minpre指向*minp结点的前驱(初值pre)。一边扫描一边比较,若p->data小于mino->data,则将p、pre分别赋值给minp、minpre。当p扫描完毕时,minp指向最小值结点,minpre指向最小值结点的前驱结点,再将minp所指向的结点删除即可。
Linklist Delete_Min(Linklist &L){ LNode *pre=L,*p=pre->next; LNode *minpre=pre,*minp=p; while(p!=NULL){ if(p-data<minp->data){ minp=p; minpre=pre; } pre=p; p=p->next; } minpre->next=minp->next; free(minp); return L; }
扫描一遍链表,时间复杂度O(n),空间复杂度O(1)
试编写算法将带头结点的单链表逆置,所谓“就地”是指辅助空间复杂度为O(1)
做题
void reverse (Linklist &L)//头插法 { LNode *p=L->next->next,*q=L->next,i; L.next=q; while (p!=NULL){ L->next=p; q=p->next; p->next=q; } }
答案
解法一,将头结点摘下,然后从第一个结点开始,依次插入到头结点的后面(头插法建立单链表)
直到最后一个结点为止,这样就实现了链表的逆置。
Linklist Reverse_1(Linklist L){ LNode *p,*r; p=L->next; L->next=NULL; while(p!=NULL){ r=p->next; p->next=L.next; L->next=p; p=r; } return L; }
解法二:假设pre、p和r指向三个相邻的结点,如图
假设经过若干操作,*pre 之前的结点指针都已调整完毕,他们的next都指向其原前驱结点,现令*p结点的next域指向*pre结点,注意一旦调整指针的指向,*p的后继结点的链就会断开,为此需要用r来指向原*p的后继结点。处理时需注意:一是处理第一个结点的时候应将其next域置为NULL,而不是指向头结点,二是在处理完最后一个结点后,需要将头结点的指针指向它。
Linklist Reverse_2(Linklist &L){ LNode *pre,*p=L->next,*r=p->next; p->next=NULL;处理第一个结点 while(r!=NULL){ pre=p; p=r; r=r->next; p->next=pre; } L->next=p;处理最后一个结点 return L; }
时间复杂度O(n),空间复杂度O(1)
设在一个带表头结点的单链表中,所有结点的元素值无序,试编写一个函数,删除表中所有介于给定的俩个值(作为函数参数给出)之间的元素(若存在)
做题
void Delete_1 (Linkinst &L,ElemType x,ElemType y){ LNode *pre=L,*p=L->next; while (p!=NULL){ if(x<p->data&&p->data<y){ pre->next=p->next; free(p); p=pre->next; }else{ pre=p; p=p->next; } } }
貌似完全正确
答案:因为链表无序,所以只能逐个结点进行检查,执行删除
void RangeDelete(Linklist &L,int min,int max){ LNode *pr=L,*p=L->next; while(p!=NULL){ if(p->data>min&&p->data<max){ pr->link=p->link; free(p); p=pr->link; } else{ pr=p; p=p->link; } } }
给定俩个单链表,试分析找出俩个链表的公共结点的思想(不用写代码)
作答:
依次遍历第一个链表的元素,取出每个元素后遍历第二个链表,看第二个链表中是否有相同元素,如果有相同元素则标记为公共结点。
答案:(题意都理解错了。。)
俩个单链表有公共结点,即俩个单链表从某个结点开始,他们的next都指向同一个结点。由于每个单链表结点只有一个next域,因此从第一个公共结点开始,之后的所有结点都是重合的,不可能在出现分叉。所以俩个有公共结点而部分重合的单链表,拓扑形状看起来像Y,而不可能像X。 本题极易想到蛮的方法:在第一个链表上顺序遍历每个结点,每遍历一个结点,在第二个链表上顺序遍历所有结点,若找到俩个相同的结点,则找到了他们俩个的公共结点。显然该算法的时间复杂度为O(len1xlen2)。 接下来我们试着去寻找一个线性时间复杂度的算法。先把问题简化:如何判断两个单向链表有没有公共结点?应注意到这样一个事实:若两个链表有一个公共结点,则该公共结点之后的所有结点都是重合的,即它们的最后一个结点必然是重合的。因此,我们判断两个链表是不是有重合的部分时,只需要分别遍历两个链表到最后一个结点。若两个尾结点是一样的,则说明它们有公共结点,否则两个链表没有公共结点。 然而,在上面的思路中,顺序通历两个链表到尾结点时,并不能保证在两个链表上同时到达尾结点,这是因为两个链表长度不一定一样。但假设一个链表比另一个长k个结点,我们先在长的链表上遍历k个结点,之后再同步遍历,此时我们就能保证同时到达最后一个结点。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的,因此它们肯定也是同时到达第一公共结点的,于是在通历中,第一个相同的结点就是第一个公共的结点。 根据这一思路中,我们先要务别遍历两个链表得到它们的长度,并求出两个长度之差、在长的链表上先遍历长度之差个结点后,在同步遍历俩个链表,直到找到相同的结点,若一直到链表结束。此时,该方法的时间复杂度为 O(lenl+len2)。
设C={a1,b1,a2,b2,…,an,bn}为线性表,采用带头结点的单链表存放,设计一个就地算法,将其拆分为俩个线性表,使得A={a1,a2,…,an},B={bn,…,b2,b1}.
作答
void divide (Linkinst &L){ //void divide(LinkList &L, LinkList &a, LinkList &b) { LNode *p=L->next,*q=L->next->next; LNode *a=L->next,*b=L->next->next; while (p->next->next!=NULL){ //(p->next != NULL && q->next != NULL) p->next=p->next->next; q->next=q->next->next; p=p->next; q=q->next; } return a,b;//错误 return; }
错误,一个是题目要求b反向 另一个思维错误,链表的原地。。。
答案:思想:循环遍历链表C,采用尾插法将一个结点插入表A,这个结点为奇数号结点,这样建立得表A与原来的结点顺序相同;采用头插法将下一结点插入表B,这个结点为偶数号结点,这样建立起B与原来的结点顺序正好相反。
Linklist Discreat_2(Linklist &A){ Linklist B=(Linklist)malloc(sizeof(LNode));//创建B表头 B->next=NULL; LNode *p=A->next,*q; LNode *ra=A; while(p!=NULL){ ra->next=p,ra=p;//将*p链接到A的表尾 p=p->next; if(p!=NULL){ p->next=B-next; B-next=p; p=q; } } ra->next=NULL; return B; }
在一个递增有序的单链表中,存在重复的元素,设计算法删除重复的元素,例如(7,10,10,21,30,42,42,42,51,70)将变为(7,10,21,30,42,51,70).
作答
void Delete_repetition(Linklist &L){ LNode *p=L->next,*pre =L; while(p!=NULL){ if (p->data==pre->data){ pre->next=p->next; free(p); p=pre->next; }else{ pre=p; p=p->next; } } }
应该基本正确,不如答案好,
答案:思想:由于是有序表,因此所有相同值域的结点都是相邻的,用p扫描递增单链表L,若*p结点的值域等于其后继结点的值域,则删除后者,否则p移向下一个结点
void Del_Same(Linklist &L){ LNode *p=L-next,*q; if (p==NULL) return; while(p->next!=NULL){ q=p->next; if(p->data==q->data){ p->next=q->next; free(q); } else p=p->next; } }
时间复杂度O(n),空间复杂度O(1)
设A和B是俩个单链表(带头结点),其中元素递增有序,设计一个算法从A和B的公共元素产生单链表C,要求不破坏A,B的结点。
作答
Linklist Creat_same(Linklist A,Linklist B){ Linklist C=(Linklist)malloc(sizeof(LNode)); LNode *p=A->next,*q=B->next,*m=C,*n;//后面几个漏了星号 while (p!=NULL&&q!=NULL){ if(p->data==q->data){ //判断相等要俩个等于 n=(LNode *)malloc (sizeof(LNode));//开始没有定义结点n n->data=p->data; n->next=NULL; m->next=n; m=n; p=p->next; q=q->next; }else if(p->data<q->data){ p=p->next; }else{ q=q->next; } } return C; }
基本正确 小细节有许多
答案:表A、B都有序,可以从第一个元素起依次比较A,B俩表的元素,若元素值不等则小的指针往后移,若元素值相等,则创建一个值等于俩结点的元素值的新结点,使用尾插法插入到新的链表中,并将两个原表指针后移一位,直到其中一个链表遍历到表尾。
void Get_Common(Linklist A,Linklist B){ LNode *p=A->next,*q=B->next,*r,*s; Linklist C=(Linklist)malloc(sizeof(LNode)); r=c; while(p!=NULL&&q!=NULL){ if(p->data<q-data) p=p->next; else if(p->data>q->data) q=q->next; else{ s=(LNode *)malloc(sizeof(LNode)); s->data=p->data; r-next=s; r=s; p=p->next; q=q->next; } } r->next=NULL; }
已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A和B的交集,并存放于A链表中。
作答
void Get_Common(Linklist &A,Linklist B){ LNode *p=A->next,*q=B->next,*r,*s,*n; free(B); r=A; while(p!=NULL&&q!=NULL){ if(p->data<q->data){ n=p; p=p->next; free(n); } else if(p->data>q->data){ n=q; q=q->next; free(n); } else{ s=(LNode *)malloc(sizeof(LNode)); s->data=p->data; r->next=s; r=s; n=p; p=p->next; free(n); n=q; q=q->next; free(n); } } if(p!=NULL){ while(p!=NULL){ n=p; p=p->next; free(n); } } else if(q!=NULL){ while(q!=NULL){ n=q; q=q->next; free(n); } } r->next=NULL; }
答案:思想:采用归并的思想,设置两个工作指针pa和pb,对两个链表进行归并扫描,只有同时出现在两个集合的元素才能链接到结果表中且仅保留一个,其他结点全部释放。当一个链表遍历完毕后,释放另一个表中剩下的全部结点。
Linklist Union(Linklist &la,Linklist &lb){ Lnode *pa=la->next; LNode *pb=lb->next; LNode *u,*pc=la; while(pa&&pb){ if(pa->data==pb->data){ pc->next=pa; pc=pa; pa=pa->next; u=pb; pb=pb->next; free(u); }else if(pa->data<pb->data){ u=pa; pa=pa->next; free(u); }else{ u=pb; pb=pb->next; free(u); } }while(pa){ u=pa; pa=pa->next; free(u); } while(pb){ u=pb; pb=pb->next; free(u); } pc->next=NULL; free(lb); return la; }
时间复杂度O(len1+len2),空间复杂度O(1)。
两个整数序列A=a1,a2,a3,…,am和B=b1,b2,b3,…,bn已存入两个单链表中,设计一个算法,判断B序列是否是序列A的连续子列。
作答
bool son(Linklist A,linklist B){ LNode *p=A->next,*q=B->next,*n; while(p){ if(p->data==q->data){ n=p } } if (n) while(p&&q){ if(p->data!=q->data) return false; p=p->next; q=q->next; } else return false; return true; }
错误
答案:思想:因为两个整数序列已经存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾才匹配成功。A链表到尾而B链表未到尾则表示匹配失败。操作应记住A链表,每次的开始结点,以便下次匹配时好从其后继开始。
int Pattern(Linklist A,Linklist B){ LNode *p=A: LNode *pre=p; LNode *q=B; while(p&&q){ if(p->data==q->data){ p=p->next; q=q->next; }else{ pre=pre->next; p=pre; q=B; } } if(q==NULL) return 1; else return 0; }