数据结构 -- 链表

1.单链表的按位序插入(带头结点)

cpp 复制代码
struct LNode {   //定义单链表节点类型
	ElemType data;   //每个节点存放一个数据元素
	struct LNode* next;   //指针指向下一个节点
};

struct LNode* p = (struct LNode*)malloc(sizeof(struct LNode));
//增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点
完整代码:
cpp 复制代码
#include <iostream>
#include <cstdlib>
using namespace std;

using ElemType = int;

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

bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;     //非法位序
	}
	LNode* p = L;     //p指向头结点
	int j = 0;        //j表示p的位序(头结点算0)
	 
	while (p != NULL && j < i - 1) {    // 找到第 i-1 个结点
		p = p->next;  
		j++;
	}
	if (p == NULL) {     // i 过大,链表不够长 
		return false;
	}

	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;                                 ----解释1

	return true;
}


/* 辅助:初始化一个空链表(只含头结点) */
void InitList(LinkList& L) {
	L = (LNode*)malloc(sizeof(LNode));
	L->next = nullptr;
}

//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
	InitList(L);
	LNode* r = L;
	cout << "请依次输入" << n << "个元素:";
	for (int i = 0;i < n;i++) {
		ElemType x;
		cin >> x;
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = NULL;
		r -> next = s;
		r = s;
	}
}                                               ----解释2


//打印链表
void PrintList(LinkList L) {
	LNode* p = L->next;
	cout << "当前链表为:";
	while (p) {
		cout << p->data << (p->next ? "->" : "");
		p = p->next;
	}
	cout << endl;
}                                              ----解释3


/* 测试 */
int main() {
	LinkList L;
	InitList(L);          // 空链表

	/* 手动插入几个元素 */
	ListInsert(L, 1, 10); // 第 1 位插入 10
	ListInsert(L, 2, 20); // 第 2 位插入 20
	ListInsert(L, 1, 5);  // 再插到最前面
	PrintList(L);         // 应输出:5 -> 10 -> 20

	/* 交互式测试 */
	int pos, val;
	cout << "请输入要插入的位序 和 元素值(位序<1结束):";
	while (cin >> pos >> val) {
		if (pos < 1) break;
		if (ListInsert(L, pos, val))
			PrintList(L);
		else
			cout << "插入失败,位序非法或内存不足!" << endl;
	}

	 
	return 0;
}
解释1:
cpp 复制代码
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;

这四句就是"把一个新生成结点 `s` 插到 `p` 结点后面"的核心四步,逐行解释如下:

1. `LNode* s = (LNode*)malloc(sizeof(LNode));`

在内存堆里申请一块能装一个 `LNode` 的空间,并把这块空间的首地址赋给指针 `s`。

通俗讲:造出一个"空结点",让 `s` 指向它。

2. `s->data = e;`

把要插入的元素值 `e` 放进这个新结点的数据域。

3. `s->next = p->next;`

让新结点 `s` 的指针域先指向 `p` 当前的后继结点。

换句话说:先把 `p` 原来的"下一跳"借过来,变成 `s` 的"下一跳",这样后面那条链就不会丢。

4. `p->next = s;`

最后把 `p` 的指针域改成指向 `s`,完成"插队"------从此 `p` 后面不再是旧结点,而是新结点 `s`。

画一条链更直观:

插入前:

... → p → A → ...

插入后:

... → p → s → A → ...

步骤 3、4 的顺序不能反,如果先执行 `p->next = s` 再执行 `s->next = p->next`,就会把 `A` 的地址丢掉,造成断链。

解释2:
cpp 复制代码
//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
	InitList(L);
	LNode* r = L;
	cout << "请依次输入" << n << "个元素:";
	for (int i = 0;i < n;i++) {
		ElemType x;
		cin >> x;
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = NULL;
		r -> next = s;
		r = s;
	}
}

这段代码干的事就是:

"从键盘读 n 个整数,用尾插法把它们依次接成一条带头结点的单链表。"

逐句直译:

1. `InitList(L);`

先给链表弄个头结点,里面不放数据,`L->next` 现在是 `nullptr`。

2. `LNode* r = L;`

`r` 叫"尾指针",永远指向当前链表的最后一个结点。一开始链表只有头结点,所以 `r` 就是 `L`。

3. `for (int i = 0; i < n; ++i)`

循环 `n` 次,每次读一个数。

4. `cin >> x;`

从键盘读一个整数到 `x`。

5. `LNode* s = (LNode*)malloc(sizeof(LNode));`

在堆里新生成一个空结点,让 `s` 指向它。

6. `s->data = x;`

把刚读到的整数塞进新结点的数据域。

7. `s->next = nullptr;`

新结点后面暂时没东西,所以先指向空。

8. `r->next = s;`

把新结点接在尾指针 `r` 的后面。

9. `r = s;`

尾指针往前挪一步,现在 `r` 又指向"最新"的最后一个结点了。

循环结束后,链表顺序就是键盘输入的顺序,头结点还在最前面,不保存有效数据。

解释3:
cpp 复制代码
//打印链表
void PrintList(LinkList L) {
	LNode* p = L->next;
	cout << "当前链表为:";
	while (p) {
		cout << p->data << (p->next ? "->" : "");
		p = p->next;
	}
	cout << endl;
}

一句话:

