【硬件-笔试面试题-105】硬件/电子工程师,笔试面试题(知识点:详细讲讲什么是链表和数组)
题目汇总版--链接:
【硬件-笔试面试题】硬件/电子工程师,笔试面试题汇总版,持续更新学习,加油!!!-CSDN博客
【硬件-笔试面试题-105】硬件/电子工程师,笔试面试题(知识点:详细讲讲什么是链表和数组)
1、题目
详细讲讲什么是链表和数组
2、知识点
链表和数组是两种最基础的数据结构,用于存储和组织一系列相同类型的数据,但它们的底层实现、特性和适用场景有显著区别。以下从定义、结构、操作、区别和适用场景等方面详细讲解:
一、数组(Array)
定义
数组是一种连续存储的线性数据结构,它在内存中占据一块连续的存储空间,所有元素按顺序排列,通过索引(下标)可以直接访问任意元素。
内存结构
- 数组的元素在内存中是物理地址连续的,例如一个包含 5 个整数的数组
int arr[5]
,在内存中可能占据地址0x1000~0x1014
(假设 int 占 4 字节),每个元素的地址间隔固定(等于元素类型大小)。 - 访问元素时,通过公式
地址 = 首地址 + 索引 × 元素大小
计算目标元素地址,实现随机访问。
核心特性
- 固定大小:数组在创建时必须指定长度(静态数组),或动态分配后长度固定(动态数组),无法直接扩容(需手动创建新数组并复制元素)。
- 随机访问:通过索引
arr[i]
访问元素,时间复杂度为O(1)
。 - 连续存储:元素物理地址连续,缓存利用率高(局部性原理)。
基本操作(以 C 语言为例)
c
运行
// 静态数组(编译时确定大小)
int static_arr[5] = {1, 2, 3, 4, 5};// 动态数组(运行时分配大小)
int* dynamic_arr = (int*)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {dynamic_arr[i] = i + 1; // 赋值
}// 访问元素(O(1))
printf("%d\n", static_arr[2]); // 输出3// 插入元素(需移动后续元素,O(n))
// 在index=2处插入6:[1,2,6,3,4,5](需先扩容)// 删除元素(需移动后续元素,O(n))
// 删除index=2的元素:[1,2,4,5](后续元素前移)free(dynamic_arr); // 释放动态数组内存
二、链表(Linked List)
定义
链表是一种非连续存储的线性数据结构,由多个节点(Node)组成,每个节点包含数据域(存储元素)和指针域(存储下一个 / 上一个节点的地址),通过指针将分散的节点串联成逻辑序列。
内存结构
- 链表的节点在内存中是物理地址分散的,节点之间通过指针关联(逻辑上连续,物理上不连续)。
- 例如单链表的节点结构:
[data | next指针] → [data | next指针] → ...
,每个节点的next
指针指向后续节点的地址。
核心类型
- 单链表:每个节点只有
next
指针(指向后一个节点),尾节点next
为NULL
。 - 双向链表:每个节点有
prev
(指向前一个)和next
(指向后一个)指针,支持双向遍历。 - 循环链表:尾节点
next
指向头节点,形成闭合环,适合循环访问场景。
核心特性
- 动态大小:无需预先指定长度,可通过添加 / 删除节点动态调整,内存利用率高。
- 顺序访问:只能从表头(或表尾)依次遍历,无法随机访问,访问第
i
个元素需O(n)
时间。 - 非连续存储:节点物理地址分散,指针域额外占用内存(如单链表每个节点多 4 字节指针)。
基本操作(C 语言单链表示例)
c
运行
// 定义单链表节点
struct Node {int data; // 数据域struct Node* next; // 指针域(指向下一个节点)
};// 创建新节点
struct Node* createNode(int data) {struct Node* node = (struct Node*)malloc(sizeof(struct Node));node->data = data;node->next = NULL;return node;
}// 插入节点(头部插入,O(1))
void insertAtHead(struct Node** head, int data) {struct Node* newNode = createNode(data);newNode->next = *head; // 新节点指向原头节点*head = newNode; // 头指针指向新节点
}// 访问第k个元素(需遍历,O(n))
int getKthElement(struct Node* head, int k) {struct Node* p = head;for (int i = 0; i < k && p != NULL; i++) {p = p->next;}return p ? p->data : -1; // 假设-1表示越界
}// 删除节点(已知前驱节点时O(1))
void deleteNode(struct Node* prev) {struct Node* toDelete = prev->next;prev->next = toDelete->next; // 跳过待删节点free(toDelete);
}
三、数组与链表的核心区别
对比维度 | 数组(Array) | 链表(Linked List) |
---|---|---|
存储方式 | 内存连续,物理地址相邻 | 内存分散,通过指针串联逻辑顺序 |
访问效率 | 随机访问(arr[i] ),O(1) | 顺序访问(遍历),O(n) |
插入 / 删除效率 | 需移动大量元素(O(n) ),头部 / 中间操作慢 | 仅需调整指针(O(1) ,已知前驱时),效率高 |
内存开销 | 无额外开销,但可能浪费空间(预分配) | 需存储指针,额外开销(如单链表多 4 字节 / 节点) |
大小灵活性 | 固定大小,扩容需复制元素(O(n) ) | 动态大小,增减节点无需整体调整 |
缓存友好性 | 连续存储,缓存命中率高 | 分散存储,缓存利用率低 |
四、适用场景选择
-
优先用数组的场景:
- 需要频繁随机访问(如查找、修改指定索引元素),例如数据库索引、矩阵存储。
- 数据大小固定或可预估,且元素数量少,例如存储用户信息列表、配置参数。
- 对内存开销敏感,不希望额外的指针占用空间。
-
优先用链表的场景:
- 需要频繁插入 / 删除(尤其是中间位置),例如实现链表版队列、栈、哈希表冲突解决。
- 数据大小动态变化,无法预估上限,例如日志记录、实时数据流处理。
- 无需随机访问,只需顺序遍历,例如实现邻接表(图的存储)、多项式表示。
五、总结
- 数组是 “连续存储的线性表”,优势是随机访问快、内存紧凑,劣势是大小固定、插入删除效率低。
- 链表是 “离散存储的线性表”,优势是动态大小、插入删除灵活,劣势是访问效率低、内存开销大。
实际开发中,需根据具体需求(访问方式、增删频率、数据规模)选择合适的数据结构,甚至结合两者的优点(如 Java 的ArrayList
用动态数组实现,LinkedList
用双向链表实现)。
题目汇总--链接:
【硬件-笔试面试题】硬件/电子工程师,笔试面试题汇总版,持续更新学习,加油!!!-CSDN博客