y++;elsex++;
}
【解答】
(1)n-1
(2)i=n/2,j=n/2
(3)n+1,n(n+1),n2,(n+1)n2,n3
(4)100,1000
1.6有实现同一功能的两个算法A1和A2,其中A1的时间复杂度为Tl=O(2n),A2的时间复杂度为T2=O(n2),仅就时间复杂度而言,请具体分析这两个算法哪一个好。
【解答】对算法A1和A2的时间复杂度T1和T2取对数,得nlog2和2logn。
显然,当n<4时,算法A1好于A2;当n=4时,两个算法时间复杂度相同;当n>4时,算法A2好于A1。
1.7选择题:
算法分析的目的是()
A、找出数据结构的合理性B、研究算法中的输入和输出的关系
C、分析算法的效率以求改进D、分析算法的易懂性和文档特点
【解答】C
二、算法设计题
1.8已知输入x,y,z三个不相等的整数,设计一个“高效”算法,使得这三个数按从小到大输出。
“高效”的含义是用最少的元素比较次数、元素移动次数和输出次数。
【算法1.8】
voidBest()
{//按序输出三个整数的优化算法
inta,b,c,t;
scanf(“%d%d%d”,&a,&b,&c);
if(a>b)
{t=a;a=b;b=t:
}//a和b已正序
if(b>c)
{t=c;c=b;//c已到位
if(a>t){b=a;a=t;}//a和b已正序
elseb=t;
}
printf(“%d,%d,%d\n”,a,b,c);
//最佳2次比较,无移动;最差3次比较,7个赋值
}
1.9在数组A[n]中查找值为k的元素,若找到则输出其位置i(1≤i≤n),否则输出0作为标志。
设计算法求解此问题,并分析在最坏情况下的时间复杂度。
【题目分析】从后向前查找,若找到与k值相同的元素则返回其位置,否则返回0。
【算法1.9】
intSearch(ElemTypeA[n+1],ElemTypek)
{i=n;
while(i>=1)&&(A[i]!
=k))i--;
if(i>=1)returni;
elsereturn0;
}
当查找不成功时,总的比较次数为n+1次,所以最坏情况下时间复杂度为O(n)。
在学过第9章“查找”后,可优化以上算法:
设“监视哨”。
算法如下:
intSearch(ElemTypeA[n+1],ElemTypek)
{i=n;A[0]=k;
while(A[i]!
=k)i--;
returni;
}
研究表明,当n>=1000时,算法效率提高50%。
第2章线性表
一、基础知识题
2.1试述头指针、头结点、元素结点、首元结点的区别,说明头指针和头结点的作
【解答】指向链表第一个结点(或为头结点或为首元结点)的指针称为头指针。
“头指针”具有标识一个链表的作用,所以经常用头指针代表链表的名字,如链表L既是指链表的名字是L,也是指链表的第一个结点的地址存储在指针变量L中,头指针为“NULL”则表示指针变量L没指向任何链表。
有时,我们在整个线性链表的第一个元素结点之前加入一个结点,称为头结点,它的数据域可以不存储任何信息(当然,作为“副产品”,头结点的数据域也可能做监视哨或存放线性表的长度等附加信息),指针域中存放的是第一个数据结点的地址,空表时为空。
“头结点”的加入,使插入和删除等操作方便、统一。
元素结点即是数据结点,至少包括元素自身信息和其后继元素的地址两个域。
首元结点是指链表中第一个数据元素的结点;为了操作方便,通常在链表的首元结点之前附设一个结点,称为头结点。
2.2分析顺序存储结构和链式存储结构的优缺点,说明何时应该利用何种结构。
【解答】①从空间上来看,当线性表的长度变化较大,难以估计其规模时,选用动态的链表作为存储结构比较合适。
由于链表除了需要设置数据域外,还要额外设置指针域,因此当线性表长度变化不大,易于事先确定规模时,为了节约存储空间,宜采用顺序存储结构。
②从时间上看,顺序表具有按元素序号随机访问的特点,在顺序表中按序号访问数据元素的时间复杂度为O
(1);而链表中按序号访问的时间复杂度为O(n)。
所以如果经常按序号访问数据元素,使用顺序表优于链表。
在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此n较大时顺序表的插入和删除效率低。
在链表中作插入、删除,虽然也要找插入位置,但操作主要是比较操作。
从这个角度考虑显然链表优于顺序表。
总之,两种存储结构各有长短,选择那一种存储结构,由实际问题中的主要因素决定。
2.3分析在顺序存储结构下插入和删除结点时平均需要移动多少个结点。
【解答】平均移动表约一半的结点。
插入操作平均移动
个结点,删除操作平均移动
个结点。
具体移动的次数取决于表长和插入、删除的结点的位置。
2.4为什么在单循环链表中常使用尾指针,若只设头指针,插入元素的时间复杂度如何?
【解答】单循环链表中无论设置尾指针还是头指针都可以遍历到表中任一个结点。
设置尾指针时,若在表尾进行插入元素或删除第一元素,操作可在O
(1)时间完成;若只设置头指针,表尾进行插入或删除操作,需要遍历整个链表,时间复杂度为O(n)。
2.5在单链表、双链表、单循环链表中,若知道指针p指向某结点,能否删除该结点,时间复杂度如何?
【解答:
】以上三种链表中,若知道指针p指向某结点,都能删除该结点。
双链表删除p所指向的结点的时间复杂度为O
(1),而单链表和单循环链表上删除p所指向的结点的时间复杂度均为O(n)。
2.6下面算法的功能是什么?
LinkedListUnknown(LinkedListla)
{LNode*q,*p;
if(la&&la->next)
{q=la;la=la->next;p=la;
while(p->next)p=p->next;
p->next=q;q->next=null;
}
returnla;
}
【解答】将首元结点删除并插入到表尾(设链表长度大于1)。
2.7选择题:
在循环双链表的*p结点之后插入*s结点的操作是()
A、p->next=s;s->prior=p;p->next->prior=s;s->next=p->next;
B、p->next=s;p->next->prior=s;s->prior=p;s->next=p->next;
C、s->prior=p;s->next=p->next;p->next:
=s;p->next->prior=s;
D、s->prior=p;s>next=p>next;p>next->prior=s;p->next=s;
【解答】D
2.8选择题:
若某线性表最常用的操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用()存储方式最节省时间。
A.顺序表B.双链表C.带头结点的双循环链表D.单循环链表
【解答】A
二、算法设计题
2.9设ha和hb分别是两个带头结点的非递减有序单链表的头指针,试设计算法,将这两个有序链表合并成一个非递增有序的单链表。
要求使用原链表空间,表中无重复数据。
【题目分析】因为两链表已按元素值非递减次序排列,将其合并时,均从第一个结点起进行比较,将小的链入链表中,同时后移链表工作指针,若遇值相同的元素,则删除之。
该问题要求结果链表按元素值非递增次序排列,故在合并的同时,将链表结点逆置。
【算法2.9】
LinkedListUnion(LinkedListha,hb)
∥ha,hb分别是带头结点的两个单链表的头指针,链表中的元素值按非递减有序
∥本算法将两链表合并成一个按元素值非递增有序的单链表,并删除重复元素
{pa=ha->next;∥pa是链表ha的工作指针
pb=hb->next;∥pb是链表hb的工作指针
ha->next=null;∥ha作结果链表的头指针,先将结果链表初始化为空
while(pa!
=null&&pb!
=null)∥当两链表均不为空时作
{while(pa->next&&pa->data==pa->next->data)
{u=pa->next;pa->next=u->next;free(u)}∥删除pa链表中的重复元素
while(pb->next&&pb->data==pb->next->data)
{u=pb->next;pb->next=u->next;free(u)}∥删除pb链表中的重复元素
if(pa->datadata)
{r=pa->next;∥将pa的后继结点暂存于r
pa->next=ha->next;∥将pa结点链于结果表中,同时逆置
ha->next=pa;
pa=r;∥恢复pa为当前待比较结点
}
elseif(pb->datadata)
{r=pb->next;∥将pb的后继结点暂存于r
pb->next=ha->next;∥将pb结点链于结果表中,同时逆置
ha->next=pb;
pb=r;∥恢复pb为当前待比较结点
}
else{u=pb;pb=pb->next;free(u)}∥删除链表pb和pa中的重复元素
}//while(pa!
=null&&pb!
=null)
if(pa)pb=pa;∥避免再对pa写下面的while语句
while(pb!
=null)∥将尚未到尾的表逆置到结果表中
{r=pb->next;pb->next=ha->next;ha->next=pb;pb=r;}
returnha
}∥算法Union结束
2.10设la是一个双向循环链表,其表中元素递增有序。
试写一算法插入元素x,使表中元素依然递增有序。
【问题分析】双向链表的插入与单链表类似,不同之处是需要修改双向指针。
【算法2.10】
DLinkedListDInsert(DLinkedListla,ElemTypex)
∥在递增有序的双向循环链表la中插入元素x,使表中元素依然递增有序
{p=la->next;∥p指向第一元素
la->data=MaxElemType;∥MaxElemType是和x同类型的机器最大值,用做监视哨
while(p->datap=p->next ;
s=(DLNode*)malloc(sizeof(DLNode));∥申请结点空间
s->data=x;
s->prior=p->prior;s->next=p;∥将插入结点链入链表
p->prior->next=s;p->prior=s;
}
2.11设p指向头指针为la的单链表中某结点,试编写算法,删除结点*p的直接前驱结点。
【题目分析】设*p是单链表中某结点,删除结点*p的直接前驱结点,要找到*p的前驱结点的前驱*pre。
进行如下操作:
u=pre->next;pre->next=u->next;free(u);
【算法2.11】
LinkedListLinkedListDel(LinkedListla,LNode*p)
{∥删除单链表la上的结点*p的直接前驱结点,假定*p存在
pre=la;
if(pre-next==p)
printf(“*p是链表第一结点,无前驱\n”) ;exit(0) ;}
while(pre->next->next!
=p)
pre=pre->next;
u=pre->next;pre->next=u->next;free(u);
return(la);
}
2.12设计一算法,将一个用循环链表表示的稀疏多项式分解成两个多项式,使这两个多项式各自仅有奇次幂或偶次幂项,并要求利用原链表中的结点空间来构造这两个链表。
【题目分析】设循环链表表示的多项式的结点结构为:
typedefstructnode
{intpower;∥幂
floatcoef;∥系数
ElemTypeother;∥其他信息
structnode*next;∥指向后继的指针
}PNode,*PolyLinkedList;
则可以从第一个结点开始,根据结点的幂是奇数或偶数而将其插入到奇次幂或偶次幂项的链表中。
假定用原链表保存偶次幂,要为奇次幂的链表生成一个表头,为了保持链表中结点的原来顺序,用一个指针指向奇次幂链表的表尾。
注意链表分解时不能“断链”。
【算法2.12】
voidPolyDis(PolyLinkedListpoly)
∥将poly表示的多项式链表分解为各含奇次幂或偶次幂项的两个循环链表
{PolyLinkedListpoly2=(PolyLinkedList)malloc(sizeof(PNode));
∥poly2表示只含奇次幂的多项式
r2=poly2;∥r2是只含奇次幂的多项式链表的尾指针
r1=poly;∥r1是只含偶次幂的多项式链表当前结点的前驱结点的指针
p=poly->next;∥链表带头结点,p指向第一个元素
while(p!
=poly)
if(p->power%2)∥处理奇次幂
{r=p->next;∥暂存后继
r2->next=p;∥结点链入奇次幂链表
r2=p;∥尾指针后移
p=r;∥恢复当前待处理结点
}
else∥处理偶次幂
{r1->next=p;r1=p;p=p->next;}
}
r->next=poly2;r1->next=poly;∥构成循环链表
}∥PolyDis
2.13以带头结点的双向链表表示的线性表L=(a1,a2,…,an),试写一时间复杂度为O(n)的算法,将L改造为L=(a1,a3,…,an,…,a4,a2)。
【题目分析】分析结果链表,易见链表中位置是奇数的结点保持原顺序,而位置是偶数的结点移到奇数结点之后,且以与原来相反的顺序存放。
因此,可从链表第一个结点开始处理,位置是奇数的结点保留不动,位置是偶数的结点插入到链表尾部,并用一指针指向链表尾,以便对偶数结点“尾插入”。
【算法2.13】
DLinkedListDInvert(DLinkedListL)
∥将双向循环链表L位置是偶数的结点逆置插入到链表尾部
{p=L->next;∥p指向第一元素
Q=p->prior;∥Q指向最后一个元素
pre=L ;∥pre指向链表中位置为奇数的结点的前驱
r=L ;∥r指向链表中偶数结点的尾结点
i=0 ;∥i记录结点序号
while(p !
=Q)∥寻找插入位置
{i++ ;
if(i%2)∥处理序号为奇数的结点
{p->prior=pre ;pre->next=p ;pre=p;p=p->next;}
else∥处理序号为偶数的结点
{u=p ;∥记住当前结点
p=p->next ;∥p指向下个待处理结点
u->prior=r->prior; ∥以下4个语句将结点插入链表尾
u->next=r;
r->prior->next=u;
r->prior=u;
r=u;∥指向新的表尾
}∥else
}∥while
}∥结束算法
2.14设单向链表的头指针为head,试设计算法,将链表按递增的顺序就地排序。
【题目分析】本题中的“就地排序”,可理解为不另辟空间,这里利用直接插入原则把链表整理成递增有序链表。
【算法2.14】
LinkedListLinkListInsertSort(LinkedListhead)
∥利用直接插入原则将带头结点的单链表head整理成递增的有序链表
{if(head->next!
=null)∥链表不为空表
{p=head->next->next;∥p指向第一结点的后继
head->next->next=null;∥第一元素有序,然后从第二元素起依次插入
while(p!
=null)
{r=p->next;∥暂存p的后继
q=head;
while(q->next&&q->next->datadata)
q=q->next;∥查找插入位置
p->next=q->next;∥将p结点链入链表
q->next=p;
p=r;
}
}
}
2.15已知递增有序的三个单链表分别代表集合A,B和C,设计算法实现A=A∪(B∩C),并使结果链表仍保持递增。
要求算法的时间复杂度为O(|A|+|B|+|C|)。
其中,|A|为集合A的元素个数。
【题目分析】本题首先求B和C的交集,即求B和C中的共有元素,再与A求并集,同时删除重复元素,以保持结果A递增。
【算法2.15】
LinkedListunion(LinkedListA,B,C)
∥A、B和C均是带头结点的递增有序的单链表,本算法实现A=A∪(B∩C)
∥结果表A保持递增有序
{pa=A->next;pb=B->next;pc=C->next;∥设置三个工作指针
pre=A;∥pre指向结果链表中当前待合并结点的前驱
A->data=MaxElemType;∥同类型元素最大值,起监视哨作用
while(pa||pb&&pc)
{while(pb&&pc)
if(pb->datadata)pb=pb->next;
elseif(pb->data>pc->data)pc=pc->next;
elsebreak;∥B表和C表有公共元素
if(pb&&pc)
{while(pa&&pa->datadata)∥先将A中小于B,C公共元素部分链入
{pre->next=pa;pre=pa;pa=pa->next;}
if(pre->data!
=pb->data)
{pre->next=pb;pre=pb;pb=pb->next;pc=pc->next;}
else{pb=pb->next;pc=pc->next;}∥A中已有B,C公共元素
}
}∥while(pa||pb&&pc)
if(pa)pre->next=pa;∥当B,C无公共元素,将A中剩余链入
elsepre->next=null;∥A已到尾,B,C也无公共元素,
}∥算法Union结束
2.16顺序表la与lb非递减有序,顺序表空间足够大。
试设计一种高效算法,将lb中元素合到la中,使新的la的元素仍保持非递减有序。
高效指最大限度地避免移动元素。
【题目分析】顺序存储结构的线性表的插入,其时间复杂度为O(n),平均移动近一半的元素。
线性表la和lb合并时,若从第一个元素开始比较,一定会造成元素后移,这不符合本题“高效算法”的要求。
应从线性表的最后一个元素开始比较,大者放到最终位置上。
设两线性表的长度各为m和n,则结果表的最后一个元素应在m+n位置上。
这样从后向前,直到第一个元素为止。
【算法2.16】
SeqListUnion(SeqListla,SeqListlb)
∥算法将顺序存储的非递减有序表la和lb中的lb合并到la中,la仍非递减有序
{m=la.last;n=lb.last;∥m,n分别为线性表la和lb的长度
k=m+n-1;∥k为结果线性表的工作指针(下标)
i=m-1;j=n-1;∥i,j分别为线性表la和lb的工作指针(下标)
while(i>=0&&j>=0)
if(la.data[i]>=lb.data[j])la.data[k--]=la.data[i--];
elsela.data[k--]=lb.data[j--];
while(j>=0)la.data[k--]=lb.data[j--];
la.last=m+n;
returnla;
}
【算法讨论】算法中数据移动是主要操作。
在最佳情况下(lb的最小元素大