顺序表与链表:头插法与尾插法详解

一、前言

在程序世界里,数据结构本质上是一种数据存储与组织策略

顺序表(Array):像工厂流水线

顺序表可以类比为一条工厂流水线,所有物品按照顺序放在连续且固定的位置上。

  • 每个位置都可以通过编号直接访问
  • 想要获取第 N 个元素,几乎是瞬间完成(随机访问效率高)
  • 但如果在中间插入或删除元素,后面的物品就必须整体搬动,代价较高

👉 它的优势在于查找快 ,劣势在于插入和删除不灵活

链表(Linked List):像一列火车

链表更像是一列火车,每节车厢只知道下一节车厢的位置,而不是整体的编号顺序。

  • 查找某一节车厢时,必须从车头一节一节向后走
  • 车厢之间通过"连接关系"串联,而不是连续摆放
  • 一旦找到目标位置,插入或删除车厢只需要修改连接关系,不影响其他车厢

👉 它的优势在于已知位置时插入、删除高效 ,劣势在于查找效率低

二、什么是头插法和尾插法?

在数据结构中,我们经常需要向已有的数据结构中添加新元素。根据插入的位置不同,常见有两种方式:头插法尾插法

头插法(Head Insertion)

概念 :将新元素插入到数据结构的最前面
特点

  • 新元素总是成为"第一个元素",原来的元素往后顺延
  • 对链表来说,只需改变头结点的指针即可,操作高效
  • 对顺序表来说,需要将数组中已有的元素整体向后移动,效率较低

尾插法(Tail Insertion)

概念 :将新元素插入到数据结构的末尾
特点

  • 新元素排在已有元素之后,顺序与输入一致
  • 链表需要维护尾指针才能高效插入
  • 顺序表直接在数组末尾放置新元素,效率高

总结

理解头插法和尾插法的本质,就是明确新元素插入的位置对数据顺序的影响,再去选择合适的数据结构和操作方法。

插入方式 插入位置 顺序效果 操作复杂度(链表) 操作复杂度(顺序表)
头插法 前面 输入逆序 O(1) O(n)
尾插法 后面 输入顺序 O(1) O(1)

三、顺序表的头插法与尾插法

顺序表(Array)是用数组实现的数据结构,元素在内存中连续存储。它的插入方式受存储方式限制,头插法和尾插法的实现方式有所不同。

顺序表头插法

原理

  • 新元素插入数组开头 L.data[0]
  • 原有元素整体向后移动一位
  • 插入后,顺序表长度 L.length 加 1

步骤

  1. 初始化顺序表,设置长度为 0。
  2. 循环输入数据,直到遇到结束标识符(如 9999)。
  3. 每次新元素插入数组首位 L.data[0]
  4. 将原有元素整体后移,保持顺序完整。
  5. L.length++,表示顺序表长度增加。

代码示例

c++ 复制代码
#include <stdio.h>

#define MAXSIZE 100

typedef struct {
    int data[MAXSIZE];
    int length;
} SqList;

// 打印顺序表
void printList(SqList L) {
    for (int i = 0; i < L.length; i++) {
        printf("%d", L.data[i]);
        if (i != L.length - 1) {
            printf(" ");
        }
    }
    printf("\n");
}
// 顺序表-头插法
int main() {
    SqList L;
    L.length = 0;
    int n;
    while (1) {
        if (scanf("%d", &n) != 1) break;   // 输入异常
        if (n == 9999) break;              // 特殊结束标志
        if (L.length >= MAXSIZE) { // 越界检查
            printf("顺序表已满,无法插入更多元素!\n");
            break;
        }
        // 将已有元素后移
        for (int j = L.length; j > 0; j--) {
            L.data[j] = L.data[j - 1];
        }
        L.data[0] = n;
        L.length++;
    }
    printList(L);
    return 0;
}

顺序表尾插法

原理

  • 新元素放在数组中当前最后一个有效位置 L.data[L.length]
  • 插入后,顺序表长度 L.length 加 1

步骤

  1. 初始化顺序表,设置长度为 0。
  2. 循环输入数据,直到遇到结束标识符(如 9999)。
  3. 每次新元素放到 L.data[L.length] 位置。
  4. L.length++,表示顺序表长度增加。

代码示例

c++ 复制代码
#include <stdio.h>
#define MAXSIZE 100

typedef struct {
    int data[MAXSIZE];
    int length;
} SqList;

void printList(SqList L) {
    for (int i = 0; i < L.length; i++) {
        printf("%d", L.data[i]);
        if (i != L.length - 1) {
            printf(" ");
        }
    }
    printf("\n");
}

