//考研复习(2):线性表的顺序表示典型题
----------------------------------------------------------------------------------------------------------------
//---1.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删除元素的值,
//空出的位置由最后一个元素填补,若顺序表为空则显示出错信息并推出运行
[算法思想]搜索整个顺序表,查找最小元素并记住其位置,搜索结束后用最后一个元
素填补空缺的原最小元素的位置
[代 码]
bool Del_Min(sqList &L,ElemType &value){
//删除顺序表L中最小元素结点,将最后一个元素补给删除元素所在位置
//并通过引用型参数value返回其值
//若删除成功,就返回true,否则返回false
if(L.length==0)
return false; //若表空,中止操作返回
value=L.data[0];
int pos=0; //假设0号元素最小
for(int i=1;i<L.length;i++) //循环,寻找最小元素,并记住其值和位置
if(L.data[i]<value){
value=L.data[i];
pos=i;
}
L.data[pos]=L.data[L.length-1]; //空出的位置由最后一个元素填补
L.length--;
return true; //此时,value为最小值
}
----------------------------------------------------------------------------------------------------------------
//---2.设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为0(1).
[算法思想]扫描顺序表L的前半部分,对于元素L.data[i](0≤i<L.length/2),
将其与后半部分元素L.data[L.length-i-1]交换。
[代码]
void Reverse(sqList &L){
//将元素逆置,用引用型变量返回逆置后的顺序表
ElemType temp; //辅助变量
for(int i=0;i<L.length/2;i++){
temp=L.data[i]; //将L.data[i]与L.data[L.length-i-1]元素交换
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=temp;
}
}
----------------------------------------------------------------------------------------------------------------
//---3. 对长度为n的顺序表L,编写一个时间复杂度为0(n)、 空间复杂度为0(1)的
//算法,该算法删除线性表中所有值为x的数据元素。
方法1:
[算法思想]用K记录顺序表中L不等于x的元素个数,边扫描L边统计k,将不等于x的元素向前移动到k的位置上,最后修改L的长度
[代码]
void Del_X_1(sqList &L,ElemType X){
//本算法实现将所有等于X的数据元素删除
//利用引用型参数L来实现删除后的顺序表的返回
int k=0; //记录不等于X的元素个数
for(int i=0;i<L.length;i++)
if(L.data[i]!=X){
L.data[k]=L.data[i]; //将不等于X的元素移动到k+1的位置
k++; //不等于X的元素加1
}
L.length=k; //顺序表的长度为k,因为当最后一个不等于X的元素移动后,k还要加1
}
方法2:
[算法思想]用k记录顺序表L中等于X的元素个数,边扫描L边统计k,将不等于X的元素向前移动k个位置,最后修改L的长度
[代码]
void Del_X_2(sqList &L){
//本算法实现将所有等于X的数据元素删除
//利用引用型参数L来实现删除后的顺序表的返回
int k=0,i=0; //k记录值等于X的元素个数
while(i<L.length){
if(L.data[i]==X){
k++; //出现等于X的元素,k加1
}
else{
L.data[i-k]=L.[i]; //出现不等于X的元素,该元素向前移k位
}
i++;
}
L.length=L.length-k; //顺序表L的长度少了k
}
方法3:
[算法思想]用头尾两个指针,从两端向中间移动,凡遇到最左端值x的元素时候,将最右边非x的元素前移至x元素的位置处,
直到两指针相遇,但是这种方法会改变原表中元素的相对位置
----------------------------------------------------------------------------------------------------------------
//---4. 从有序顺序表中删除其值在给定值s与t之间(要求s < t)的所有元素,如
//果s或t不合理或顺序表为空,则显示出错信息并退出运行。
[算法思想]先寻找值大于等于s的第一个元素(要删除的第一个元素),然后寻找大于t的第一个元素(要删除的元素的最后
一个元素的下一个),将这段元素后面的元素前移来删除这段元素
[代码]
bool Del_s_t2(sqList &L,ElemType s,ElemType t){
//删除有序表L中值在给定s与t之间的所有元素
int i ,j;
if(s>=t||L.length==0)
return false;
for(i=0;i<L.length&&L.data[i]<s;i++); //寻找值大于等于s的第一个元素所在位置
if(i>=L.length) //若一直扫描完都没找到,那么就中止程序
return false;
for(j=i;j<L.length&&L.data[j]<=t;j++); //寻找值大于t的第一个元素所在的位置,扫描完还没找到那就停在表尾部
for(;j<L.length;i++,j++) //将大于t的第一个元素以及后面的元素前移,填补被删除元素
L.data[i]=L.data[j];
L.length=i; //表L的长度为i(因为当填补完成后,j在L.length-1,但是还会加1,所以对于i也为新表长度)
return true;
}
----------------------------------------------------------------------------------------------------------------
//5.从顺序表中删除其值在给定值s与t之间(包含s和t,要求s<t)的所有元
//素,如果s或t不合理或顺序表为空,则显示出错信息并退出运行。
[算法思想]从前往后扫描顺序表L,用K记录下元素在s和t之间的元素个数,k初始为0,对于当前扫描的元素,若其值不在s和t
之间则,往前移动k个位置,否则执行k++,由于这样每个在s到t之间的元素只需要移动一次,所以算法效率高
[代码]
bool Del_s_t(sqList &L,ElemType s,ElemType t){
//删除顺序表L中s到t中的所有元素
int i,k=0;
if(s>=t||L.length==0)
return false; //若s,t不合法或表为空则返回,程序终止
for(i=0;i<L.length;i++){
if(L.data[i]<s||L.data[i]>t)
L.data[i-k]=L.data[i]; //若元素不在s,t之间就往前移动k个元素
else
k++; //若元素在s到t之间就执行k++
L.length=L.length-k; //表的长度比删除前减少k
return true;
}
}
//备注:第5题虽然和第4题相比只差了1个词语,但是它的思想却和第3题完全一样,有三种解法
----------------------------------------------------------------------------------------------------------------
//---6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。
[算法思想]类似直接插入排序的思想,初始时候将第一个元素看作一个非重复表,扫描其后的元素,与非重复表最后一个元素比,
若相同,则往后扫描,若不同,则将此元素移动到非重复表的结尾,直至表尾
[代码]
bool Del_same(sqList &L){
if(L.length==0)
return false;
int i,j; //i存储第一个不相同的元素,j扫描后面的元素
for(i=0,j=1;j<L.length;j++)
if(L.data[i]!=L.data[j]) //查找下一个与不相同表最后一个元素不相同的元素
L.data[++i]=L.data[j]; //找到后前移到不重复表的最后
L.length=i+1; //因为前面前移用的是L.data[++i],则长度为i+1
return true;
}
----------------------------------------------------------------------------------------------------------------
//---7.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
[算法思想]首先,按照顺序不断取两个顺序表的表头较小的系欸DNA存入新的顺序表中,然后看哪个表还有剩余,将剩下的部分加
到新表的后面
[代码]
bool Merge(sqList A,sqList B,sqList &C){
//将两个有序表A与B合并为一个新的有序表C
if(A.length+B.length>C.maxSize) //若原两表长度大于要合并的表的最大长度,则返回
return false;
int i=0,j=0,k=0;
while(i<A.length&&j<B.length) //扫描两表,并比较,将最小的放入C中
if(A.data[i]<B.length[j])
C.data[k++]=A.data[i++];
else
C.data[k++]=B.data[j++];
while(i<A.length) //若A还有剩下,则A剩下的元素放入C中,若B还有剩的,将B剩下的元素放入C中
C.data[k++]=A.data[i++]
while(j<B.length)
C.data[k++]=B.data[j++]
C.length=k; //设置C表的长度
return true;
}
----------------------------------------------------------------------------------------------------------------
//---8.已知在一维数组A[m+n]中依次存放两个线性(a1,a2,a3,...,am)和(b1,b2,b3...bn).
//试编写一个函数,将数组中两个顺序表的位置互换,即将(b1, b2,...,bn)放在(a1,a2, a3,..,an)的前面。
[算法思想]首先将数组A[m+n]中的元素(a1,a2,a3,...,am,b1,b2,b3,...,bn)原地逆置,变成(bn,...,b2,b1,am,...,a2,a1),
然后分别将(bn,...,b2,b1)和(am,...,a2,a1)逆置,即可得到(b1,b2,...,bn,a1,a2,...,am)
[代码]
typedef int DataType;
void Reverse(DataType A[],int left,int right,int arraySize){
//将数组A中从left到right的元素逆置
if(left>=right||right>=arraySize)
return;
int mid;
mid=(left+right)/2;
for(int i=0;i<mid-left;i++){
DataType temp;
temp=A[left+i];
A[left+i]=A[right-i];
A[right-i]=temp;
}
}
void Exchange(DataType A[],int m,int n,int arraySize){
//A[m+n]中0到m-1存放(a1,a2,...,am)从m到m+n-1存放(b1,b2,...,bn)
Reverse(A,0,m+n-1,arraySize);
Reverse(A,0,n-1,arraySize);
Reverse(A,n,m+n-1,arraySize);
}
-----------------------------------------------------------------------------------------------------------------
//---9.线性表(a1,a2,a3,...,an)中的元素递增有序且按顺序存储于计算机内。要求设计一算法,
//完成用最少时间在表中查找数值为x的元素,若找到则将其与后继元素位置交换,若找不到则将其插入表中
//并使表中元素仍递增有序。
[算法思想]根据题目要求,本体采用折半查找
[代码]
void SearchExchangeInsert(ElemType A[],int n,ElemType x){
int low=0,high=n-1,mid; //开始的时候,low和high分别指向数组的最小下标和最大下标
while(low<=high){ //这一步是查找x元素,若没找到,那么x就应该放到high+1的位置
mid=(low+high)/2;
if(A[mid]==x)
break;
else if(A[mid]<x)
low=mid+1;
else
high=mid-1;
}
if(A[mid]==x&&mid!=n-1){ //若找到x并且x不在数组最后的位置,这时候才可以与后继元素交换
ElemType temp;
temp=A[mid];
A[mid]=A[mid+1];
A[mid+1]=temp;
}
if(low>high){ //若没找到x,就将x插进去
int i;
for(i=n-1;i>high;i--){
A[i+1]=A[i];
}
A[i+1]=x; //x要放在high+1的位置,因为上一步循环当i=high+1循环完成后i又减1了
}
}
-----------------------------------------------------------------------------------------------------------------
//---10[2010统考真题]设将n (n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。
//将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0, X1,..., Xn-1 )变换为(Xp, Xp+1,..., Xn-1, X0,...,Xp-1).
//(1)给出算法的基本设计思想。
//(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
//(3)说明你所设计算法的时间复杂度和空间复杂度。
[算法思想]可以将这个问题看作把数组ab(a代表数组的前p个元素,b代表数组中余下的n-p个元素),先把a逆置得到a^(-1)b,
再将b逆置得到a^(-1)b^(-1),最后将a^(-1)b^(-1)整体逆置,得到(a^(-1)b^(-1))^(-1)=ba,设Reverse函数执行逆置操作
例如将abcdefgh向左循环p(p=3 )个单位的过程如下:
Reverse(0,p-1)得到cbadefgh;
Reverse(p,n-1)得到cbahgfed;
Reverse(0,n-1)得到defghabc;
注:Reverse中两个参数分别表示待逆置的元素始末位置。
[用c语言描述]
void Reverse(int R,int from,int to){ //逆置函数
int i,temp;
for(i=0;i<(to-from)/2;i++){
temp=R[from+i];
R[from+i]=R[to-i];
R[to-i]=temp;
}
}
void Converse(int R[],int n,int p){
Reverse(R,0,p-1);
Reverse(R,p,n-1);
Reverse(R,0,n-1);
}
[时间与空间复杂度]
时间复杂度:三个逆置函数的时间复杂度分别为O(p/2),O((n-p)/2),O(n/2),故时间复杂度为O(n)
空间复杂度:申请了temp,所以为O(1)
-----------------------------------------------------------------------------------------------------------------
//---11.[2011统考真题]一个长度为L (L>=1)的升序序列S,处在第[L/2](向上取整)个位置的数称为S的中位数。
//例如,若序列S1=(11, 13, 15,17, 19),则S的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。
//例如,若S2= (2,4,6,8,20),则S1和S2的中位数是11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都
//尽可能高效的算法,找出两个序列A和B的中位数,要求:
//(1)给出算法的基本设计思想。
//(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
//(3)说明你所设计算法的时间复杂度和空间复杂度。
[算法思想]算法的基本思想如下:
求两个升序序列A、B的中位数设为a和b,求序列A、B的中位数过程如下
(1)、若a=b,则a或b即为所求中位数,算法结束。
(2)、若a<b,则舍弃序列A中较小的一半,同时舍弃B中较大一半,要求两次舍弃的大小一样
(3)、若a>b,则舍弃序列A中较大的一半,同时舍弃B中较小一半,要求两次舍弃的大小一样
在保留的两个升序当中重复以上三个过程,直到两个序列中只含有一个元素时为止,较小者即为所求
[用c语言描述]
int M_Search(int A[],int B[],int n){
int s1=0,d1=n-1,m1,s2=0,d2=n-1,m2;
//以上分别代表序列A和B的首位数、末位数和中位数
while(s1!=d1||s2!=d2){
m1=(s1+d1)/2;
m2=(s2+d2)/2;
if(A[m1]=B[m2]) //满足第一种情况
return A[m1];
if(A[m1]<B[m2]){ //满足第二种情况
if((s1+d2)%2==0){ //元素个数为奇数
s1=m1; //舍弃A序列中点以前的部分,保留中点
d2=m2; //舍弃B序列中点以后的部分,保留中点
}
else{ //元素个数为偶数
s1=m1+1; //舍弃A序列中点以及中点以前的部分
d2=m2; //舍弃B序列中点以后的部分,保留中点
}
}
else{ //满足第三种情况
if((s2+d2)%2==0){ //元素个数为奇数
s2=m2; //舍弃B序列中点以前的部分,保留中点
d1=m1; //舍弃A序列中点以后的部分,保留中点
}
else{ //元素个数为偶数
s2=m2+1; //舍弃B序列中点以及中点以前的部分
d1=m1; //舍弃A序列中点以后的部分,保留中点
}
}
}
return A[s1]<B[s2]?A[s1]:B[s2]; //组后各剩一个元素中较小的是中位数
}
[时间与空间复杂度]
时间复杂度:O(log2n)
空间复杂度:O(1)
-----------------------------------------------------------------------------------------------------------------
//---12. [2013统考真题]已知一个整数序列A = (a0,a1,...,an-1), 其中θ≤ai<n(0≤i<n)。若存在ap1=ap2= ... =apm=x 且m>n/2
//(0≤pk<n,1≤k≤m),则称x为A的主元素。例如A -(0,5,5,3,5,7,5,5),则5为主元素;又如A=(0,5,5,3.5,1,5,7),则A中没有主元素。
//假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素;
//否则输出-1.要求:
//(1)给出算法的基本设计思想。
//(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
//(3)说明你所设计算法的时间复杂度和空间复杂度。
[算法思想]算法的策略是从前往后扫描数组元素,标记出一个可能成为住院时的元素Num。然后重新计数,确认Num是否是主元素。
算法可以分为两步:
①、选取候选主元素。依次扫描所给数组的中的每个整数,将第一个遇到的整数Num保存到c中,记录Num的出现次数是1,
若遇到下一个整数仍等于Num,则计数加1,否则计数减1,当计数减到0的时候,将遇到的下一个整数保存到c中,将计
数重新记为1;开始新一轮计数,即充当前位置重复上述过程,直到扫描完全部数组元素。
②、判断c中元素是否是真正的主元素。再次扫描该数组,统计c中元素出现的次数,若大于n/2,则为主元素;否则,序
列中不存在主元素。
[用c语言描述]
int Majority(int A[],int n){
int i,c ,count=1; //c用来保存候选主元素,count用来计数
c=A[0]; //开始,设置A[0]为主元素
for(i=0;i<n;i++){ //查找主元素
if(A[i]==c)
count++; //对当前c保存的候选主元素计数
else //处理不是主元素时候的情况
if(count>0)
count--;
else{
c=A[i]; //更换主元素,重新计数
count=0;
}
}
for(i=count=0;i<n;i++) //统计主元素实际出现的次数
if(A[i]==c)
count++;
if(count>n/2) //符合定义,返回主元素
return c;
else //不符合定义,返回-1
return -1;
}
[时间与空间复杂度]
时间复杂度:O(n)
空间复杂度:O(1)
注意:本体如果是用先排序再统计的办法(时间复杂度为O(nlog2n)),只要回答正确,最高可拿11分,即便是写
出O(n^2)的算法最高也能拿10分,因此对于算法题,花费大量时间为一个想出来可能性还不大的最优算法是得不偿失的。
----------------------------------------------------------------------------------------------------------------
//---13. [2018统考真题]给定一个含n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的
//最小正整数。例如,数组{-5,3,2,3}中未出现的最小正整数是1;数组{1,2,3}中未出现的最小正整数是4.要求:
//(1)给出算法的基本设计思想。
//(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
//(3)说明你所设计算法的时间复杂度和空间复杂度。
[算法思想]我们用B[n]来标注A中是否出现了1~n中的数,其中B[i]代表i+1,B[n-1]代表n。B初始化全部为0,由于A中含n个数,返
回可能是1~n中的任何一个数,也可能是n+1,当A中n个数恰好为1~n时返回n+1,当A中出现了小于1或者大于n的数时,其
中1~n中必然至少有一个数是A没有包含的,这时候返回值应该是这些没包含的数中最小的那个。
流程为:
从A[0]开始遍历A,若0<A[i]<=n,则令B[A[i]-1]=1,否则不做操作。遍历A结束后,开始遍历B数组,第一个满是B[i]==
0 的时候,返回i+1,若B全部为1,返回i+1 (这时候i循环结束后由于又执行了一次加1操作为n,i+1 为n+1 ),此时
A中未出现的最小整数为n+1
[用c语言描述]
int findMissMin(int A[],int n){
int i,*B; //标记数组
B=(int*)malloc(sizeof(int )*n); //分配空间
memset(B,0,sizof(int)*n); //赋初值为0,memset参数分别是起始地址、填充的数、填充长度
for(i=0;i<n;i++)
if(A[i]>0&&A[i]<=n) //若A[i]的值介于1~n,则标记数组B
B[A[i]-1]=1;
for(i=0;i<n;i++) //扫描数组B,找到结果
if(B[i]==0)
break;
return i+1; //①若找到为0的元素,此时i为为0的元素下标,返回结果是i+1,
//②若没找到为0的元素,循环结束,i还会加1,从n-1变成n,返回结果还是i+1
}
[时间复杂度]
时间复杂度:O(n)
空间复杂度:O(n)