一、前言
在程序世界里,数据结构本质上是一种数据存储与组织策略。
顺序表(Array):像工厂流水线
顺序表可以类比为一条工厂流水线,所有物品按照顺序放在连续且固定的位置上。
- 每个位置都可以通过编号直接访问
- 想要获取第 N 个元素,几乎是瞬间完成(随机访问效率高)
- 但如果在中间插入或删除元素,后面的物品就必须整体搬动,代价较高
👉 它的优势在于查找快 ,劣势在于插入和删除不灵活。
链表(Linked List):像一列火车
链表更像是一列火车,每节车厢只知道下一节车厢的位置,而不是整体的编号顺序。
- 查找某一节车厢时,必须从车头一节一节向后走
- 车厢之间通过"连接关系"串联,而不是连续摆放
- 一旦找到目标位置,插入或删除车厢只需要修改连接关系,不影响其他车厢
👉 它的优势在于已知位置时插入、删除高效 ,劣势在于查找效率低。
二、什么是头插法和尾插法?
在数据结构中,我们经常需要向已有的数据结构中添加新元素。根据插入的位置不同,常见有两种方式:头插法 和尾插法。
头插法(Head Insertion)
概念 :将新元素插入到数据结构的最前面 。
特点:
- 新元素总是成为"第一个元素",原来的元素往后顺延
- 对链表来说,只需改变头结点的指针即可,操作高效
- 对顺序表来说,需要将数组中已有的元素整体向后移动,效率较低
尾插法(Tail Insertion)
概念 :将新元素插入到数据结构的末尾 。
特点:
- 新元素排在已有元素之后,顺序与输入一致
- 链表需要维护尾指针才能高效插入
- 顺序表直接在数组末尾放置新元素,效率高
总结
理解头插法和尾插法的本质,就是明确新元素插入的位置 和对数据顺序的影响,再去选择合适的数据结构和操作方法。
| 插入方式 | 插入位置 | 顺序效果 | 操作复杂度(链表) | 操作复杂度(顺序表) |
|---|---|---|---|---|
| 头插法 | 前面 | 输入逆序 | O(1) | O(n) |
| 尾插法 | 后面 | 输入顺序 | O(1) | O(1) |
三、顺序表的头插法与尾插法
顺序表(Array)是用数组实现的数据结构,元素在内存中连续存储。它的插入方式受存储方式限制,头插法和尾插法的实现方式有所不同。
顺序表头插法
原理:
- 新元素插入数组开头
L.data[0] - 原有元素整体向后移动一位
- 插入后,顺序表长度
L.length加 1
步骤:
- 初始化顺序表,设置长度为 0。
- 循环输入数据,直到遇到结束标识符(如
9999)。 - 每次新元素插入数组首位
L.data[0]。 - 将原有元素整体后移,保持顺序完整。
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
步骤:
- 初始化顺序表,设置长度为 0。
- 循环输入数据,直到遇到结束标识符(如
9999)。 - 每次新元素放到
L.data[L.length]位置。 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;
}
四、顺序表的头插法与尾插法
链表头插法
原理
- 新结点插入链表头部(头结点后),原有链表通过指针自动衔接
- 插入后,链表长度增加
- 头插法会导致最终链表顺序与输入顺序相反
步骤
- 初始化带头结点的链表,头结点
L指针置空或next = NULL - 循环输入数据,直到遇到结束标识符(如
9999) - 每次新建结点
p - 将新结点
p->next指向原链表第一个结点L->next - 头结点
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维护链表末端 - 插入后,链表长度增加
- 尾插法保持输入顺序与链表顺序一致
步骤
- 初始化带头结点的链表,头结点
L指针置空或next = NULL - 设置尾指针
tail = L - 循环输入数据,直到遇到结束标识符(如
9999) - 每次新建结点
p,p->next = NULL - 尾指针
tail->next指向新结点p - 更新尾指针
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;
}
总结
头插法和尾插法是顺序表与链表中最基础的插入操作,它们体现了不同数据结构的特点:
- 顺序表依赖连续内存,头插需要移动元素,尾插效率高;
- 链表通过指针连接,插入灵活,头插逆序构建,尾插顺序构建;
- 选择原则:根据是否需要保持输入顺序、是否频繁在开头插入、以及效率要求来选择适合的方法。