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

// 特点:分散分配,需要递归释放,容易内存泄漏
相关推荐
小小ken4 小时前
ubuntu添加新网卡时,无法自动获取IP原因及解决办法
linux·网络·tcp/ip·ubuntu·dhcp
BD_Marathon4 小时前
MyBatis各种查询功能
java·开发语言·mybatis
雪人.4 小时前
Spring常见面试题(2026版30道面试题)
java·后端·spring
温暖小土4 小时前
深入理解 Spring Boot 配置加载顺序:外部化配置的艺术
java·springboot
小林rr5 小时前
深入探索 C++:现代特性、工程实践与性能优化全解
java·c++·性能优化
Xの哲學5 小时前
Linux 软中断深度剖析: 从设计思想到实战调试
linux·网络·算法·架构·边缘计算
林鸿风采5 小时前
在Alpine Linux上部署docker,并配置开机自启
linux·docker·eureka·alpine
专注数据的痴汉5 小时前
「数据获取」全国民用运输机场吞吐量排名(2006-2024)
java·大数据·服务器·数据库·信息可视化
悟空码字5 小时前
无缝集成指南,SpringBoot三步接入华为云短信服务
java·springboot·编程技术·后端开发·华为云短信
l1t5 小时前
在arm64 Linux系统上编译tdoku-lib的问题和解决
linux·运维·服务器·c语言·cmake