数组和广义表的对比
一、核心概念对比
1. 基本定义
// 数组(固定大小,同类型) int array[5] = {1, 2, 3, 4, 5}; // 5个整数的连续存储 // 广义表(递归结构,可嵌套) typedef struct GLNode { int tag; // 0:原子,1:表 union { int atom; struct { struct GLNode *head, *tail; } ptr; }; } GLNode; // 广义表示例:(1, (2, 3), 4) GLNode* glist = ...; // 复杂的链式结构
2. 内存结构对比
数组的内存布局
内存地址: 1000 1004 1008 1012 1016 数组元素: arr[0] arr[1] arr[2] arr[3] arr[4] 值: 1 2 3 4 5 ↓ ↓ ↓ ↓ ↓ 连续存储,固定间隔(sizeof(int)=4)
广义表的内存布局
广义表:(1, (2, 3), 4) 内存结构: ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ tag=1│─head→│tag=0│─head→│tag=1│─head→│tag=0│ ├─────┤ ├─────┤ ├─────┤ ├─────┤ │head─┼─┐ │atom=2│ │head─┼─┐ │atom=4│ │tail─┼─┼─┐ │ │ │tail─┼─┼─┐ │ │ └─────┘ │ │ └─────┘ └─────┘ │ │ └─────┘ ↓ ↓ ↓ ↓ 原子1 子表(2,3) 原子4
二、详细特性对比表
| 特性维度 | 数组 | 广义表 |
|---|---|---|
| 数据元素 | 必须同类型 | 可以是不同类型(通过tag区分) |
| 结构形式 | 线性结构 | 非线性结构(树状) |
| 存储方式 | 顺序存储(连续) | 链式存储(非连续) |
| 维度限制 | 固定维度(一维/多维) | 无维度限制,可无限嵌套 |
| 大小 | 固定大小(编译时确定) | 动态大小(运行时可变) |
| 访问方式 | 随机访问(O(1)) | 顺序访问(O(n)) |
| 内存效率 | 高(无额外开销) | 低(指针开销+结构标记) |
| 插入/删除 | O(n)(需要移动元素) | O(1)(修改指针) |
| 查找效率 | O(1)(已知下标) | O(n)(需要遍历) |
三、访问方式对比详解
1. 数组:随机访问
数组访问:直接计算地址 int array[100]; array[50] = 123; // 直接访问第51个元素 内存地址计算: array[i]的地址 = 基地址 + i × 元素大小 这是一个常量时间操作:O(1)
2. 广义表:顺序访问
// 广义表访问:需要遍历 int getNthElement(GList L, int n) { GLNode *p = L; int count = 0; while (p && count < n) { if (p->tag == LIST) { p = p->ptr.tail; // 必须沿着指针走 count++; } else { break; } } if (p && p->ptr.head && p->ptr.head->tag == ATOM) { return p->ptr.head->atom; } return -1; // 访问失败 } // 访问第n个元素需要遍历前n-1个元素:O(n)
四、存储效率对比分析
1. 数组存储开销
假设存储1000个int int dense_array[1000]; 内存使用:1000 × 4字节 = 4000字节 额外开销:0字节 空间利用率:100%(假设全用)
2. 广义表存储开销
假设存储相同的1000个int作为原子 每个节点需要: struct GLNode { int tag; // 4字节 union { int atom; // 4字节 struct { GLNode *head; // 8字节(64位) GLNode *tail; // 8字节 } ptr; }; }; // 至少12-16字节 内存使用:1000 × 16字节 ≈ 16000字节 额外开销:12000字节(指针和tag) 空间利用率:25%(仅原子值有用)
五、操作复杂度对比
数组操作复杂度
int array[N];
// 1. 访问元素
array[i] = x; // O(1)
// 2. 查找元素
for (i = 0; i < N; i++) // O(n)
if (array[i] == x) break;
// 3. 插入元素(中间)
for (i = N-1; i >= pos; i--) // O(n)
array[i+1] = array[i];
array[pos] = new_value;
// 4. 删除元素(中间)
for (i = pos; i < N-1; i++) // O(n)
array[i] = array[i+1];
广义表操作复杂度
GList list;
// 1. 访问第n个元素
getNthElement(list, n); // O(n) - 需要遍历
// 2. 查找元素
findElement(list, x); // O(n) - 递归遍历
// 3. 在头部插入
newList = createList(newElement, list); // O(1)
// 4. 在尾部插入
appendElement(list, newElement); // O(n) - 需要找到尾部
// 5. 复制操作
copyList(list); // O(n) - 递归复制所有节点
六、内存管理对比
数组内存管理
// 1. 栈上分配(自动管理)
int localArray[100]; // 函数结束时自动释放
// 2. 堆上分配(手动管理)
int *dynamicArray = malloc(100 * sizeof(int));
// 使用...
free(dynamicArray); // 必须手动释放
// 特点:一次性分配/释放,管理简单
广义表内存管理
// 1. 节点逐个分配
GLNode* createAtom(int value) {
GLNode *node = malloc(sizeof(GLNode));
node->tag = ATOM;
node->atom = value;
return node;
}
// 2. 递归释放
void destroyGList(GList L) {
if (!L) return;
if (L->tag == LIST) {
destroyGList(L->ptr.head);
destroyGList(L->ptr.tail);
}
free(L);
}
// 特点:分散分配,需要递归释放,容易内存泄漏