hyx_蓝桥杯C++学习_系列三
链表
1. 常见的链表有单链表和双链表
单链表
| data | next |
|---|
单链表的每个结点由用于存数据的 data 和指向下一个结点的指针 next 构成
typedef struct LNode{
ElemType data; // 单链表的数据域
struct LNode *next; // 单链表的指针域
} LNode, *LinkList; // LinkList为指向LNode的指针类型
有 *LinkList 时,在定义单链表就可以写成 LinkList L;若没有,则只能写成 LNode *L;
双链表
| prev | data | next |
|---|
双链表的每个结点由用于存数据的 data、指向上一个节点的指针 prev 以及指向下一个节点的指针 next 构成
typedef struct LNode{
ElemType data;
struct LNode *prev, *next; // 指向上一个节点的指针prev和指向下一个节点的指针next
} DNode, *DLinkList;
2. 链式存储和顺序存储
链式存储是一种非连续的物理存储结构,其中数据元素存储在分散的节点(Node)中,每个节点包含:数据域和指针域。
顺序存储是一种连续的物理存储结构,其中数据元素按逻辑顺序依次存储在一组地址连续的存储单元中。通常通过数组(Array)实现,可以通过基地址 + 偏移量直接计算任何元素的存储位置。
| 操作 | 链式存储 | 顺序存储 | 说明 |
|---|---|---|---|
| 访问第i个元素 | O(n) | O(1) | 链表需要从头遍历 |
| 插入/删除(头部) | O(1) | O(n) | 数组需要移动元素 |
| 插入/删除(中间) | O(n) 查找 + O(1) 操作 | O(n) 移动 | 链表查找慢,操作快 |
| 插入/删除(尾部) | O(n) 或 O(1)(有尾指针) | O(1)(容量足够)或 O(n)(扩容) | |
| 内存利用率 | 较低(有指针开销) | 较高(只有数据) | 链表每个节点都有指针开销 |
| 缓存友好性 | 差(内存不连续) | 好(内存连续) | CPU缓存预取对数组更有利 |
常见的顺序存储的实例就是数组和顺序表。
下面以数组来讲解顺序存储的元素在内存中的定位问题:
对于数组 ElemType a[]
如果想访问第n个元素,可以直接使用下标索引 a[n-1]
这里第n个元素在内存中的地址就是:
address_n = address_a[0] + sizeof(ElemType) * n
这里补充一下数组的定义:
数组的定义有两种方法
a. 静态定义
ElemType data[MAXSIZE];
b. 动态定义
ElemType *data;
data = (ElemType *)malloc(sizeof(ElemType) * MAXSIZE);
这里使用C++中的new操作符书写:
ElemType* data = new ElemType[MAXSIZE];
关于内存分配函数的一些知识
| 函数 | malloc | calloc | realloc |
|---|---|---|---|
| 参数 | size (字节数) | num (元素个数), size (元素大小) | ptr (原指针), new_size (新大小) |
| 是否初始化 | 不初始化(内容是随机的) | 初始化为0 | 保持原有数据(新空间不初始化) |
| 性能 | 较快 | 较慢 | 取决于情况 |
| 常用场景 | 一般内存分配 | 需要清零的数组 | 动态调整数组大小 |
int *arr = (int*)malloc(n * sizeof(int)); // 分配n个整数的空间
int *arr = (int*)calloc(n, sizeof(int)); // 分配并清零一个整数数组
int *arr = (int*)malloc(3 * sizeof(int));
int *temp = (int*)realloc(arr, 5 * sizeof(int)); // 拓展原数组至5个整数的空间
3. 头指针,头结点和首元结点
头指针:指向链表中第一个结点的指针
首元结点:链表中存储第一个数据元素a₁的结点
头结点:在链表的首元结点前附设的一个结点,data域为空
4. 单链表建立的两种方法
a. 头插法
算法思路:
- 从一个空表开始,重复读入数据
- 生成新的结点,将读入的数据存放到新结点的数据域中
- 从最后一个结点开始,依次将各节点插入到链表前端
void CreateList_H(LinkList &L, int n){
L = new LNode;
L -> next = NULL;
for(int i = n; i > 0; i--){
p = new LNode;
cin >> p -> data;
p -> next = L -> next;
L -> next = p;
}
}
b. 尾插法
算法思路:
- 从一个空表L开始,将新结点逐个插入到链表尾部,尾指针r指向链表的尾结点
- 初始时,r同L均指向头结点,每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点
void CreateList_R(LinkList &L, int n){
L = new LNode;
L -> next = NULL;
r = L;
for(int i = 0; i < n; i++){
p = new LNode;
cin >> p -> data;
p -> next = NULL;
r -> next = p;
r = p;
}
}
浙公网安备 33010602011771号