hyx-ahpu

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. 头插法

算法思路:

  1. 从一个空表开始,重复读入数据
  2. 生成新的结点,将读入的数据存放到新结点的数据域中
  3. 从最后一个结点开始,依次将各节点插入到链表前端
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. 尾插法

算法思路:

  1. 从一个空表L开始,将新结点逐个插入到链表尾部,尾指针r指向链表的尾结点
  2. 初始时,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;
    }
}

posted on 2025-12-22 21:43  后宇翔  阅读(0)  评论(0)    收藏  举报

导航