1.设顺序表用数组A[]
表示,表中元素存储在数组下标1~m+n
的范围内,前m
个元素递增有序,后n
个元素也递增有序,设计一个算法,使得整个顺序表有序。
(1) 给出算法的基本设计思想。
(2) 根据设计思想,采用C或C++语言描述算法,并在关键之处给出注释。
(3) 说明你所设计的算法的时间复杂度和空间复杂度。
解:
(1) 算法的基本设计思想:
将数组A[]中的m+n个元素(假设元素为int
型)看成两个顺序表:表L
和表R
。将数组当前状态看作是起始状态,即此时表L
由A[]
中前m个元素构成,表R
由A[]
中后n个元素构成。要使A[]
中m+n
个元素整体有序,只需将表R
中的元素逐个插入表L
中的合适位置即可。
插入过程:取表R中的第一个元素A[m+1]
存入辅助变量temp
中,让temp
逐个与A[m]
,A[m-1]
,...,A[1]
进行比较,当temp
<A[j]
(1≤j≤m
)时,将A[j]
后移一位,否则将temp
存入A[j+1]
中。重复上述过程,继续插入A[m+1]
,A[m+3]
,...,A[m+n],最终
A[]`中元素整体有序。
(2) 算法描述:
void Insert(int A[],int m,int n)
{
int i,j;
int temp; //辅助变量,用来暂存待插入元素
for(i=m+1;i<=m+n;++i) //将A[m+1,...,m+n]插入到A[1,..,m]中
{
temp=A[i];
for(j=i-1;j>=1 && temp<A[j];--j)
A[j+1]=A[j]; //整体元素后移,以便腾出一个位置插入temp
A[j+1]=temp;
}
}
(3) 算法的时间和空间复杂度
I. 本题的规模有m
和n
共同决定。取最内层循环中A[j+1]=A[j];
这一句作为基本操作,其执行次数在最坏的情况下为R
中的每个元素都小于L
中的所有元素;又因为R
中元素递增有序,则对于每个R
中的元素,要将其插入正确位置都必须进行m
次移动,R
中共有n
个元素,因此有;
f(m,n)=mn
由此可见,本算法的时间复杂度为O(mn)
。
II. 算法额外空间中只有一个变量temp
,因此空间复杂度为O(1)
。
2.已知递增有序的单链表A
、B
(A
、B
中元素个数分别为m
、n
,且A
、B
都带有头结点)分别存储了一个集合,请设计算法,以求出两个A
和B
的差集A-B
(仅由在A
中出现而不在B
中出现的元素所构成的集合)。将差集保存在单链表A
中,并保持元素的递增有序性。
(1) 给出算法的基本设计思想。
(2) 根据设计思想,采用C或C++语言描述算法,并在关键之处给出注释。
(3) 说明你所设计的算法的时间复杂度。
解:
(1) 算法的基本设计思想
只需从A
中删去A
与B
中共有的元素即可。由于两个链表中元素是递增有序的,所以可以这么做:
设置两个指针p
、q
开始时分别指向A
和B
的开始结点。循环进行以下判断和操作:如果p
所指结点的值小于q
所指结点的值,则p
后移一位;如果q
所指结点的值小于p
所指结点的值,则q
后移一位;如果两者所指结点的值相同,则删除p
所指结点。最后p
与q
任一指针为NULL
的时候算法结束。
(2) 算法描述
void Difference(LNode *A,LNode *B)
{
LNode *p=A->next,*q=B->Next; //p和q分别是链表A和B的工作指针
LNode *pre=A; //pre为A中p所指结点的前驱结点的工作指针
LNode *r; //用来删除相同结点p
while(p!=NULL && q!=NULL)
{
if(p->data < q->data)
{
pre=p;
p=p->next; //A链表中当前结点指针往后移
}
else if(p->data > q->data)
{
q=q->next; //B链表中当前结点指针往后移
else
{
pre->next=p->next; //处理A、B中元素值相同的结点,应删除
r=p;
p=p->next;
free(r); //删除结点
}
}
}
(3) 算法的时间复杂度分析
由算法描述可知,算法的规模由m
和n
共同确定。算法中有一个单层循环,循环内的所有操作都是常数级的,因此可以赢循环执行的次数作为基本操作执行的次数。可见循环执行的次数即为p
、q
两指针沿着各自链表移动的次数,考虑最坏的情况,即p
、q
都走完了自己所在的链表,循环执行m+n
次,因此时间复杂度为O(m+n)
。
3.设计一个算法,将顺序表中的所有元素逆置。
解:
(1) 算法的基本设计思想:
两个变量i
、j
指示顺序表的第一个元素和最后一个元素,交换i
、j
所指元素,然后i
向后移动一个位置,j
向前移动一个位置,如此循环,直到i
与j
相遇时结束,此时顺序表L
中的元素已经逆置。
(2) 算法描述:
void reverse(Sqlist &L) //L要改变,用引用型
{
int i,j;
int temp; //辅助变量,用于交换
for(i=1,j=L.length;i<j;++i,--j) //当i与j相遇时循环结束
{
temp=L.data[i];
L.data[i]=L.data[j];
L.data[j]=temp;
}
}
注意:本题中for
循环的执行条件要写成i<j
,不要写成i!=j
。如果数组中元素有偶数个,则i
与j
有可能出现互相跨越状态。若条件设为i!=j
,则i
继续往右走,j
继续往左走,会互相跨越对方,循环不会结束。
4.设计一个算法,从一给定的顺序表L
中删除下标i~j
(i≤j
,包括i
、j
)的所有元素,假定都是合法的。
解:
(1) 算法的基本设计思想:
本题是顺序表删除算法的扩展,可以采用如下方法解决:从第j+1
个元素开始到最后一个元素为止,用这之间的每个元素去覆盖从这个元素开始往前数j-i+1
个元素,即可完成删除i~j
的所有元素。
(2) 算法描述:
void delete(Sqlist &L,int i,int j) //L要改变,用引用型
{
int k,l; //l是表长
l=j-i+1;
for(k=j+1;k<L.length;++k)
{
L.data[k-1]=L.data[k]; //用第k个元素去覆盖它前边的第l个元素
}
L.length-=l; //表长改变
}
5.有一个顺序表L
,且元素为整型数据,设计一个算法,将L
中所有小于表头元素的整数放在前半部分,大于表头元素的整数放在后半部分,数组从下标1开始存储。
解:
(1) 算法的基本设计思想:
先将L
的第一个元素存于temp
中,然后定义两个整型变量i
、j
。i
从左往右扫描,j
从右往左扫描,边扫描边交换。
要注意以下几点:
i
和j
是轮流移动的,即当i
找到比2大的元素时,将i
所指元素放入j
所指位置,i
停在当前位置不动,j
开始移动。j
找到比2小的元素时,将j
所指元素放在i
所指位置,j
停在当前位置不变,i
开始移动,如此交替,直到i==j
。每次元素覆盖(如执行
L.data[i]=L.data[j];
不会造成元素丢失,因为在这之前被覆盖位置的元素已经存入其他位置。
(2) 算法描述:
void move(Sqlist &L) //L要改变,所以用引用型
{
int temp;
int i=1,j=L.length;
temp=L.data[i];
while(i<j)
{
/*关键步骤开始*/
while(i<j&L.data[j]>temp) --j; //j从右往左扫描,当来到第一个比temp
//小的元素时停止,并且每走一步都要判断
//i是否小于j,这个判断容易遗漏
if(i<j) //检测看是否已满足i<j,这一步同样很重要
{
L.data[i]=L.data[j]; //移动元素
++i; //i右移一位
}
while(i<j&&L.data[i]<temp) ++i; //与上面处理类似
if(i<j) //与上面处理类似
{
L.data[j]=L.data[i]; //与上面处理类似
--j;
}
/*关键步骤结束*/
}
L.data[i]=temp; //将表首位置放在最终位置
}
6.有一个递增非空单链表,设计一个算法删除域重复的结点。例如,{1,1,2,3,3,3,4,4,7,7,7,9,9,9}
经过删除后变成{1,2,3,4,5}
。
解法一:
(1)算法的基本设计思想:
定义指针p
指向起始结点。将p
所指的当前结点值域和其直接后继值域比较。如果当前结点值域等于后继结点值域,则删除后继结点;否则p
指向后继结点。重复以上过程,直到p
的后继结点为空。
(2)算法描述:
void delsll(LNode *L)
{
LNode *p=L->next,*q;
while(p->next!=NULL)
{
if(p->data==p->next->data) //找到重复结点并删除
{
q=p->next;
p->next=q->next;
free(q);
}
else
p=p->next;
}
}
解法二:
(1)算法的基本设计思想:
依次将原序列中每个连续相等子序列的第一个元素移动到表的前端,将剩余元素删除即可。
令p
指向起始结点。q
从p
的后继结点开始扫描,q
每来到一个新结点的时候进行检测:当q->data
等于p->data
的时候,什么都不做……
7.设计一个算法删除单链表L(有头结点)中的一个最小值结点。
解:
(1)算法的基本设计思想:
用p
从头到尾扫描链表,pre
指向*p
结点的前驱,用minp
保存值最小的结点指针,minpre
指向minp
的前驱。一边扫描,一边比较,将最小值结点放到minp
中。
(2)算法描述:
void delminnode(LNode *L)
{
LNode *pre=L,*p=pre->next,*minp=p,*minpre=pre;
while(p!=NULL) //查找最小值结点minp以及前驱结点minpre
{
if(p->data<minp->data)
{
minp=p;
minpre=pre;
}
pre=p;
p=pre->next;
}
minpre->next=minp->next; //删除*minp结点
free(minp);
}
8.有一个线性表,采用带头结点的单链表L
存储。设计一个算法将其逆置。要求不能建立新结点,只能通过表中已有结点的重新组合来完成。