数据结构C语言综合应用题
引用型参数和指针的区别:
- 首先,引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。
- 其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。
- 引用是变量的一个别名,调用这个别名和调用这个变量是完全一样的。所以swap2的结果可以解释。值得注意的是,由于引用时别名,所以引用并不是一种数据类型,内存并不会给它单独分配内存,而是直接调用它所引用的变量。这个与地址传递也就是指针是不一样的(也就是说一个指针虽然指向一个变量,但是这个指针变量在内存中是有地址分配的
1 设计一个函数-从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值。空出的位置由最后一个元素填补,若顺序表为空则显出出错误信息并退出运行。
这里之所以使用引用传参的方式,是因为这样可以有多个返回值(用地址引用去开辟一片新的内存-然后不管函数怎么给value赋值,也只是改变指针的指向地址,并不会改变原来的数据,通过引用变量去获取两个返回值(一个是隐式的),这是一种很好的编程方法),而使用函数返回值的话只能有一个返回值
#include<stdio.h>
#include<stdlib.h>
//C语言综合应用题
bool delMin(sqlList &L,Elemtype &value){
//删除顺序表L中最小值元素点,并通过引用型参数value返回其值
//若删除成功,则返回true; 否则返回false
if(L.length==0){
return false;
}
value=L.data[0];
int pos=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的所有元素逆置,要求算法的空间复杂度为O(1)
扫描顺序表的前半部分元素。对于元素L.data[i] (0<=i<L.length/2), 将其与后半部分的对应元素L.data[L.length-i-1] 进行交换。
始终只是多开辟了一个 temp的 空间
#include<stdio.h>
#include<stdlib.h>
void reverse(Sqlist &L){
Elemtype temp;
for(i=0;i<L.length/2;i++){
temp=L.data[i];
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=temp;
}
}
3. 对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。
解法1是典型的错误算法–会强行增加时间复杂度,因为如果这样做,后面所有元素都需要前移,正确的还得看解法2
int count=0; //可以作为全局变量
bool DelAll(Sqlist &L,Elemyype x){
//记录有多少个值为x的数据元素
if(L.length==0){
print("该表为空!!")
return false;
}
for(i=0;i<L.length;i++){
if(x==L.data[i]){
L.data[i]=L.data[i+1];
L.length--;
count++;
}
}
return true;
}
count=0; //清零
解法2:用K记录顺序表L中不等于x的元素个数(即需要保存的元素个数),边扫描边统计k,并将不等于x的元素向前移动k个位置,最后修改L的长度。
bool del_x_1(Sqlist &L,Elemtype x){
//本算法实现删除顺序表L中所有值为x的数据元素
int k=0; //记录值不等于x的元素个数
for(i=0;i<L.length;i++){
if(L.data[i]!=k){
L.data[k]=L.data[i];
k++;
}
}
L.length=k; //顺序表L的长度等于k
}
4. 从有序顺序表中删除其值在给定值s与t之间(s<t)的所有元素,如果s或t不合理或顺序表为空,则显示出错信息并退出运行。
解法1(按照上题的思路)
bool del_spe(Sqlist &L,Elemtype s,Elemtype t){
if(L.length==0 || s>=t){
return false;
}
int k=0; //记录所有不在这个元素的元素
for(i=0;i++;i<L.length){
if(i<=S || i>=t){
L.data[k]=L.data[i];
k++;
}
}
L.length=k;
}
解法2–根据有序表的特性,要删除的元素必须是相连的整体。
所以我们只需要先寻找值大于等于s的第一个元素(第一个删除的元素),然后寻找值大于t的第一个元素(最后一个删除的元素的下一个元素),要将这段元素删除,只需直接将后面的元素前移。
bool Del_s_t2(SqList &L,Elemtype s,Elemtype 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++) //i从当前位置(也就是小于等于s的最后一个元素)向后增加
L.data[i]=L.data[j]; //前移填补位置
L.length=i;
return true;
}
5. 从顺序表中删除其值为给定值s与t之间(要求s < t)的所有元素,如果s或t不合理或顺序表为空,则显示出错信息并退出运行
解法1就是我们上面一题的解法1,这个解法可以说是万能解法
bool del_spe(Sqlist &L,Elemtype s,Elemtype t){
if(L.length==0 || s>=t){
return false;
}
int k=0; //记录所有不在这个元素的元素
for(i=0;i++;i<L.length){
if(i<=S || i>=t){
L.data[k]=L.data[i];
k++;
}
}
L.length=k;
}
解法2–从前向后扫描顺序表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之间(要求s<t)的所有元素
int i,k=0;
if(L.length==0||s>=t)
return false; //安全性检测
for(i=0;i<L.length;i++){
if(L.data[i]>=s&&L.data[i]<=t)
k++;
else
L.data[i-k]=L.data[i]; //当前元素前移k个位置
}
L.length-=k;
return true;
}
6. (重点)从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。 算法思路1–直接使用while循环或者if语句的方式检测重复元素的个数,用计数器记录重复元素的个数,监测完后;
bool Del_same(Sqllist &L){
//删除顺序表中相同的元素,使其中数据元素保持互异性
if(L.length==0)
return false;
int count=0; //记录总的相同的元素的数量
int i=0;
int k=0; //临时计数相同的元素
for(;i<L.length;i++){
while(L.data[i]==L.data[i+1]){
k++;
count++;
i=i+1;
}
L.data[i+1]=L.data[length-k];
k=0;
}
L.length-=count;
return true;
}
算法思路2–注意是有序顺序表,值相同的元素一定在连续的位置上,用类似于直接插入排序的思想,初始时将第一个元素视为非重复的有序表。之后依次判读后面的元素是否与前面非重复有序表的最后一个元素相同,若相同则继续向后判断,若不同则插入道前面的非重复有序表的最后,直到判断道表尾为止。–更好的办法(用指针)
bool Delete_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
}
L.length=i+1;
return true;
}
若不是有序顺序表,而是无序的表,可以用散列表(哈希表)实现,可以直接将数据元素(也就是值)作为关键字(也就是key),先遍历顺序表,将其放在散列表中,然后自动删除重复的key只保留一个即可。因为hash表是元素各异的,不能有重复的,唯一对应的。
7. (重点) 将两个有序(都是从小到大)顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表
–算法思路1:因为两个都是有序的,所以只需要进行一次首尾相连,但是需要注意的是大小的问题,来决定连接的方式,还需要判断每一个顺序表是从小到大排列的还是从大到小排列的
如果不知道有序顺序表的顺序是从大到小还是从小到大,就不是一个函数能够完成的,需要加判断函数来判断是正常工作还是需要用到顺序表的inverse函数才可以
bool Merge(Sqlist A,sqlist B,Sqlist &C){
//将有序顺序表A和表合并成一个新的有序顺序表C 新的有序顺序表是从小到大的
if(A.length+B.length>C.maxSize) //安全性检测
return false;
int i=0;j=0;k=0; //定义三个工作指针 //三个指针进行扫描--逐个比较
while(i<A.length&&J<B.length){
if(A.data[i]<=B.data[j])
C.data[k++]=A.data[i++];
else
C.data[k++]=B.data[j++];
}
while(i<A.length) //还剩一个没有比较完的顺序表
C.data[k++]=A.data[i++];
while(j<B.length)
C.data[k++]=B.data[j++];
C.length=k;
return true;
}
8.(重点)已知在一维数组A[m+n]中依次存放两个线性表(a1,a2,a3,…,am)和(b1,b2,b3,—,bn).试编写一个函数,对数组中两个顺序表的位置互换,即将(b1,b2,b3,…,bn)放在(a1,a2,a3,…am)的前面
--算法思路: 先将数组A[m+n]中的全部元素(a1,a2,a3,…,am.b1,b2,b3,…,bn)原地逆置为(bn,bn-1,bn-2,…,
b1,am,am-1,am-2,—,a1), 再对前n个元素和后m个元素分别使用逆置算法,即可得到(b1,b2,b3,—,bn.a1,a2,a3,a3,…,am),从而实现顺序表的位置互换.
typedef int DataType;
void Reverse(DataType A[],int left,int right,int arraySize){
//逆转(aleft,aleft+1,aleft+2...,aright)为(aright,aright-1,...,aleft)
if(left>=right || right >= arraySize)
return;
int mid=(left+right)/2;
for(int i=0;i<mid-left;i++){
Datatype 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,a3,---,am), 从m到m+n-1存放顺序表
(b1,b2,b3,---,bn) */
Reverse(A,0,m+n-1,arraySize);
Reverse(A,0,n-1,arraySize);
Reverse(A,n,m+n-1,arraySize);
}
9.线性表(a1,a2,an)中的元素递增有序且按顺序存储于计算机内,要求设计一算法,完成用最少时间在表中查找数值为x的元素,若找到则将其与后继元素位置相交换,若找不到则将其插入表中并使表中元素仍递增有序。
–算法思路:首选既然元素是递增有序的,最快的方法肯定是二分查找,如果找不到的话也可以将其插入到两个相邻构成区间的中间。
解法1(使用递归法,但是好像这种只适用于肯定能找到x,应该也可以改造成可以找到,但是我懒)
typedef int DataType;
int left=0;
int right=L.length;
DataType find_binary(sqlist &L,int left,int right,DataType x){
int mid=left+right/2;
int temp=0;
while(x!=L.data[mid]){
if(x>L.data[mid]){
find_binary((sqlist &L),mid,L.length-1,x);
}
else
find_binary((sqlist &L),0,mid-1,x);
}
temp=L.data[mid+1];
L.data[mid+1]=L.data[mid];
L.data[mid]=temp;
return mid;
}
解法2**(常用)(**不使用递归,但可以通过多重判断解决此题)
void SearchExchangeInsert(ElemType A[],ElemType x){
int low=0,high=n-1,mid; //low和gigh指向顺序表下界和上界的下标
while(low<=high){
mid=(low+high)/2; //找中间位置
if(A[mid]==x) break; //找到x,退出while循环
else if(A[mid]<x) low=mid+1; //到中点mid的右半部去查
else high=mid-1; //到中点mid的左半部去查
} //下面两个if语句只会执行一个
if(A[mid]==x&&mid!=n-1){
//若最后一个元素与x相等,则不存在其与后继交换
t=A[mid]; A[mid]=A[mid+1]; A[mid+1]=t; //的操作
}
if(low>high){
//查找失败,插入数据元素x
for(i=n-1;i>=high;i--) A[i+1]=A[i];//后移元素
A[high]=x; //插入x
}
}
10.
- 算法设计的基本思想:首先,将长度为n的一维数组进行reverse(),现在的顺序就是n-1-0分成两份,一份是b-1到 p,一份是p-1到0,然后做成两个顺序表,然后将两份都分别使用逆序(reverse()),就变成了p到p-1 和 0到 p-1,再进行merge合在一起即可。
形象化的流程可以理解为:
Reverse()函数的两个参数需要表示数组中待转换元素的始末位置
void Reverse(int R[],int from,int to){
int i,temp;
for(i=0;i<(to-from+1)/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);
}
- 上述算法中三个Reverse函数的时间复杂度分别为O(p/2)、O((n-p)/2)和O(n/2),故所设计的
算法的时间复杂度为O(n),空间复杂度为O(1);
算法思想2:–补充-- 可以借助辅助数组来实现。创建大小为p的辅助数组S,将R中前p分整数依次暂存在S中,同时将R中后n-p个整数左移,然后将S中暂存的p个数依次放回到R中的后续单元。时间复杂度为O(n),空间复杂度为O§;
–也是一种较为常见的经典算法
1. 算法的基本设计思想–这个算法实现的关键是要合并出把两个升序的顺序表合成一个升序的顺序表,可以采用工作指针–引用变量i,j分别遍历两个顺序表,并进行逐步比较,当一个顺序表被遍历完后,直接将另一个顺序表未遍历完的元素加到末尾即可。但这种方式至少都要遍历n次,算法的时间复杂度为O(n);
-
标准答案的算法设计思想–
分别求两个升序序列A、B的中位数,设为a和b,求序列A、B的中位数的过程如下:
- 若a=b,则a或b即为所求中位数,算法结束
- 若a<b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等
- 若a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等
int M_search(int A[],int B[],int n){
int s1=0,d1=n-1,m1=0,s2=0,d2=n-1,m2=0;
//分别表示序列A和B的首位数、末位数和中位数
while(s1!=d1||s2!=d2){
m1=(s1+d1)/2;
m2=(s2+d2)/2;
if(A[m1]==B[m2]) //满足条件1
return A[m1];
if(A[m1]<A[m2]){
//满足条件2
if((s1+d1)%2==0){
//若元素为奇数
s1=m1; //舍弃A中间点以前的部分且保留中间点
d2=m2; //舍弃B中间点以后的部分且保留中间点
}
else{
s1=m1+1; //舍弃A中间点及A中间点以前的部分
d2=m2; //舍弃B中间点以后的你部分且保留中间点
}
}
else{
//满足条件3
if((s2+d2)%2==0){
//若元素个数为奇数
d1=m1;
s2=m2;
}
else{
//若元素个数为偶数
d1=m1;
s2=m2+1;
}
}
}
return A[s1]<B[s2]?A[s1]:B[s2];
}
空间复杂度为O(1); 时间复杂度在最坏的情况下推导如下:
算法的基本设计思想–从前往后扫描数组元素,标记出一个可能成为主元素的元素Num。然后重新计数,确认Num是否是主元素。这个算法的核心就是找到数组中出现次数最多的元素,然后再去判断其是否是主元素即可。
算法可分为以下两步:
- 选取候选的主元素。依次扫描所给数组中的每个整数,将第一个遇到的整数Num保存到c中,选取Num的出现次数为1;若遇到的下一个整数仍等于Num,则计数加1,否则计数减1;当计数减到0时,将遇到的下一个整数保存到c中,计数重新记为1,开始新一轮计数,即从当前位置开始重复上述过程,直到扫描完全部数组元素。
- 判断c中元素是否是真正的主元素。再次扫描该数组,统计c中元素出现的次数,若大于n/2,则为主元素;否则,序列中不存在主元素。
int Majority(int A[],int n){
int i,c,count=1; //c用来保存候选主元素,count用来计数
c=A[0]; //设置A[0]为候选主元素
}
for(i=1;i<n;i++) //查找候选主元素
if(A[i]==c)
count++; //对A中的候选主元素计数
else
if(count>0) //处理不是候选主元素的情况
count--;
else{
//更换候选主元素,重新计数
c=A[i];
count=1;
}
if(count>0)
for(i=count=0;i<n;i++) //统计候选主元素的实际出现次数
if(A[i]==c)
count++;
if(count>n/2) return c; //确认候选主元素
else return -1; //不存在主元素
}
时间复杂度为O(n+n)=O(n),空间复杂度为O(1);
本题如果采用先排好序再统计的方法[时间复杂度可为O(nlog2n)],只要解答正确,最高可拿11分。即便是写出O(n2)的算法,最高也能拿10分,因此对于统考算法题,花费大量时间去思考最优解法是得不偿失的。
算法设计思想1–因为只需要在时间上尽可能高效,所以采用用空间换时间的方法,也就是设计一个辅助数组B[n],分别对应正整数1到n,值得注意的事,当数组A中出现了小于等于0或者大于n的值时,会导致1-n中出现空余位置,返回结果必然为1-n中,因此对于A中出现了小于等于0或大于n的值可以不采取任何操作。
所以我们的算法流程是:从A[0]开始遍历A,若0<A[i]<=n,则令B[A[i]-1]=1;
否则不做操作。对A遍历结束后,开始遍历数组B,若能查找到第一个满足B[i]==0的下标i,返回i+1即为结果,此时说明A中未出现的最小正整数在1~n之间。若B[i]全部不为0,返回i+1 (跳出循环时 i=n, i+1等于n+1), 此时说明A中未出现的最小正整数是n+1.
int findMissMin(int A[].int n){
int i,*B; //标记数组
B=(int *)malloc(sizeof(int)*n); //分配空间
memset(B,0,sizeof(int)*n); //赋初值为0
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; //返回结果
}
时间复杂度:两个遍历,时间复杂度为O(n). 空间复杂度:为O(n)辅助数组的长度.