Linux环境下的C语言编程(五十二)

数组和广义表的对比

一、核心概念对比

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);
}

// 特点:分散分配,需要递归释放,容易内存泄漏
相关推荐
天才奇男子1 小时前
HAProxy高级功能全解析
linux·运维·服务器·微服务·云原生
qq_297574671 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
老毛肚1 小时前
MyBatis插件原理及Spring集成
java·spring·mybatis
学嵌入式的小杨同学1 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
lang201509282 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
Re.不晚2 小时前
Java入门17——异常
java·开发语言
酥暮沐2 小时前
iscsi部署网络存储
linux·网络·存储·iscsi
缘空如是2 小时前
基础工具包之JSON 工厂类
java·json·json切换
精彩极了吧2 小时前
C语言基本语法-自定义类型:结构体&联合体&枚举
c语言·开发语言·枚举·结构体·内存对齐·位段·联合
❀͜͡傀儡师2 小时前
centos 7部署dns服务器
linux·服务器·centos·dns