前言
书接上文(数据结构新手,如有错误,欢迎指正)
运行环境和基本程序可移步:郝斌数据结构-线性表之顺序表程序
题目
第四题
从有序顺序表中删除其值在给定值s 与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
我的版本:
/*有序表的元素是按照升序或降序排列好的*/
/*只需要找到需要删除的s和t所在位置*/
bool Del_s_t(struct arr *pArr,int s,int t){
int i,j;
int k;
if(s>=t||is_empty(pArr)){
return false;
} //不合理的情况退出运行
for(i=0;i<pArr->cnt;i++){
if(pArr->pBase[i]<s){
k++; //记录满足条件的元素个数
}else{
break;
}
}
for(j=pArr->cnt-1;j>0;j--){//cnt-1是最有一个元素的数组下标
if(pArr->pBase[j]>t){
k++; //记录满足条件的元素个数
}else{
break;
}
}
for(;j<pArr->cnt;i++,j++){
pArr->pBase[i] = pArr->pBase[j+1];//j+1的目的是将等于t的元素删掉
}
pArr->cnt = k; //将多余元素删除
return true;
}
参考答案:
bool Del_s_t_two(struct arr *pArr,int s,int t){
int i,j;
if(s>=t||pArr->cnt==0){
return false;
}
for(i=0;i<pArr->cnt&&pArr->pBase[i]<s;i++);//i直接停在小于s的第一个元素下标上
if(i>=pArr->cnt){
return false;//检错,例如出现数组最大元素5,删除区间在5-7的情况;
}
for(j=i;j<pArr->cnt&&pArr->pBase[j]<=t;j++);//j直接停在大于t的第一个元素下标上
for(;j<pArr->cnt;i++,j++){
pArr->pBase[i] = pArr->pBase[j];
}
pArr->cnt=i;//脑抽了!!!i就是数组长度,无需再用k计数了。
return true;
}
第五题
从顺序表中删除其值在给定值s与t之间(包含s和t,要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
基本思路:
不再是有序表了,首先遍历数组的全部元素,找到满足小于s和大于t的元素。用k计数,将满足条件的元素存入k的连续下标数组内。k的值就是新数组的长度。
bool Del_s_t_upgrade(struct arr *pArr,int s,int t){
int i,k=0;
if(is_empty(pArr)||s>=t){
return false;
}
for(i=0;i<pArr->cnt;i++){
if(pArr->pBase[i]<s||pArr->pBase[i]>t){
pArr->pBase[k] = pArr->pBase[i];
k++;
}
}
pArr->cnt = k;
return true;
}
参考答案:
bool Del_s_t_upgrade(struct arr *pArr,int s,int t){
int i,k=0;
if(is_empty(pArr)||s>=t){
return false;
}
for(i=0;i<pArr->cnt;i++){
if(pArr->pBase[i]>=s||pArr->pBase[i]<=t){
k++;//记录在s和t之间的元素
}else{
pArr->pBase[i-k]=pArr->pBase[i];//将在s和t之外的元素按顺序排列
}
}
pArr->cnt -= k; //这样判断反而有点麻烦了
return true;
}
第六题
从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。
/*有序表中,值相同的元素一定在连续的位置*/
/*我的思路,将相邻的两个元素对比,不同则存入第一个元素*/
bool Del_same(struct arr *pArr){
int i;
int k=0;
if(is_empty(pArr)){
return false;
}
for(i=0;i<pArr->cnt-1;i++){
if(pArr->pBase[i]!=pArr->pBase[i+1]){
pArr->pBase[k]=pArr->pBase[i];
k++;
}
}
pArr->cnt=k;
return true;
}
BUG:
参考答案:
bool Del_same(struct arr *pArr){
int i,j;
if(is_empty(pArr)){
return false;
}
//将第一个元素作为被比较的对象
for(i=0,j=1;j<pArr->cnt;j++){
if(pArr->pBase[i]!=pArr->pBase[j]){//查找下一个和上一个元素值不同的元素
pArr->pBase[++i]=pArr->pBase[j];//找到后将j元素覆盖i元素的后一个元素。
}
}
pArr->cnt = i+1;//更新当前数组的有效计数。
return true;
}
第七题
将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
我的思路:错得比较离谱。
参考答案:
思路:创建三个while循环体,从小到大排序,第一个循环体比较两个表中的数据大小,将小的一方存入array3中,同时对满足条件的有序表下标自增1,两个参与比较的有序表有一方遍历完就退出循环,第二第三个while循环用来查缺补漏,此时两个有序表中剩余的元素都是较大的,将剩余元素一一遍历存入array3即可。
关于后两个while循环的顺序问题,顺序不影响执行效果,因为从第一个while循环退出时,必然有一个有序表被遍历完了。
该算法的方法非常典型,需要牢固掌握!
bool Sum_A_B(struct arr *pArr1,struct arr *pArr2,struct arr *pArr3){
int i=0,j=0;
/*以下判断用来排出bug*/
if(pArr1->cnt+pArr2->cnt>pArr3->len){
return false;
}
while(i<pArr1->cnt&&j<pArr2->cnt){
if(pArr1->pBase[i]<=pArr2->pBase[j]){
append_arr(pArr3,pArr1->pBase[i++]);
}
else{
append_arr(pArr3,pArr2->pBase[j++]);
}
show_arr(pArr3);
}
while(i<pArr1->cnt){
append_arr(pArr3,pArr1->pBase[i++]);
show_arr(pArr3);
}
while(j<pArr2->cnt){
append_arr(pArr3,pArr2->pBase[j++]);
show_arr(pArr3);
}
return true;
}
算法执行过程:
/*主函数测试程序*/
int main(void){
struct arr array1;
struct arr array2;
struct arr array3;
int i,j=0;
int min=0;
init_arr(&array1,20);
init_arr(&array2,20);
init_arr(&array3,40);
for(i=0;i<1;i++){
for(j=1;j<=3;j++){
append_arr(&array1,j);
append_arr(&array1,j);
}
}
for(i=0;i<1;i++){
for(j=4;j<=7;j++){
append_arr(&array2,j);
append_arr(&array2,j);
}
}
show_arr(&array1);
show_arr(&array2);
Sum_A_B(&array1,&array2,&array3);
show_arr(&array3);
}
第八题
已知在一维数组A[m+n]中依次存放两个线性表(a1,a2, a3,…, am)和(b1, b2, b3,…, bn,)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1, b2, b3…, bn,)放在(a1, a2,a3,…,am)的前面。
我的思路:完全没思路。。
参考答案:
//left和right可以对一个顺序表内的任意切片片段进行排序
//left指定位置;right-left指定长度
void Reverse(struct arr *pArr,int left,int right){
int i;
int temp=0;
int mid = (left+right)/2;//一般的逆序输出法的中值,取前半元素和后半元素对调
//这里mid增强了函数的适用性,可对不同区域的数据切片进行逆序。
//排除bug
if(left>=right||right>pArr->cnt){
return;
}
for(i=0;i<=mid-left;i++){
temp = pArr->pBase[left+i];
pArr->pBase[left+i]=pArr->pBase[right-i];
pArr->pBase[right-i]=temp;
show_arr(pArr);
}
}
void Exchange(struct arr *pArr,int m,int n){
Reverse(pArr,0,m+n-1);//将全部数据原地逆序输出,第二个(n)逆序为第一个顺序表,第一个(m)逆序为第二个顺序表
Reverse(pArr,0,n-1);//前n个数据再逆序一次,
Reverse(pArr,n,m+n-1);//后m个数据再逆序一次
}
测试程序:
int main(void){
int i,j;
struct arr array;
init_arr(&array,20);
for(i=0;i<6;i++){
append_arr(&array,i);
}
for(j=8;j<20;j++){
append_arr(&array,j);
}
show_arr(&array);
printf("\n");
Exchange(&array,6,12);//满足left小于right
show_arr(&array);
}
输出结果:
第九题
线性表(a1, a2, a3,.…,an,)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素仍递增有序。
什么是二分法查找(折半查找)?
参考博文:【查找】折半查找
在有序数组中查找某一特定的元素,其基本思想是减少查找次数,每次比较后折半查找区间。这种方法要求被查表必须是有序顺序表。相比顺序查找法所用时间更短。
对于一个有序递增的序列:
(1,2,3,4,5,6,7,8,9)
如果要查找元素8,采用顺序查找法要比较8次,采用二分查找法可以设置三个指针,分别是low指向第一个元素1,mid指向中间元素5,high指向最后一个元素9。
首先,比较mid和8的大小关系,mid<8,说明查找对象在[mid,high]区间内;然后更新三个指针,low指向mid+1=6,mid指向low+(high-low)/2=7,high不变;
(1,2,3,4,5,6,7,8,9)
mid仍然<8,再取low=mid+1=8,mid=low+(high-low)/2=8,high不变;
mid=8,查找结束。
参考答案:
void SearchExchangeInsert(struct arr *pArr,int x){
int i,t;
int low=0,high=pArr->cnt-1,mid;//low和high分别指向顺序表的下界和上界
while(low<=high){
mid=(low+high)/2;//取中间位置
if(pArr->pBase[mid]==x)break;//找到x,退出while循环
else if(pArr->pBase[mid]<x)low=mid+1;//否则去[mid+1,high]区间查找
else high = mid-1; //再否则去[low,mid-1]查找
}
//两个if判断只可能执行一个。
if(pArr->pBase[mid]==x&&mid!=pArr->cnt-1){//查找的x不是最后一个元素
t = pArr->pBase[mid];
pArr->pBase[mid]=pArr->pBase[mid+1];
pArr->pBase[mid+1] = t;//将x和后继元素交换
}
//当查找元素不在顺序表内
if(low>high){
for(i=pArr->cnt-1;i>high;i--){
pArr->pBase[i+1]=pArr->pBase[i];
}
pArr->pBase[i+1]=x;
pArr->cnt+=1;//插入元素后,有效计数+1,不然不显示最后一个元素
}
}
测试程序:
int main(void){
int i,j;
struct arr array;
init_arr(&array,20);
for(i=0;i<13;i++){
append_arr(&array,i);
}
append_arr(&array,16);
show_arr(&array);
SearchExchangeInsert(&array,14);
show_arr(&array);
SearchExchangeInsert(&array,6);
show_arr(&array);
}
第十题
【2010统考真题】设将n (n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p ( 0<p<n)个位置,即将R中的数据由(X0,X1,…,Xn-1)变换为(Xp,Xp+t,…,Xn-1,X0,X1,…,Xp-1)。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
(1)算法思想:
可将这个问题视为把数组ab转换成数组ba(a代表数组的前p个元素,b代表数组中余下的n-p个元素),先将a逆置得到(a-1)b,再将b逆置得到(a-1)(b-1),最后将整个(a-1)(b-1)逆置得到(a-1b-1)-1=ba.设reverse函数执行将数组元素逆置的操作,对abcdefgh向左循环移动3(p=3)个位置的过程如下:
reverse(0,p-1)得到cbadefgh;
reverse(p,n-1)得到cbahgfed;
reverse(0,n-1)得到defghabc;
其中reserve函数的两个元素分别代表数组中待转换元素的始末位置。
(2)C语言算法描述:
void reserve(struct arr *pArr,int from,int to){
int i,temp;
for(i=0;i<(to-from+1)/2;i++){
temp=pArr->pBase[from+i];
pArr->pBase[from+i]=pArr->pBase[to-i];
pArr->pBase[to-i]=temp;
}
}
/*---------------------------
参数n是整个线性表的长度;
参数p是左移个数
----------------------------*/
void Converse(struct arr *pArr,int n,int p){
reserve(pArr,0,p-1);
reserve(pArr,p,n-1);
reserve(pArr,0,n-1);
}
/*这个函数实现的功能和第八题一致*/
void Converse(struct arr *pArr,int n,int p){
reserve(pArr,0,n-1);
reserve(pArr,n,n+p-1);
reserve(pArr,0,n+p-1);
}
(3)以上三个reserve函数的时间复杂度分别为O(p/2)、O((n-p)/2)和O(n/2),故设计的算法时间复杂度为O(n),空间复杂度O(1).
测试程序:
int main(void){
int i,j;
struct arr array;
init_arr(&array,20);
for(i=0;i<18;i++){
append_arr(&array,i);
}
show_arr(&array);
printf("\n");
Converse(&array,18,6);//顺序表中一共18个元素,将前6个元素整体左移6次。
show_arr(&array);
}