int main() {
    SqList L;
    L.length = 0;
    int n;

    while (1) {
        if (scanf("%d", &n) != 1) break;   // 输入异常
        if (n == 9999) break;              // 特殊结束标志
        if (L.length >= MAXSIZE) {         // 越界检查
            printf("顺序表已满,无法插入更多元素!\n");
            break;
        }

        // 尾插法:直接放在数组末尾
        L.data[L.length] = n;
        L.length++;
    }

    printList(L);
    return 0;
}

四、顺序表的头插法与尾插法

链表头插法

原理

  • 新结点插入链表头部(头结点后),原有链表通过指针自动衔接
  • 插入后,链表长度增加
  • 头插法会导致最终链表顺序与输入顺序相反

步骤

  1. 初始化带头结点的链表,头结点 L 指针置空或 next = NULL
  2. 循环输入数据,直到遇到结束标识符(如 9999
  3. 每次新建结点 p
  4. 将新结点 p->next 指向原链表第一个结点 L->next
  5. 头结点 L->next 指向新结点 p

代码示例

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

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode, *LinkList;

void printList(LinkList L) {
    L = L->next;  // 跳过头结点
    while (L != NULL) {
        printf("%d", L->data);
        if (L->next != NULL) printf(" ");
        L = L->next;
    }
    printf("\n");
}
// 链表-头插法
int main() {
    LinkList L = (LinkList)malloc(sizeof(LNode)); // 头结点
    L->next = NULL;
    int n;

    while (1) {
        if (scanf("%d", &n) != 1) break;    // 输入异常
        if (n == 9999) break;   // 特殊结束标志
        LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = n;
        p->next = L->next;
        L->next = p;
    }

    printList(L);
    return 0;
}

链表尾插法

原理

  • 新结点插入链表尾部,通过尾指针 tail 维护链表末端
  • 插入后,链表长度增加
  • 尾插法保持输入顺序与链表顺序一致

步骤

  1. 初始化带头结点的链表,头结点 L 指针置空或 next = NULL
  2. 设置尾指针 tail = L
  3. 循环输入数据,直到遇到结束标识符(如 9999
  4. 每次新建结点 pp->next = NULL
  5. 尾指针 tail->next 指向新结点 p
  6. 更新尾指针 tail = p

代码示例

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

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode, *LinkList;

void printList(LinkList L) {
    L = L->next;  // 跳过头结点
    while (L != NULL) {
        printf("%d", L->data);
        if (L->next != NULL) printf(" ");
        L = L->next;
    }
    printf("\n");
}

// 链表-尾插法
int main() {
    LinkList L = (LinkList)malloc(sizeof(LNode)); // 头结点
    L->next = NULL;
    LinkList tail = L; // 尾指针初始化为头结点
    int n;

    while (1) {
        if (scanf("%d", &n) != 1) break;  // 输入异常
        if (n == 9999) break;             // 特殊结束标志

        LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = n;
        p->next = NULL;

        // 尾插法
        tail->next = p;
        tail = p;
    }

    printList(L);
    return 0;
}

总结

头插法和尾插法是顺序表与链表中最基础的插入操作,它们体现了不同数据结构的特点:

  • 顺序表依赖连续内存,头插需要移动元素,尾插效率高;
  • 链表通过指针连接,插入灵活,头插逆序构建,尾插顺序构建;
  • 选择原则:根据是否需要保持输入顺序、是否频繁在开头插入、以及效率要求来选择适合的方法。
相关推荐
小北方城市网4 分钟前
第1课:架构设计核心认知|从0建立架构思维(架构系列入门课)
大数据·网络·数据结构·python·架构·数据库架构
好易学·数据结构16 分钟前
可视化图解算法77:零钱兑换(兑换零钱)
数据结构·算法·leetcode·动态规划·力扣·牛客网
bkspiderx29 分钟前
C++中的map容器:键值对的有序管理与高效检索
开发语言·c++·stl·map
Hard but lovely31 分钟前
Linux: 线程同步-- 基于条件变量 &&生产消费模型
linux·开发语言·c++
独自破碎E42 分钟前
【归并】单链表的排序
数据结构·链表
L_09071 小时前
【C++】高阶数据结构 -- 平衡二叉树(AVLTree)
数据结构·c++
今儿敲了吗1 小时前
C++概述
c++·笔记
冰冰菜的扣jio1 小时前
Redis基础数据结构
数据结构·数据库·redis
C+-C资深大佬1 小时前
C++逻辑运算
开发语言·c++·算法
阿华hhh1 小时前
项目(购物商城)
linux·服务器·c语言·c++