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

一、前言

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

顺序表(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;
}

总结

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

  • 顺序表依赖连续内存,头插需要移动元素,尾插效率高;
  • 链表通过指针连接,插入灵活,头插逆序构建,尾插顺序构建;
  • 选择原则:根据是否需要保持输入顺序、是否频繁在开头插入、以及效率要求来选择适合的方法。
相关推荐
重生之后端学习6 小时前
56. 合并区间
java·数据结构·后端·算法·leetcode·职场和发展
历程里程碑7 小时前
C++ 5:模板初阶
c语言·开发语言·数据结构·c++·算法
leoufung7 小时前
LeetCode 74. Search a 2D Matrix
数据结构·算法·leetcode
dllmayday8 小时前
Qt/QML + C++ 双向数据绑定(MVVM 模式的几种常用方法(ChatGPT)
开发语言·c++·qt
liu****8 小时前
一.脚手架介绍以及部分工具使用
开发语言·数据结构·c++·手脚架开发
fish_xk8 小时前
c++类和对象(上)
c++
R-G-B9 小时前
哈希表(hashtable),哈希理论,数组实现哈希结构 (C语言),散列理论 (拉链发、链接发),散列实现哈希结构,c++ 实现哈希
c语言·哈希算法·散列表·哈希表·数组实现哈希结构·散列实现哈希结构·c++ 实现哈希
历程里程碑9 小时前
C++ 6 :string类:高效处理字符串的秘密
c语言·开发语言·数据结构·c++·笔记·算法·排序算法
xu_yule9 小时前
算法基础-(数据结构)
数据结构