"跳过头结点,把链表里的有效数据按顺序打印成 `a -> b -> c` 这种形式。"

逐句解释:

1. `LNode* p = L->next;`

头结点不存数据,从第一个真正结点开始。

2. `while (p)`

只要当前结点存在就继续。

3. `cout << p->data`

输出当前结点的数据。

4. `(p->next ? " -> " : "")`

如果后面还有结点,就输出箭头,否则什么都不输出(避免末尾多一个 `->`)。

5. `p = p->next;`

指针往后挪一个。

2.单链表的按位序插入(不带头结点)

如果不带头结点,则插入,删除第1个元素时,需要更改头指针L;

cpp 复制代码
#include <iostream>
#include <cstdlib>
using namespace std;

using ElemType = int;

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

bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;     //非法位序
	}
	LNode* p = L;     //p指向头结点
	int j = 0;        //j表示p的位序(头结点算0)
	 
	while (p != NULL && j < i - 1) {    // 找到第 i-1 个结点
		p = p->next;  
		j++;
	}
	if (p == NULL) {     // i 过大,链表不够长 
		return false;
	}
	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}


/* 辅助:初始化一个空链表(只含头结点) */
void InitList(LinkList& L) {
	L = (LNode*)malloc(sizeof(LNode));
	L->next = nullptr;
}

//尾插法建表,方便测试
void CreateListTail(LinkList& L, int n) {
	InitList(L);
	LNode* r = L;
	cout << "请依次输入" << n << "个元素:";
	for (int i = 0;i < n;i++) {
		ElemType x;
		cin >> x;
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = NULL;
		r -> next = s;
		r = s;
	}
}


//打印链表
void PrintList(LinkList L) {
	LNode* p = L->next;
	cout << "当前链表为:";
	while (p) {
		cout << p->data << (p->next ? "->" : "");
		p = p->next;
	}
	cout << endl;
}


/* 测试 */
int main() {
	LinkList L;
	InitList(L);          // 空链表

	/* 手动插入几个元素 */
	ListInsert(L, 1, 10); // 第 1 位插入 10
	ListInsert(L, 2, 20); // 第 2 位插入 20
	ListInsert(L, 1, 5);  // 再插到最前面
	PrintList(L);         // 应输出:5 -> 10 -> 20

	/* 交互式测试 */
	int pos, val;
	cout << "请输入要插入的位序 和 元素值(位序<1结束):";
	while (cin >> pos >> val) {
		if (pos < 1) break;
		if (ListInsert(L, pos, val))
			PrintList(L);
		else
			cout << "插入失败,位序非法或内存不足!" << endl;
	}

	 
	return 0;
}

运行结果:

3.单链表的删除

1.按位序删除(删除第 i 个结点,数据带回)
cpp 复制代码
bool ListDelete(LinkList &L, int i, ElemType &e){
    if (i < 1) return false;          // 位序非法

    LNode *p = L;                     // p 指向头结点(位序 0)
    int j = 0;
    while (p && j < i - 1){           // 找第 i-1 个结点
        p = p->next;
        ++j;
    }
    if (!p || !p->next) return false; // i 过大或已到表尾

    LNode *q = p->next;               // q 指向待删结点(第 i 个)
    e = q->data;                      // 带回数据
    p->next = q->next;                // 跨过待删结点
    free(q);
    return true;
}
2. 指定结点删除(偷后继法,O(1),p 不能是尾结点)
cpp 复制代码
bool DeleteNode(LNode *p){
    if (!p || !p->next) return false; // p 为空或是尾结点则失败
    LNode *q = p->next;               // q 是后继
    p->data = q->data;                // 数据搬上来
    p->next = q->next;                // 跨过后继
    free(q);
    return true;
}

使用示例:

cpp 复制代码
LinkList L;
InitList(L);               // 建带头结点的空表
/* ...建表... */

int x;
if (ListDelete(L, 3, x)) cout << "被删元素=" << x << endl;

LNode *p = GetElem(L, 2);  // 拿到已知结点
if (DeleteNode(p)) cout << "指定结点已删除" << endl;
相关推荐
Sheep Shaun9 分钟前
STL中的unordered_map和unordered_set:哈希表的快速通道
开发语言·数据结构·c++·散列表
optimistic_chen20 分钟前
【Redis 系列】常用数据结构---String类型
数据结构·数据库·redis·缓存·string
wen__xvn32 分钟前
代码随想录算法训练营DAY1第一章 数组part01
数据结构·算法·leetcode
爱编码的傅同学36 分钟前
【程序地址空间】页表的映射方式
c语言·数据结构·c++·算法
Mintopia39 分钟前
🧠 从零开始:纯手写一个支持流式 JSON 解析的 React Renderer
前端·数据结构·react.js
造夢先森1 小时前
常见数据结构及算法
数据结构·算法·leetcode·贪心算法·动态规划
iAkuya1 小时前
(leetcode)力扣100 29删除链表的倒数第 N 个结点(双指针)
算法·leetcode·链表
良木生香2 小时前
【数据结构-初阶】二叉树---链式存储
c语言·数据结构·c++·算法·蓝桥杯·深度优先
长安er11 小时前
LeetCode215/347/295 堆相关理论与题目
java·数据结构·算法·leetcode·
粉红色回忆11 小时前
用链表实现了简单版本的malloc/free函数
数据结构·c++