《线性表、顺序表与链表》教案(C语言版本)

🌟 各位看官好,我是 maomi_9526

🌍 种一棵树最好是十年前,其次是现在!

🚀 今天来学习C语言的相关知识。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦

目录

教学目标

教学重点与难点

教学方法

教学大纲

[1. 线性表(Linear List)](#1. 线性表(Linear List))

[1. 线性表的定义](#1. 线性表的定义)

[2. 线性表的基本特性](#2. 线性表的基本特性)

[3. 线性表的常见实现方式](#3. 线性表的常见实现方式)

[3.1 顺序存储(顺序表)](#3.1 顺序存储(顺序表))

[3.2 链式存储(链表)](#3.2 链式存储(链表))

[4. 顺序表与链表的比较](#4. 顺序表与链表的比较)

[5. 顺序表操作](#5. 顺序表操作)

6.链表基本操作

[2. 顺序表(Sequence Table)](#2. 顺序表(Sequence Table))

[2.1 静态顺序表与动态顺序表](#2.1 静态顺序表与动态顺序表)

[3. 顺序表 vs 链表](#3. 顺序表 vs 链表)

对比分析

课堂练习

作业设计

教学总结


教学目标
  1. 理解线性表的逻辑结构和物理实现方式。

  2. 掌握顺序表和链表的实现原理及核心操作。

  3. 能够通过代码实现顺序表和链表的基本功能。

  4. 分析顺序表与链表的性能差异及适用场景。


教学重点与难点
  • 重点:顺序表的动态增容、链表的指针操作、时间复杂度分析。

  • 难点:链表节点的插入与删除逻辑、顺序表与链表的性能权衡。


教学方法
  • 理论讲解 + 代码演示 + 对比分析 + 课堂练习

教学大纲


1. 线性表(Linear List)

1. 线性表的定义
  • 线性表 :线性表是由n个具有相同特性的元素组成的有限序列,元素之间有明确的前后关系。每个元素有唯一的前驱和后继元素。线性表可以是顺序存储链式存储

    • 逻辑结构:线性表是逻辑上的顺序排列,指的是元素之间的关系是依次排列的,每个元素都有一个明确的前后关系。

    • 物理结构:线性表的元素在计算机内存中的存储方式,可以是连续的(顺序表)或者不连续的(链表)。

2. 线性表的基本特性
  • 元素的顺序性:线性表中元素的顺序关系决定了元素之间的前后依赖关系,通常我们按从第一个元素到最后一个元素的顺序进行访问。

  • 唯一性:每个元素在序列中有唯一的前驱和后继元素,只有第一个元素没有前驱,最后一个元素没有后继。

  • 有限性:线性表包含一个有限的元素集合,且元素数量固定。


3. 线性表的常见实现方式

线性表有两种常见的物理存储方式:顺序存储链式存储

3.1 顺序存储(顺序表)

顺序存储使用一段连续的内存空间存储数据,通常使用数组来实现。数据元素在内存中的地址是连续的,因此可以通过索引来直接访问。

  • 优点

    • 高效的随机访问:可以通过索引直接访问任意位置的元素,时间复杂度为 O(1)。

    • 空间效率高:在内存中是连续存储,相对来说能够更高效地利用内存。

  • 缺点

    • 插入和删除效率较低:在顺序表中间插入或删除元素时,需要移动大量的元素,时间复杂度为 O(N)。

    • 固定容量问题:顺序表的容量一旦固定,后期无法动态调整空间,需要使用动态扩容,但扩容可能导致空间浪费。

3.2 链式存储(链表)

链表使用非连续的内存空间存储数据,每个元素包含一个数据域和一个指向下一个元素的指针(或者同时包含指向前后节点的指针),因此数据元素的地址不一定是连续的。

  • 优点

    • 插入和删除效率高:链表只需要调整指针即可完成插入和删除操作,时间复杂度为 O(1)(不需要移动元素)。

    • 动态内存分配:链表不需要预先定义大小,内存空间可以根据实际需求动态分配,避免了空间浪费问题。

  • 缺点

    • 随机访问效率低:访问链表中的元素需要从头节点开始依次遍历,时间复杂度为 O(N)。

    • 指针开销:每个节点需要额外的指针空间存储前驱或后继元素。


4. 顺序表与链表的比较

顺序表和链表是两种常见的线性表实现方式,它们的优缺点适用于不同的应用场景。

特性 顺序表 链表
存储方式 使用连续的内存块(数组)存储元素 使用不连续的内存块(通过指针链接)
随机访问 O(1) O(N)
插入/删除操作 O(N)(需要移动元素) O(1)(只需修改指针)
扩容/缩容 动态扩容(可能浪费空间) 不需要扩容,按需分配空间
内存空间使用 存储空间固定,可能造成浪费 每个节点动态分配内存
应用场景 适合频繁访问的场景 适合频繁插入和删除的场景
内存开销 低,只有存储元素的数据 较高,每个节点还需要存储指针

5. 顺序表操作
  • 初始化:创建一个顺序表,设定初始容量。

  • 插入:将一个元素插入到指定位置,涉及移动元素。

  • 删除:删除指定位置的元素,涉及移动元素。

  • 查找:根据索引访问元素。

  • 扩容:当顺序表的空间不足时,需要扩容并将现有数据复制到新数组中。

代码示例(插入与删除):

复制代码
// 在位置index插入元素
void insert(DynamicArray* arr, int index, int value) {
    if (index < 0 || index > arr->size) {
        printf("Index out of bounds\n");
        return;
    }
    if (arr->size == arr->capacity) {
        resize(arr, 2 * arr->capacity);
    }
    // 元素后移
    for (int i = arr->size; i > index; i--) {
        arr->array[i] = arr->array[i - 1];
    }
    arr->array[index] = value;
    arr->size++;
}

// 删除位置index的元素
void delete(DynamicArray* arr, int index) {
    if (index < 0 || index >= arr->size) {
        printf("Index out of bounds\n");
        return;
    }
    // 元素前移
    for (int i = index; i < arr->size - 1; i++) {
        arr->array[i] = arr->array[i + 1];
    }
    arr->size--;
}
6.链表基本操作
  • 初始化:创建一个空链表,通常使用虚拟头节点(dummy node)来简化操作。

  • 插入:在指定位置插入一个元素,修改指针链接。

  • 删除:删除指定位置的元素,修改指针链接。

  • 查找:遍历链表查找指定元素。

  • 反转:将链表的元素顺序反转,调整指针的指向。

代码示例(单向链表):

复制代码
#include <stdio.h>
#include <stdlib.h>

struct ListNode {
    int val;
    struct ListNode* next;
};

// 创建链表的头插法
struct ListNode* create_linked_list_head(int* values, int size) {
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = NULL;
    for (int i = 0; i < size; i++) {
        struct ListNode* new_node = (struct ListNode*)malloc(sizeof(struct ListNode));
        new_node->val = values[i];
        new_node->next = dummy->next;
        dummy->next = new_node;
    }
    return dummy->next;
}

// 创建链表的尾插法
struct ListNode* create_linked_list_tail(int* values, int size) {
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail = dummy;
    for (int i = 0; i < size; i++) {
        struct ListNode* new_node = (struct ListNode*)malloc(sizeof(struct ListNode));
        new_node->val = values[i];
        tail->next = new_node;
        tail = tail->next;
    }
    return dummy->next;
}

void print_linked_list(struct ListNode* head) {
    struct ListNode* current = head;
    while (current != NULL) {
        printf("%d ", current->val);
        current = current->next;
    }
    printf("\n");
}

int main() {
    int values[] = {1, 2, 3};
    struct ListNode* head = create_linked_list_tail(values, 3);
    print_linked_list(head);  // 输出:1 2 3
    return 0;
}

代码示例(反转链表、删除节点):

复制代码
// 反转链表
struct ListNode* reverse_linked_list(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    while (curr != NULL) {
        struct ListNode* next_node = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next_node;
    }
    return prev;
}

// 删除链表中所有值为val的节点
struct ListNode* remove_elements(struct ListNode* head, int val) {
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode* current = dummy;
    while (current->next != NULL) {
        if (current->next->val == val) {
            struct ListNode* temp = current->next;
            current->next = temp->next;
            free(temp);
        } else {
            current = current->next;
        }
    }
    return dummy->next;
}

6.代码示例(C语言):

复制代码
#include <stdio.h>

#define MAX_SIZE 10

// 顺序表(数组实现)
int linear_list[MAX_SIZE] = {1, 2, 3, 4, 5};

// 链表(链式存储)
struct Node {
    int data;
    struct Node* next;
};

void print_linear_list() {
    for (int i = 0; i < 5; i++) {
        printf("%d ", linear_list[i]);
    }
    printf("\n");
}

void print_linked_list(struct Node* head) {
    struct Node* current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

int main() {
    print_linear_list();  // 输出线性表
    return 0;
}

2. 顺序表(Sequence Table)

2.1 静态顺序表与动态顺序表
  1. 静态顺序表(Static Sequence Table)

    • 定义:静态顺序表是使用固定大小的数组来存储数据元素。其大小在编译时就已经确定,运行时不能更改。

    • 特点

      • 大小固定,一旦定义了数组的大小,就无法动态调整。

      • 存储元素的内存地址在编译时就被分配好了,内存空间是连续的。

      • 适合数据量已知并且不会频繁变化的场景。

    • 优点

      • 存储效率高,访问元素的时间复杂度为O(1)。
    • 缺点

      • 固定容量可能会造成内存浪费或空间不足的情况。

      • 插入和删除元素时可能会产生大量的数据搬移,时间复杂度为O(N)。

    示例(C语言)

    复制代码
    #include <stdio.h>
    
    #define MAX_SIZE 10
    
    int static_array[MAX_SIZE] = {1, 2, 3, 4, 5};
    
    void print_static_array() {
        for (int i = 0; i < 5; i++) {
            printf("%d ", static_array[i]);
        }
        printf("\n");
    }
    
    int main() {
        print_static_array();  // 输出:1 2 3 4 5
        return 0;
    }
  2. 动态顺序表(Dynamic Sequence Table)

    • 定义:动态顺序表是一个支持动态扩容的数组。当数组的存储空间不足时,系统会自动分配更大的内存空间,并将原有元素复制到新数组中。

    • 特点

      • 容量是动态可调的,通常根据需要扩展数组的容量(例如扩容为原来的2倍)。

      • 数据的内存地址可能会发生变化,因为重新分配了更大的内存空间。

      • 适合数据量不确定或需要经常变化的场景。

    • 优点

      • 容量可动态增长,不会浪费内存空间。

      • 避免了固定容量数组的空间不足问题。

    • 缺点

      • 扩容操作可能会引起性能下降,尤其是在频繁扩容时,需要进行内存的重新分配和数据的搬移。

    示例(C语言实现动态顺序表)

    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct {
        int* array;     // 动态分配的数组
        int size;       // 当前元素个数
        int capacity;   // 数组容量
    } DynamicArray;
    
    // 初始化动态数组
    void init(DynamicArray* arr) {
        arr->capacity = 2;  // 初始容量为2
        arr->size = 0;
        arr->array = (int*)malloc(arr->capacity * sizeof(int));
    }
    
    // 扩容:将数组容量翻倍
    void resize(DynamicArray* arr, int new_capacity) {
        arr->array = (int*)realloc(arr->array, new_capacity * sizeof(int));
        arr->capacity = new_capacity;
    }
    
    // 向动态数组中添加元素
    void append(DynamicArray* arr, int value) {
        if (arr->size == arr->capacity) {
            // 扩容
            resize(arr, 2 * arr->capacity);
        }
        arr->array[arr->size] = value;
        arr->size++;
    }
    
    // 打印数组内容
    void print_array(DynamicArray* arr) {
        for (int i = 0; i < arr->size; i++) {
            printf("%d ", arr->array[i]);
        }
        printf("\n");
    }
    
    // 释放动态数组内存
    void free_array(DynamicArray* arr) {
        free(arr->array);
    }
    
    int main() {
        DynamicArray arr;
        init(&arr);
    
        // 添加元素
        append(&arr, 1);
        append(&arr, 2);
        append(&arr, 3);  // 扩容时容量变为4
        append(&arr, 4);
    
        printf("动态顺序表的内容:\n");
        print_array(&arr);  // 输出:1 2 3 4
    
        free_array(&arr);   // 释放内存
        return 0;
    }
  • 静态顺序表:内存空间是固定的,适用于数据量确定的情况,访问效率高,但插入和删除操作可能需要大量的数据搬移,且扩容困难。

  • 动态顺序表:内存空间是可扩展的,适用于数据量不确定或变化较大的情况,动态扩容避免了固定容量带来的问题,但在扩容时可能会影响性能。


3. 顺序表 vs 链表

对比分析
特性 顺序表 链表
存储方式 连续内存 离散内存,通过指针链接
随机访问 O(1) O(N)
插入/删除 O(N)(需移动元素) O(1)(只需调整指针)
空间管理 动态扩容可能浪费空间 按需分配,无空间浪费
缓存局部性 高(连续存储) 低(节点分散)

课堂练习
  1. 顺序表练习:实现一个函数,删除顺序表中所有等于给定值的元素。

  2. 链表练习:合并两个有序链表,返回新的有序链表头节点。


作业设计

  1. 编码题

    • 实现动态顺序表的缩容功能(当元素数量小于容量的1/4时,容量减半)。

    • 实现双向链表的插入和删除操作。

  2. 分析题

    • 分析顺序表动态扩容均摊时间复杂度为何是O(1)。

    • 对比单向链表和双向链表在删除尾节点时的时间复杂度差异。


教学总结

通过代码实现和对比分析,学生应掌握以下内容:

  1. 顺序表和链表的实现原理及适用场景。

  2. 动态扩容的策略与时间复杂度分析。

  3. 链表指针操作的逻辑与常见算法。

相关推荐
柒_汐8 分钟前
第十五届蓝桥杯C/C++B组省赛真题讲解(分享去年比赛的一些真实感受)
c语言·c++·蓝桥杯·真题·省赛
代码AC不AC30 分钟前
【数据结构】二叉树
数据结构·二叉树·学习分享·递归分析·二叉树遍历方法
huofengfeihu1 小时前
【c】-include经典注入问题
c语言·开发语言
nuo5342021 小时前
第十六届蓝桥杯 省赛C/C++ 大学B组
c语言·c++·算法·蓝桥杯·图论
无敌的牛1 小时前
双数之和+三数之和+四数之和
数据结构·算法
映秀小子1 小时前
C语言打印的坑
c语言·开发语言
ん贤2 小时前
2021第十二届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
c语言·c++·蓝桥杯
xrkhy3 小时前
提高课:数据结构之树状数组
数据结构·c++·算法
新生农民4 小时前
最小覆盖子串
java·数据结构·算法
纪元A梦4 小时前
华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·c语言·javascript·c++·python·华为od·go