一些用来复习和提高的题,包括选择题、算法设计题、统考真题等,不定期更新
注:代码部分为伪代码
目录
一、选择题
二、算法设计题
三、真题
(1)在一个长度为 n 的顺序表中删除第 i 个元素时(1 <= i <= n)需前移()个元素
A. n B. i - 1
C. n - i D. n - i + 1
答案:C
(2)在长度为n的非空线性表中实现插入操作,不是动态分配存储的情况下,i 的合法取值范围为()
A. 1<= i <= n B. 1 <= i <= n + 1
C. 0 <= i <= n - 1 D. 0 <= i <= n
答案:B
(1)LA,LB为两个集合A和B,现求一个集合A = A ∪ B(数据结构C语言版(严蔚敏)P20)
void union(List &La, List &Lb){
La_len = ListLengh(La);
Lb_len = ListLengh(Lb); //获取长度
for(i = 1; i <= Lb_length; i++){
e = GetElem(Lb, i); //获取Lb中的一个元素
if(LocateElem(La ,e) == -1){ //判断La中是否有该元素,如果没有,则插入La中
ListInsert(La, ++La_len, e);
}
}
}
(2)已知LA,LB中的数据元素按值非递减排列,合并LA,LB为一个新的线性表LC,且LC中的数据元素仍然非递减有序,如LA =(3,5,8,11),LB =(2,6,8,9,11,15,20),则LC =(2,3,5,6,8,8,9,11,11,15,20)(数据结构C语言版(严蔚敏)P20)
void MergeList(List &La, List &Lb, List &Lc){
initList(Lc);
i = j = 1;
k = 0; //i指La,j指Lb,k指Lc
La_length = ListLength(La);
Lb_length = ListLength(Lb);
while((i < La_length) && (j < Lb_length)){//La,Lb非空
GetElem(La, i, ei);
GetElem(La, j, ej);
if(ei < ej){ //比较哪个大,谁小就将其插入线性表LC
ListInsert(Lc, ++k, ei);
i++;
} else{
ListInsert(Lc, ++k, ej);
j++;
}
}
while(i <= La_len){ //La还没插入完
GetElem(La, i, ei);
ListInsert(Lc, ++k, ei);
}
while(j <= Ln_len){ //Lb还没插入完
GetElem(Lb, i, ej);
ListInsert(Lc, ++k, ej);
}
}
(3)实现顺序表插入算法(数据结构C语言版(严蔚敏)P24)
Status ListInsert_Sq(SqList &L, int i, ElemType e){
if(i < 1 || i > L.length + 1) return ERROR; //判断插入位置是否合法
if(L.length >= L.MaxSize){
//判断顺序表是否满,此处动态分配,若不能动态分配,直接ERROR
newbase = (ElemType*)realloc(L.data,
(L.MaxSize + LISTINCREASEMENT) * sizeof(LNode));
//LISTINCREASEMENT是每次再分配内存时的大小增量,如一次再分配10个节点长度的内存
if(!newbase) exit(OVERFLOW); //存储分配失败
L.data= newbase; //分配新基址
L.MaxSize += LISTINCREASEMENT;
//return ERROR //如果是静态存储分配的话
}
q = &(L.data[i-1]); //获取插入位置
for(p = &(L.data[L.length-1]); p >= q; --p){
*(p + 1) = * p; //插入位置及之后位置后移
}
*q = e; //此处是地址
++L.length;
return OK;
}
(4)实现顺序表删除算法,删除第i个元素(数据结构C语言版(严蔚敏)P24)
Status ListDelete_Sq(SqList &L, int i, ElemType &e){
if((i < 1) || (i > L.length)) return ERROR //删除位置合法
p = &(L/elem[i-1]); //p为被删除的元素的位置
e = *p;
q = L.elem + L.length -1; // q为表尾元素的位置
for(++p; p <= q; ++p){
*(p - 1) = *p;
}
--L.length;
return OK;
}
(5)从顺序表中删除具有最小值的元素(唯一),并由函数进行返回,空出的位置由最后一个元素填补,若顺序表为空,则提示错误并退出运行(王道2023数据结构 P18)
bool Del_Min(sqList & L, ElemType &e){
if(L.length == 0){
return false;
}
e = L.data[0]; //初始化最小值为顺序表第一个元素
int pos = 0; //设置位置指针
for(int i = 1; i < L.length; i++){
if(L.data[i] < e){
e = L.data[i];
pos = i
}
}
L.data[pos] = L.data[L.length-1]; 用最后一位填充
L.length--; 减去长度,默认原有的最后一位元素不在顺序表中了,但实际物理存储中仍然在那个位置
return true;
}
(6)逆置顺序表L,要求算法时间复杂度为O(1)(王道2023数据结构 P18)
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;
}
}
(7)从有序顺序表中删除值在s与t之间的所有元素(s
bool Del_s_t(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
(8)对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,删除顺序表中值为x的所有节点(王道2023数据结构 P18)
void del_x_1(SqList &L, ElemType x){ //直接跳过值为x的值,用不为x的节点进行填充
int k = 0, i;
for(i = 0; i < L.length; i++){
if(L.data[i] != x){
L.data[k] = L.data[i];
k++;
}
}
L.length = k;
}
void del_x_2(SqList &L, ElemType x){ //k记录所有值为k的元素
int k = 0, i = 0;
while(i < L.length){
if(L.data[i] == x){
k++;
} else{
L.data[i-k] = L.data[i];
}
i++;
}
L.length = L.length - k;
}
void del_x_3(SqList &L, ElemType){ //设置头尾指针往中间遍历
int i = 0;
int j = L.length - 1;
int k = 0; //记录值为x的数量
while(i < j){ //循环结束条件
if(L.data[i] == x){
k++;
while(L.data[j] == x){ //找到右侧第一个不为x的值
k++;
j--;
}
L.data[i] = L.data[j];
i++;
j--;
} else{
i++;
}
}
L.length = L.length - k;
}
//这是我自己写的算法,可能会有错误,欢迎指正
(9)在一维数组A[m+n]中依次存放两个线性表(a1,a2,a3,...,am)个(b1,b2,b3,...bn)。编写一个函数,让数组中两个顺序表位置互换,即(b1,b2,b3...,bn,a1,a2,a3,...am)(王道2023数据结构 P18)
typedef int DataType;
void Reverse(DataType A[], int left, int right, int arraySize){
//逆转从left到right间A中的元素
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){
//先整体逆序,再前n个逆序,后m个再逆序
Reverse(A, 0, m+n-1, arraySize);
Reverse(A, 0, n-1, arraySize);
Reverse(A, n, m+n-1, arraySize);
}
(10)线性表(a1,a2,a3,...an)中元素递增有序且顺序存储到一维数组中,设计一个算法,在较短时间内查找值为x的元素,找到后将其与其后继元素互换位置,若没有找到,则插入该x元素,并保持线性表仍然递增有序(王道2023数据结构 P18)
void SearchExchangeInsert(ElemType A[], ElemType x){
//时间要好,就折半查找,再插入
int low = 0, high = n - 1, mid;
while(low <= high){ //折半查找
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相等,则不用交换后继
t = A[mid];
A[mid] = A[mid+1];
A[mid+1 = t;
}
if(low < high){ //没有该元素,则从high开始后移元素,再插入mid位置一个x
for(i = n-1; i > high; i--) A[i+1] = A[i];
A[i+1] = x;
}
}
(1)设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0 【2010统考真题】)
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1)
u1s1,这个其实和算法设计题第9题类似。从原顺序表变化到现在的顺序表,同样可以先将现有的顺序表逆置,然后前n-p个逆置,后p个再逆置,就得到题中的结果,过程应为:
Reverse(0, n-1);
Reverse(0, n-p-1);
Reverse(n-p, n-1);
或者先逆置前p个元素,再逆置后n-p个元素,再逆置整个顺序表,效果是一样的
2)
代码如下:
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(0, n-1); Reverse(0, n-p-1); Reverse(n-p, n-1); }
3)
三个Reverse函数的时间复杂度分别为O(n/2)、O((n-p)/2)、O(p/2),故时间复杂度应为O(n),仅采用了一个temp作为辅助空间,所以空间复杂度为O(1)
另解:
使用辅助数组来实现,使用一个长度为p的辅助数组存放需要左移的p个元素,再将原数组之后的n-p个元素全部左移,最后将辅助数组中的元素再填充回去即可,时间复杂度也为O(n),但空间复杂度为O(p)
代码如下:(也是自己写的,可能有错误,欢迎指正)
void Converse_2(int R[], int n, int p){ int i; int t[p]; //定义辅助数组 for(i = 0; i < p; i++){ t[i] = R[i]; //存储前p个的值 } for(i = 0; i < n-p; i++){ R[i] = R[i+p]; //左移后n-p个元素 } for(i = 0; i < p; i++){ R[i+n-p] = t[i]; //重新赋值 } }
(2)一个长度为L(L ≥ 1)的升序序列S,处在第 [L/2] 个位置的数称为S的中位数。例如,若序列S=(11,13, 15, 17, 19)。则S的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若S=(2,4,6,8,20),则S和S的中位数是11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数,要求:(【2011统考真
题】)
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1)
直接合并两个序列再找其中位数固然可行,但显然不是最优解,于是可以考虑中位数的特点,可分如下三种情况进行讨论
① 若a = b,则a或b即为所求的中位数,算法结束
② 若a < b,则证明所求的中位数肯定在a,b之间,故舍弃A中较小的一半,舍弃B中较大的一半,要求舍去的长度相等
③ 若a > b,则证明所求中位数在b、a之间,则与②相反即可
2)代码如下
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 + d1) % 2 == 0){ //若元素个数为奇数 s1 = m1; d2 = m2; } else{ s1 = m1 + 1; d2 = m2; } } else{ //情况③ if((s2 + d2) % 2 == 0){ //若元素个数为奇数 d1 = m1; s2 = m2; } else{ d1 = m1; s2 = m2 + 1; } } } return A[s1] < B[s2] ? A[s1] : B[s2]; }
3)
时间复杂度为O(log2n),空间复杂度为O(1)
(3)已知一个整数序列A=(a0,a1,···,an-1),其中0<=ai
1)给出算法的基本设计思想。代码注释中
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
3)说明你所设计算法的时间复杂度和空间复杂度。时间复杂度:O(n);空间复杂度:O(1)。
1)
算法目标即找到数组中数目超过一半的元素是哪一个,大体流程应该是先标记出一个可能成为主元素的元素,然后判断其是否为主元素。故分为两步
① 选取候选的主元素,由于主元素需要超过一半,故平均来说,每两个元素中应该必有一个主元素,故采取如下方法进行选取:依次扫描数组,将遇到的第一个整数记为候选元素,同时计数器记为1,再往后遍历,如果下一个元素与之相等,则计数器加1,如果不相等,则减1,若计数器值为0,则选取下一个元素作为候选元素,从当前位置开始重复上述过过程。在这种选取的情况下,只要一个元素是主元素,则它必会被选中,即使它分布不均匀,当然被选中的元素却未必一定是主元素,所以需要进行第二步的判断。
② 再次扫描数组,统计候选主元素出现的次数,如果大于n / 2,则说明它是主元素,否则不是。
2)代码如下
int Majority(int A[], int n){ int i, c, count = 1; c = A[0] for(i = 1; i < n; i++){ //第一次遍历整个数组 if(A[i] != c){ count--; } else{ count++; } if(count < 1){ c = A[i]; count = 1; } } //计算是否为主元素 if(count > 0){ // <0代表没有主元素 for(i = count = 0; i < n; i++){ //第二次遍历整个数组 if(A[i] == c){ count++; } } } if(count > n / 2) return c; else return -1; }
3)
时间复杂度为O(n),空间复杂度为O(1)
此外,如果采用先排序再统计的方法(快排),时间复杂度为O(log2n),先排序再统计,可以在排序后只扫描一次数组即可得出答案,因为每次遇到新的元素,count只需重置即可,若重置前count大于了n / 2,即可直接得到主元素,无需继续遍历。
算法题只要解答正确,也能拿分,就算此题采用了O(n2)时间复杂度的算法,也能拿个七八分,所以统考无需花费大量时间在优化算法上,容易得不偿失。
(4)给定一个含 n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算 法,找出数组中未出现的最小正整数。例如,数组{-5, 3, 2, 3}中未出现的最小正整数是 1;数组{1, 2, 3}中未出现的最小正整数是 4。要求:(【2018统考真题】)
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1)
时间上尽量高效,但未对空间做要求,可以定义一个动态分配存储的数组,直接映射原数组中的值,即新数组的下标对应原数组的值,原数组该值存在,则新数组中此处的值大于0,然后遍历新数组得到第一个值等于0的元素的下标即可。
2)代码如下
int findMissMin(int A[], int n){ int i, *B; B = (int*)malloc(sizeof(int) *n); memset(B, 0, sizeof(int)*n); for(i = 0; i < n; i++){ if(A[i] > 0 && A[i] < n){ B[A[i]-1] = 1; } } for(i = 0; i < n; i++){ if(B[i] == 0) break; } return i+1; }
3)
时间复杂度为O(n),空间复杂度为O(n)
(5)定义三元组(a, b, c)(其中a, b, c均为正数)的距离D=|a-b| + |b-c| + |c-a|。给定三个非空整数集合S1、S2和S3,按升序分别存储在3个数组中。设计一个尽可能高效的算法,计算并输出所有可能的三元组(a, b, c)(a∈S1, b∈S2, c∈S3)中的最小距离。例如S1 = {-1,0,9}, S2 = {-25,-10,10,11},S3 = {2,9,17,30,41},则最小距离为2,相应的三元组为(9,10,9)。要求:(【2020统考真题】)
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1)
观察题目易得当a = b = c时,距离最小
其余情况,假设a < b < c,不失一般性,如下图所示
L1 = |a - b|
L2 = |b - c|
L3 = |c - a|
D = L1 + L2 + L3 = 2L3,其余情况与之类似,即问题的关键在于最大值和最小值之间的距离,故每次固定c找一个a,或固定a找一个c,使L3 = |c - a|最小即可
故基本思想为:
① 适应Dmin记录最小距离
② S1,S2,S3的下标分别为i,j,k,循环遍历三个数组,执行如下操作,直至遍历结束
a)计算A[i]、B[j]、C[k]的距离D,并与Dmin比较
b)将A[i]、B[j]、C[k]中最小值的下标加1
③ 输出Dmin
2)代码如下
#define INT_MAX 0x7ffffff int abs(int a){ return a < 0 ? -a : a; } bool xls_min(int a, int b, int c){ //判断a是否为a,b,c中的最小值 if(a <= b && a <= c) return true; return false; } int findMinofTrip(int A[], int n, int B[], int m, int C[], int p){ //n,m,p分别为数组长度 int i = 0. j = 0, k = 0, D_Min = INT_MAX; while(i < n && j < m && k < p && D_min > 0) { D = abs(A[i] - B[j]) + abs(A[i] - C[k]) + abs(B[j] - C[k]); //计算距离 if(D < D_min) D_min = D; if(xls_min(A[i], B[j], C[k])) i++; //如果a最小 else if(xls_min(B[j], A[i], C[k])) j++; //如果b最小 else k++; //如果c最小 } return D_min; }
3)
时间复杂度为O(n),空间复杂度为O(1)