《C语言中线性表、顺序表与链表的教学方案》

2天前发布 gsjqwyl
4 0 0

《C语言中线性表、顺序表与链表的教学规划》

🌟
各位朋友好,我是maomi_9526

🌍 若想栽一棵树,最早是在十年前,其次就是当下的时候!

🚀 今日来钻研C语言的相关知识。

👍 要是觉得这篇文章有帮助,欢迎您进行点赞、收藏并分享给更多人哦

目录

  • 教学目标
  • 教学重点与难点
  • 教学方法
  • 教学大纲
  • 线性表(Linear List)
    • 线性表的定义
    • 线性表的基本特性
    • 线性表的常见实现方式
    • 顺序存储(顺序表)
    • 链式存储(链表)
    • 顺序表与链表的比较
    • 顺序表操作
    • 链表基本操作
  • 顺序表(Sequence Table)
    • 静态顺序表与动态顺序表
  • 顺序表 vs 链表
    • 对比分析
  • 课堂练习
  • 作业设计
  • 教学总结

教学目标

  1. 领会线性表的逻辑构造以及其在物理层面的实现途径。
  2. 熟练掌握顺序表和链表的实现原理以及核心操作方法。
  3. 能够借助代码来实现顺序表和链表的基础功能。
  4. 剖析顺序表与链表在性能方面的不同之处以及各自适用的场景。

教学重点与难点

  • 重点:顺序表的动态扩充过程、链表的指针运用方式、时间复杂度的分析方法。
  • 难点:链表节点的插入和删除逻辑、顺序表与链表在性能上的权衡要点。

教学方法

理论讲解结合代码演示、对比分析以及课堂练习相结合的方式。

教学大纲

1. 线性表(Linear List)

1.1 线性表的定义

线性表是由若干个具备相同特性的元素所构成的有限序列,元素之间存在清晰的前后关系。每一个元素都拥有唯一的前驱和后继元素。线性表能够采用顺序存储或者链式存储的方式。
逻辑结构:线性表在逻辑上是按顺序排列的,意味着元素之间是依次排列的,每个元素都有着明确的前后关联。
物理结构:线性表的元素在计算机内存中的存储方式,既可以是连续的(顺序表),也可以是不连续的(链表)。

1.2 线性表的基本特性
  • 元素的顺序性:线性表中元素的顺序关系决定了元素之间的前后依赖关系,一般我们按照从第一个元素到最后一个元素的顺序来进行访问。
  • 唯一性:每个元素在序列里有唯一的前驱和后继元素,只有第一个元素没有前驱,最后一个元素没有后继。
  • 有限性:线性表包含一个有限的元素集合,并且元素的数量是固定的。
1.3 线性表的常见实现方式

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

1.3.1 顺序存储(顺序表)

顺序存储借助一段连续的内存空间来存储数据,通常利用数组来实现。数据元素在内存中的地址是连续的,所以能够通过索引直接进行访问。
优点
高效的随机访问:能够通过索引直接访问任意位置的元素,时间复杂度为 O(1)。
空间效率高:在内存中是连续存储的,相对而言能够更高效地利用内存。
缺点
插入和删除效率较低:在顺序表中间插入或者删除元素时,需要移动大量的元素,时间复杂度为 O(N)。
固定容量问题:顺序表的容量一旦确定,后期难以动态调整空间,需要运用动态扩容,但扩容可能会造成空间浪费。

1.3.2 链式存储(链表)

链表运用非连续的内存空间来存储数据,每个元素包含一个数据域以及一个指向下一个元素的指针(或者同时包含指向前后节点的指针),所以数据元素的地址不一定是连续的。
优点
插入和删除效率高:链表仅需调整指针就能完成插入和删除操作,时间复杂度为 O(1)(无需移动元素)。
动态内存分配:链表不需要预先定义大小,内存空间能够依据实际需求动态分配,避免了空间浪费的情况。
缺点
随机访问效率低:访问链表中的元素需要从头节点开始依次遍历,时间复杂度为 O(N)。
指针开销:每个节点需要额外的指针空间来存储前驱或后继元素。

1.4 顺序表与链表的比较
特性 顺序表 链表
存储方式 使用连续的内存块(数组)存储元素 使用不连续的内存块(通过指针链接)
随机访问 O(1) O(N)
插入/删除操作 O(N)(需要移动元素) O(1)(只需修改指针)
扩容/缩容 动态扩容(可能浪费空间) 不需要扩容,按需分配空间
内存空间使用 存储空间固定,可能造成浪费 每个节点动态分配内存
应用场景 适合频繁访问的场景 适合频繁插入和删除的场景
内存开销 低,只有存储元素的数据 较高,每个节点还需要存储指针
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--;
}
1.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)
  2. 定义:静态顺序表是运用固定大小的数组来存储数据元素。它的大小在编译的时候就已经确定,运行过程中不能进行更改。
  3. 特点
    • 大小固定,一旦对数组的大小进行了定义,就没办法动态地进行调整。
    • 存储元素的内存地址在编译的时候就被分配好了,内存空间是连续的。
    • 适用于数据量已知且不会频繁变化的场景。
  4. 优点
    • 存储效率高,访问元素的时间复杂度为O(1)。
  5. 缺点
    • 固定的容量有可能会导致内存浪费或者空间不足的情况。
    • 插入和删除元素的时候可能会产生大量的数据搬移,时间复杂度为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;
}
  1. 动态顺序表(Dynamic Sequence Table)
  2. 定义:动态顺序表是一个支持动态扩容的数组。当数组的存储空间不够的时候,系统会自动分配更大的内存空间,并且将原有的元素复制到新的数组中。
  3. 特点
    • 容量是可以动态调整的,通常会根据需求将数组的容量进行扩展(比如扩容为原来的2倍)。
    • 数据的内存地址有可能会发生变化,因为重新分配了更大的内存空间。
    • 适用于数据量不确定或者需要经常变化的场景。
  4. 优点
    • 容量能够动态增长,不会造成内存空间的浪费。
    • 避免了固定容量数组出现空间不足的问题。
  5. 缺点
    • 扩容操作可能会导致性能下降,尤其是在频繁扩容的时候,需要进行内存的重新分配和数据的搬移。

示例(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;
}
顺序表与链表对比分析
  • 静态顺序表:内存空间固定,适用于数据量明确的情形,访问效率高,但插入和删除操作或许需要大量的数据搬移,并且扩容较为困难。
  • 动态顺序表:内存空间可扩展,适用于数据量不确定或变化较大的场景,动态扩容规避了固定容量引发的问题,但在扩容时可能会影响性能。
特性 顺序表 链表
存储方式 使用连续的内存块(数组)存储元素 使用不连续的内存块(通过指针链接)
随机访问 O(1) O(N)
插入/删除操作 O(N)(需要移动元素) O(1)(只需修改指针)
空间管理 动态扩容可能浪费空间 按需分配,无空间浪费
缓存局部性 高(
© 版权声明

相关文章

暂无评论

暂无评论...