【良师408】计算机考研408真题解析(2023-01 深入解析顺序表操作的时间复杂度)
传播知识,做懂学生的好老师
1.【哔哩哔哩】(良师408)
2.【抖音】(良师408) goodteacher408
3.【小红书】(良师408)
4.【CSDN】(良师408) goodteacher408
5.【微信】(良师408) goodteacher408
特别提醒:【良师408】所收录真题根据考生回忆整理,命题版权归属教育部考试中心所有
摘要:本文针对2023年计算机考研408数据结构真题中关于顺序表操作时间复杂度的考查,详细分析了顺序表在查找、插入、删除及获取元素等操作上的平均时间复杂度,并通过C语言代码示例验证了其O(1)特性。旨在帮助读者深入理解顺序表的底层机制与性能特点,为数据结构学习与实际编程提供参考。
在计算机科学与技术考研408综合科目中,数据结构是核心内容之一。时间复杂度分析作为衡量算法效率的重要指标,更是历年考查的重点。2023年的一道真题(2023-01)便直指顺序表操作的平均时间复杂度,引发了广泛讨论。
题目原文:
【2023-01】 下列对顺序存储的有序表(长度为n)实现给定操作的算法中,平均时间复杂度为O(1)的是( )。
A. 查找包含指定值元素的算法
B. 插入包含指定值元素的算法
C. 删除第i(1≤i≤n)个元素的算法
D. 获取第i(1≤i≤n)个元素的算法
顺序表(Sequential List)是线性表的一种重要实现方式,它将数据元素存储在一组地址连续的存储单元中,通常采用数组来实现。这种存储方式赋予了顺序表一个显著的特性:随机访问。
时间复杂度是衡量算法执行效率的重要指标,它描述了算法执行时间随输入规模增长的趋势。常见的复杂度包括:
对于查找包含指定值元素的算法,其时间复杂度取决于表的有序性及查找策略。
因此,选项A的平均时间复杂度并非O(1)。
插入包含指定值元素的算法通常包含两个步骤:
n/2
个元素,此步骤的时间复杂度为 O(n)。综合来看,插入操作的平均时间复杂度为 O(n)。
删除第i个元素的算法也涉及元素移动:
i
直接定位到待删除元素,此步骤为 O(1)。i+1
个元素及之后的所有元素向前移动一个位置,填补空缺。平均情况下,需要移动大约 (n-i)/2
个元素,此步骤的时间复杂度为 O(n)。因此,删除操作的平均时间复杂度同样为 O(n)。
获取第i个元素的算法是顺序表最核心的优势体现。
由于顺序表是基于数组实现的,其元素在内存中是连续存放的。这意味着我们可以通过基地址和偏移量直接计算出任意元素的存储地址,从而在不进行任何遍历或移动的情况下,直接访问到目标元素。
公式:元素地址 = 基地址 + (i-1) × 元素大小
这个操作的执行时间与顺序表的长度 n
无关,始终是一个固定的常数时间。因此,获取第 i
个元素的平均时间复杂度为 O(1)。
结论:根据上述分析,只有选项D的操作平均时间复杂度为O(1)。
为了更直观地理解顺序表O(1)的特性,我们提供一个简单的C语言实现,并进行测试。
#include
#include
// 顺序表结构定义
typedef struct {
int *data; // 存储数据的数组
int length; // 当前长度
int maxSize; // 最大容量
} SqList;
// 初始化顺序表
void InitList(SqList *L, int size) {
L->data = (int*)malloc(size * sizeof(int));
L->length = 0;
L->maxSize = size;
}
// D. 获取第i个元素 - O(1)
int GetElem(SqList L, int i, int *e) {
// 判断i是否合法
if (i < 1 || i > L.length) {
printf("位置非法:%d\n", i);
return 0;
}
// 直接访问第i个元素 - O(1)时间复杂度
*e = L.data[i-1]; // 下标从0开始,第i个元素的下标为i-1
return 1;
}
// A. 查找指定值元素 - O(log n)
int BinarySearch(SqList L, int key) {
int low = 0, high = L.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (L.data[mid] == key) {
return mid + 1; // 返回位置(从1开始)
} else if (L.data[mid] < key) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return 0; // 未找到
}
// B. 插入指定值元素 - O(n)
int InsertElem(SqList *L, int value) {
// 判断是否已满
if (L->length >= L->maxSize) {
printf("顺序表已满\n");
return 0;
}
// 查找插入位置
int i = 0;
while (i < L->length && L->data[i] < value) {
i++;
}
// 移动元素,为插入腾出空间
for (int j = L->length; j > i; j--) {
L->data[j] = L->data[j-1];
}
// 插入元素
L->data[i] = value;
L->length++;
return 1;
}
// C. 删除第i个元素 - O(n)
int DeleteElem(SqList *L, int i) {
// 判断i是否合法
if (i < 1 || i > L->length) {
printf("位置非法:%d\n", i);
return 0;
}
// 移动元素
for (int j = i; j < L->length; j++) {
L->data[j-1] = L->data[j];
}
L->length--;
return 1;
}
// 打印顺序表内容
void PrintList(SqList L) {
printf("顺序表内容:");
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]);
}
printf("\n");
}
// 测试函数
void TestOperations() {
printf("=== 顺序表操作时间复杂度测试 ===\n\n");
// 初始化顺序表
SqList L;
InitList(&L, 100);
// 插入有序数据
int testData[] = {1, 3, 5, 7, 9};
for (int i = 0; i < 5; i++) {
L.data[i] = testData[i];
L.length++;
}
PrintList(L);
// 测试D:获取操作 - O(1)
printf("\n测试D:获取第3个元素(O(1)操作)\n");
int e;
if (GetElem(L, 3, &e)) {
printf("第3个元素值为:%d\n", e);
}
// 测试A:查找操作 - O(log n)
printf("\n测试A:查找值为5的元素(O(log n)操作)\n");
int pos = BinarySearch(L, 5);
if (pos) {
printf("值为5的元素在第%d个位置\n", pos);
} else {
printf("未找到值为5的元素\n");
}
// 测试B:插入操作 - O(n)
printf("\n测试B:插入值为4的元素(O(n)操作)\n
");
InsertElem(&L, 4);
PrintList(L);
// 测试C:删除操作 - O(n)
printf("\n测试C:删除第3个元素(O(n)操作)\n");
DeleteElem(&L, 3);
PrintList(L);
// 释放内存
free(L.data);
}
int main() {
TestOperations();
return 0;
}
本题不仅考查了对顺序表操作时间复杂度的理解,更深层次地揭示了数据结构选择的核心原则:
[1] 严蔚敏. 数据结构(C语言版)[M]. 北京:清华大学出版社,2024.
[2] 王道考研. 2024年计算机考研数据结构考点解析[M]. 北京:电子工业出版社,2023.