题目出处:王道考研2022年数据结构考研复习指导
本篇博客中所有题目代码均使用C++语言。
1.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删函数的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行。
遍历顺序表,记录最小值元素及其位置,遍历结束后用最后一个元素填补到记录的位置,顺序表长度减1。
bool del_min(SqList& L, ElemType& value) {
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;
}
2.设计一个高效算法,将顺序表 L L L 的所有元素逆置,要求算法的空间复杂度为 O ( 1 ) O(1) O(1)。
遍历顺序表前半部分元素,将其与后半部分对应元素进行交换。
void Reverse(SqList& L) {
ElemType t;
for (int i = 0; i < L.length / 2; i++) {
t = L.data[i];
L.data[i] = L.data[L.length - i - 1];
L.data[L.length - i - 1] = t;
}
}
3.对长度为 n n n 的顺序表 L L L,编写一个时间复杂度为 O ( n ) O(n) O(n)、空间复杂度为 O ( 1 ) O(1) O(1) 的算法,该算法删除线性表中所有值为 x x x 的数据元素。
方法1:边遍历顺序表边记录顺序表中不等于 x x x 的元素个数
cnt
,将其向前移动cnt
个位置,最后修改顺序表长度。
void del_x(SqList& L, Elemtype x) {
int cnt = 0;
for (int i = 0; i < L.length; i++) {
if (L.data[i] != x) {
L.data[cnt++] = L.data[i];
}
}
L.length = cnt;
}
方法2:边遍历顺序表边记录顺序表中等于 x x x 的元素个数
cnt
,将不等于 x x x 的元素向前移动cnt
个位置,最后修改顺序表长度。
void del_x(SqList& L, ElemType x) {
int cnt = 0, i = 0;
while (i < L.length) {
if (L.data[i] == x) {
cnt++;
} else {
L.data[i - cnt] = L.data[i];
}
i++;
}
L.length -= cnt;
}
4.从有序顺序表中删除其值在给定值 s s s 与 t t t 之间(要求 s < t s < t s<t)的所有元素,若 s s s 或 t t t 不合理或顺序表为空,则显示出错信息并退出运行。
寻找值大于或等于 s s s 的第一个元素,然后寻找值大于 t t t 的第一个元素,将这段元素删除。
bool del_s_t(SqList& L, ElemType s, ElemType t) {
if (s >= t || L.length == 0) {
// 显示出错信息
return false;
}
int i, j;
for (i = 0; i < L.length && L.data[i] < s; i++);
if (i >= L.length) {
// 显示出错信息
return false;
}
for (j = i; j < L.length && L.data[j] <= t; j++);
for (; j < L.length; i++, j++) {
L.data[i] = L.data[j];
}
L.length = i;
return true;
}
5.从顺序表中删除其值在给定值 s s s 与 t t t 之间(包含 s s s 和 t t t,要求 s < t s < t s<t)的所有元素,若 s s s 或 t t t 不合理或顺序表为空,则显示出错信息并退出运行。
遍历顺序表,记录元素值在 s s s 与 t t t 之间元素的个数
cnt
,对于当前元素,若其值不在 s s s 与 t t t 之间,则前移cnt
个位置,否则执行cnt++
。
bool del_s_t(SqList& L, ElemType s, ElemType t) {
int cnt = 0;
if (s >= t || L.length == 0) {
// 显示出错信息
return false;
}
for (int i = 0; i < L.length; i++) {
if (L.data[i] >= s && L.data[i] <= t) {
cnt++;
} else {
L.data[i - cnt] = L.data[i];
}
}
L.length -= k;
return true;
}
6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。
初始时将第一个元素视为非重复有序表,依次判断后面的元素是否与前面非重复有序表的最后一个元素相同,若相同则继续向后判断,若不同则插入前面的非重复有序表的最后,直至判断到表尾。
bool del_same(SqList& L) {
if (L.length == 0) {
return false;
}
int 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;
return true;
}
7.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
按顺序不断取两表表头较小的元素存入新的顺序表,上述流程结束后将有剩余元素的那个表中的元素存入新的顺序表。
bool merge_list(SqList A, SqList B, SqList& 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 ] A[m+n] A[m+n] 中依次存放两个线性表 ( a 1 , a 2 , a 3 , ⋯ , a m ) (a_1,a_2,a_3,\cdots,a_m) (a1,a2,a3,⋯,am) 和 ( b 1 , b 2 , b 3 , ⋯ , b n ) (b_1,b_2,b_3,\cdots,b_n) (b1,b2,b3,⋯,bn)。试编写一个函数,将数组中两个顺序表的位置互换,即将 ( b 1 , b 2 , b 3 , ⋯ , b n ) (b_1,b_2,b_3,\cdots,b_n) (b1,b2,b3,⋯,bn) 放在 ( a 1 , a 2 , a 3 , ⋯ , a m ) (a_1,a_2,a_3,\cdots,a_m) (a1,a2,a3,⋯,am) 的前面。
先将数组 A [ m + n ] A[m+n] A[m+n] 中的全部元素逆置,再对前 n n n 个元素和后 m m m 个元素分别逆置。
void Reverse(ElemType arr[], int left, int right, int arrSize) {
if (left >= right || right >= arrSize) {
return;
}
int mid = left + (right - left) / 2;
for (int i = 0; i <= mid - left; i++) {
ElemType t = arr[left + i];
arr[left + i] = arr[right - i];
arr[right - i] = t;
}
}
void exchange(ElemType A[], int m, int n, int ASize) {
Reverse(A, 0, m + n - 1, ASize);
Reverse(A, 0, n - 1, ASize);
Reverse(A, n, m + n - 1, ASize);
}
9.线性表 ( a 1 , a 2 , a 3 , ⋯ , a n ) (a_1,a_2,a_3,\cdots,a_n) (a1,a2,a3,⋯,an) 中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为 x x x 的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素仍递增有序。
折半查找。
void search(ElemType A[], ElemType x, int n) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (A[mid] = x) {
break;
} else if (A[mid] > x) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (A[mid] == x && mid != n - 1) { // 查找成功且不为最后一个元素
ElemType t = A[mid];
A[mid] = A[mid + 1];
A[mid + 1] = t;
}
if (left > right) { // 查找失败
for (int i = n - 1; i > right; i--) {
A[i + 1] = A[i];
}
A[i + 1] = x;
}
}
10.【2010统考真题】设将 n ( n > 1 ) n(n>1) n(n>1) 个整数存放到一维数组 R R R 中。设计一个在时间和空间两方面都尽可能高效的算法。将 R R R 中保存的序列循环左移 p ( 0 < p < n ) p(0<p<n) p(0<p<n) 个位置,即将 R R R 中的数据由 ( X 0 , X 1 , ⋯ , X n − 1 ) (X_0,X_1,\cdots,X_{n-1}) (X0,X1,⋯,Xn−1) 变换为 ( X p , X p + 1 , ⋯ , X n − 1 , X 0 , X 1 , ⋯ , X p − 1 ) (X_p,X_{p+1},\cdots,X_{n-1},X_0,X_1,\cdots,X_{p-1}) (Xp,Xp+1,⋯,Xn−1,X0,X1,⋯,Xp−1)。要求:
1)给出算法的基本设计思想。
先将数组的前 p p p 个元素逆置,再将数组余下的 n − p n-p n−p 个元素逆置,最后再将整个数组逆置。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
void Reverse(int R[], int left, int right) {
for (int i = 0; i < (right - left + 1) / 2; i++) {
int t = R[left + i];
R[left + i] = R[right - i];
R[right - i] = t;
}
}
void solve(int R[], int n, int p) {
Reverse(R, 0, p - 1); // 逆置前p个元素
Reverse(R, p, n - 1); // 逆置余下的n-p个元素
Reverse(R, 0, n - 1); // 将整个数组逆置
}
3)说明你所设计算法的时间复杂度和空间复杂度。
solve
函数中的三个Reverse
函数的时间复杂度分别为 O ( p / 2 ) O(p/2) O(p/2)、 O ( ( n − p ) / 2 ) O((n-p)/2) O((n−p)/2) 和 O ( n / 2 ) O(n/2) O(n/2),所以solve
函数的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 为数组 R R R 的长度, p p p 为循环左移的偏移量。空间复杂度为 O ( 1 ) O(1) O(1),使用常数级空间。
11.【2011统考真题】一个长度为 L ( L ≥ 1 ) L(L \geq 1) L(L≥1) 的升序序列 S S S,处在第 ⌈ L / 2 ⌉ \lceil L/2 \rceil ⌈L/2⌉ 个位置的数称为 S S S 的中位数。例如,若序列 S 1 = ( 11 , 13 , 15 , 17 , 19 ) S_1=(11,13,15,17,19) S1=(11,13,15,17,19),则 S 1 S_1 S1 的中位数是 15 15 15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若 S 2 = ( 2 , 4 , 6 , 8 , 20 ) S_2=(2,4,6,8,20) S2=(2,4,6,8,20),则 S 1 S_1 S1 和 S 2 S_2 S2 的中位数是 11 11 11。现在有两个等长升序序列 A A A 和 B B B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列 A A A 和 B B B 的中位数。要求:
1)给出算法的基本设计思想。
分别求序列 A , B A,B A,B 的中位数 a , b a,b a,b。
①若 a = b a=b a=b,则 a a a(或 b b b)即为所求,算法结束。
②若 a < b a<b a<b,则舍弃序列 A A A 中较小的一半和序列 B B B 中较大的一半。
③若 a > b a>b a>b,则舍弃序列 A A A 中较大的一半和序列 B B B 中较小的一半。
在保留的两个升序序列中,重复上述过程,直到两个序列中均只含一个元素时为止,较小者即为所求。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
int get_median(int A[], int B[], int n) {
int s1 = 0, e1 = n - 1, s2 = 0, e2 = n - 1; // 分别表示A和B的首末位元素
while (s1 != e1 || s2 != e2) {
int m1 = (s1 + e1) / 2; // 序列A的中位数
int m2 = (s2 + e2) / 2; // 序列B的中位数
if (A[m1] == B[m2]) { // 满足条件1
return A[m1];
} else if (A[m1] < B[m2]) { // 满足条件2
if ((s1 + e1) % 2 == 0) { // 元素个数为奇数,舍弃操作保留中间点
s1 = m1;
e2 = m2;
} else { // 元素个数为偶数
s1 = m1 + 1; // 舍弃A中间点及之前部分
e2 = m2; // 舍弃B中间点(不含)之后部分
}
} else { // 满足条件3
if ((s2 + e2) % 2 == 0) { // 元素个数为奇数,舍弃操作保留中间点
e1 = m1;
s2 = m2;
} else { // 元素个数为偶数
e1 = m1; // 舍弃A中间点(不含)之后部分
s2 = m2 + 1; // 舍弃B中间点及之前部分
}
}
}
return min(A[s1], B[s2]); // 返回剩余元素的较小者
}
3)说明你所设计算法的时间复杂度和空间复杂度。
时间复杂度为 O ( log n ) O(\log n) O(logn),其中 n n n 为序列 A A A(或序列 B B B)的长度。空间复杂度为 O ( 1 ) O(1) O(1),使用常数级空间。
12.【2013统考真题】已知一个整数序列 A = ( a 0 , a 1 , ⋯ , a n − 1 ) A=(a_0,a_1,\cdots,a_{n-1}) A=(a0,a1,⋯,an−1),其中 0 ≤ a i < n ( 0 ≤ i < n ) 0 \leq a_i < n(0 \leq i < n) 0≤ai<n(0≤i<n)。若存在 a p 1 = a p 2 = ⋯ = a p m = x a_{p_1}=a_{p_2}=\cdots=a_{p_m}=x ap1=ap2=⋯=apm=x 且 m > n / 2 ( 0 ≤ p k < n , 1 ≤ k ≤ m ) m>n/2(0 \leq p_k<n,1 \leq k \leq m) m>n/2(0≤pk<n,1≤k≤m),则称 x x x 为 A A A 的主元素。例如 A = ( 0 , 5 , 5 , 3 , 5 , 7 , 5 , 5 ) A=(0,5,5,3,5,7,5,5) A=(0,5,5,3,5,7,5,5),则 5 5 5 为主元素;又如 A = ( 0 , 5 , 5 , 3 , 5 , 1 , 5 , 7 ) A=(0,5,5,3,5,1,5,7) A=(0,5,5,3,5,1,5,7),则 A A A 中没有主元素。假设 A A A 中的 n n n 个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出 A A A 的主元素。若存在主元素,则输出该元素;否则输出 − 1 -1 −1。要求:
1)给出算法的基本设计思想。
遍历序列 A A A 中的元素,将第一个整数保存到变量
c
中,记录出现次数cnt = 1
。若遇到的下一个整数仍然等于c
,则计数cnt
加 1 1 1,否则计数减 1 1 1。当计数cnt
减为 0 0 0 时,再将下一个整数保存到变量c
中,计数重新置为 1 1 1,从当前位置起重复上述流程,直至遍历结束,此时得到候选变量c
。判断候选值
c
是否为真正的主元素,第二次扫描序列 A A A,统计序列中等于c
的元素个数,根据题意进行判断。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
int major_elem(int* A, int n) {
int c = A[0], cnt = 1; // c为候选元素,初始值为A[0];cnt为计数变量
for (int i = 1; i < n; i++) { // 第一次遍历
if (A[i] == c) { // 遇到相同元素计数+1
cnt++;
} else { // 遇到不同元素
if (cnt > 0) { // 计数大于0,计数-1
cnt--;
} else { // 计数已减为0,令A[i]为新的候选元素,重置计数为1
c = A[i];
cnt = 1;
}
}
}
int count = 0; // 第二次遍历的计数变量
if (cnt > 0) {
for (int i = 0; i < n; i++) { // 统计候选变量的出现次数
if (A[i] == c) {
count++;
}
}
}
return count > n / 2 ? c : -1; // 根据题意返回结果
}
3)说明你所设计算法的时间复杂度和空间复杂度。
两次遍历的时间复杂度均为 O ( n ) O(n) O(n),因此总的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 为序列 A A A 的长度。空间复杂度为 O ( 1 ) O(1) O(1),使用常数级空间。
13.【2018统考真题】给定一个含 n ( n ≥ 1 ) n(n \geq 1) n(n≥1) 个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如,数组 { − 5 , 3 , 2 , 3 } \{-5,3,2,3\} {−5,3,2,3} 中未出现的最小正整数是 1 1 1;数组 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 中未出现的最小正整数是 4 4 4。要求:
1)给出算法的基本设计思想。
因为数组
arr
中含有 n n n 个整数,因此返回的值一定是 [ 1 , n + 1 ] [1,n+1] [1,n+1] 范围内的正整数。可以申请一个辅助数组t
,长度为 n n n。遍历arr
,对于arr
中小于或等于 0 0 0 或大于 n n n 的元素不采取任何操作,对其他元素,t[i]
表示arr
中值等于 i + 1 i+1 i+1 的元素出现的次数。遍历结束后,遍历辅助数组t
,若能找到第一个满足t[i] == 0
的下标i
,则跳出循环;若不能找到,则 n + 1 n+1 n+1 为结果,此时满足i == n
。两种情况均返回i + 1
即可。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
int find_miss(int* arr, int n) {
int* t = (int*) malloc(sizeof(int) * n); // 申请辅助数组t空间
memset(t, 0, sizeof(int) * n); // 初始化辅助数组为全0
int i;
for (i = 0; i < n; i++) { // 遍历数组arr,计数
if (arr[i] > 0 && arr[i] <= n) {
t[arr[i] - 1] = 1;
}
}
for (i = 0; i < n; i++) { // 寻找第一个满足条件的下标
if (t[i] == 0) { // 找到则退出循环
break;
}
}
return i + 1;
}
3)说明你所设计算法的时间复杂度和空间复杂度。
遍历
arr
和遍历t
的时间复杂度均为 O ( n ) O(n) O(n),所以总的时间复杂度为 O ( n ) O(n) O(n)。使用了辅助数组t
,空间复杂度为 O ( n ) O(n) O(n)。其中 n n n 为数组长度。
14.【2020统考真题】定义三元组 ( a , b , c ) (a,b,c) (a,b,c)( a , b , c a,b,c a,b,c 均为正数)的距离 D = ∣ a − b ∣ + ∣ b − c ∣ + ∣ c − a ∣ D=|a-b|+|b-c|+|c-a| D=∣a−b∣+∣b−c∣+∣c−a∣。给定 3 3 3 个非空整数集合 S 1 S_1 S1、 S 2 S_2 S2 和 S 3 S_3 S3,按升序分别存储在 3 3 3 个数组中。请设计一个尽可能高效的算法,计算并输出所有可能的三元组 ( a , b , c ) ( a ∈ S 1 , b ∈ S 2 , c ∈ S 3 ) (a,b,c)(a \in S_1,b \in S_2,c \in S_3) (a,b,c)(a∈S1,b∈S2,c∈S3) 中的最小距离。例如 S 1 = { − 1 , 0 , 9 } S_1=\{-1,0,9\} S1={−1,0,9}, S 2 = { − 25 , − 10 , 10 , 11 } S_2=\{-25,-10,10,11\} S2={−25,−10,10,11}, S 3 = { 2 , 9 , 17 , 30 , 41 } S_3=\{2,9,17,30,41\} S3={2,9,17,30,41},则最小距离为 2 2 2,相应的三元组为 ( 9 , 10 , 9 ) (9,10,9) (9,10,9)。要求:
1)给出算法的基本设计思想。
根据 D = ∣ a − b ∣ + ∣ b − c ∣ + ∣ c − a ∣ D=|a-b|+|b-c|+|c-a| D=∣a−b∣+∣b−c∣+∣c−a∣ 可以分析出:
①当 a = b = c a=b=c a=b=c 时, D D D 可以取到最小值 0 0 0。
②其他情况下,假设 a ≤ b ≤ c a \leq b \leq c a≤b≤c, L 1 = ∣ a − b ∣ L_1=|a-b| L1=∣a−b∣, L 2 = ∣ b − c ∣ L_2=|b-c| L2=∣b−c∣, L 3 = ∣ c − a ∣ L_3=|c-a| L3=∣c−a∣,则可以得到 D = L 1 + L 2 + L 3 = 2 L 3 D=L_1+L_2+L_3=2L_3 D=L1+L2+L3=2L3。所以,要使 D D D 最小,就需要最小化 L 3 L_3 L3。
使用变量
i
、j
、k
作为三个指针,分别指向三个序列的起始元素,遍历序列,计算S1[i]
、S2[j]
、S3[k]
的距离,并更新结果变量ans
。将S1[i]
、S2[j]
、S3[k]
的最小值的下标加 1 1 1。遍历结束后结果变量ans
即为所求。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
int find_min_D(int* S1, int* S2, int* S3, int S1Size, int S2Size, int S3Size) {
int i = 0, j = 0, k = 0; // 指针变量,分别指向三个序列
int ans = INT_MAX; // 初始化结果变量为INT_MAX
while (i < S1Size && j < S2Size && k < S3Size && ans > 0) {
int d = abs(S1[i] - S2[j]) + abs(S2[j] - S3[k]) + abs(S3[k] - S1[i]);
ans = min(d, ans); // 更新ans为较小值
if (S1[i] <= S2[j] && S1[i] <= S3[k]) { // 将最小元素的下标+1
i++;
} else if (S2[j] <= S1[i] && S2[j] <= S3[k]) {
j++;
} else {
k++;
}
}
return ans;
}
3)说明你所设计算法的时间复杂度和空间复杂度。
时间复杂度为 O ( n ) O(n) O(n),其中 n n n 为 3 3 3 个数组的长度之和。空间复杂度为 O ( 1 ) O(1) O(1),使用常数级空间。