链表的定义
链表是一种常见的数据结构,由一系列节点(Node)组成,每个节点包含两个部分:数据域(Data Field)和指针域(Pointer Field 或 Link Field)。数据域用于存储节点的数据,而指针域用于存储指向下一个节点的引用(即地址或链接)。链表通过指针将这些节点连接起来,从而形成一个序列。
常见的链表有单向链表(Singly Linked List)、双向链表(Doubly Linked List)、循环链表(Circular Linked List)等,我们今天练习的是双向链表。
注:黑色箭头指向下一个节点,红色箭头反之:
特性
双向链表
- 每个节点包含两个指针,一个指向下一个节点,另一个指向前一个节点。
- 允许从任意节点向前或向后遍历。
指针
指针是编程语言中的一种数据类型,它存储了内存中的一个地址,这个地址指向另一个变量的值。换句话说,指针是一个变量,其内容是另一个变量的内存地址,而不是数据值本身。通过使用指针,程序可以直接访问和操作内存。
- 指针类型:指针变量有一个类型,这个类型指明了它所指向的数据的类型。例如,一个指向整数的指针类型在
C
语言中表示为int*
。 - 指针变量:指针本身是一个变量,它存储了一个地址值。这个地址值是指向另一个变量的内存地址。
- 解引用:通过指针可以访问它所指向的变量的值。这个过程通常称为
解引用
或间接访问
。在C语言中,解引用是通过在指针变量前加上星号(*)
来实现的。 - 地址运算:指针可以进行一些特殊的运算,比如加法、减法和比较运算。这些运算通常基于指针所指向的数据类型的大小来进行。
- 空指针:一个指针变量可以被赋值为
NULL
(在C
和C++
中)或nullptr
(在C++11
及以后版本中),以表示它不指向任何有效的内存地址。 - 野指针:一个未初始化或已被释放但仍被使用的指针被称为
野指针
。野指针
的使用是危险的,因为它们可能指向无效的内存地址,导致程序崩溃或不可预测的行为。 - 多级指针:一个指针可以指向另一个指针,从而形成多级指针。多级指针在处理复杂的
数据结构
或进行底层内存管理时非常有用。
思路描述
目标反向输出数组,即链表需要有指向上一个节点的指针,然后从最后一个节点不断往上循环,访问并输出节点中的值,直到达到链表的头节点。下面,我将逐步讲解这个思路。
代码实现
既然是写代码,头文件和main函数自然是不能少的:
这里使用的#include<bits/stdc++.h>
头文件是C++
的“万能头文件”,万能头文件省去了引用其他各种头文件的烦恼,一般编程软件都会有此头文件,如果没有可以尝试手动添加。
#include<bits/stdc++.h>
#define int long long // 宏命令,可省略
using namespace std;
signed main(){ // 因为将int定义为“long long”,mian函数返回值类型要改变
}
不过,写链表还要在中间插入一个结构体
,话不多说,直接上:
结构体可以理解为链表的单个节点的“变量类型”。不过要注意,结构体和函数不一样,在大括号{ }
之后要加分号;
。
#include<bits/stdc++.h> // C++万能头文件
#define int long long // 宏命令,可省略
using namespace std;
struct l{ // struct是结构体的关键字,l是结构体的名称(类似变量名)
int n; // 链表中存储数据的变量
l *shang; // 指向上一个节点的指针
l *xia; // 指向下一个节点的指针
};
signed main(){ // 因为将int定义为“long long”,mian函数返回值类型要改变
}
接着是输入数据,处理指针的时候,过程比较复杂,尽量理解吧
注意:用指针访问结构体变量时要用->
,名称访问结构体变量用点.
即可
int n;
cin >> n;
l *lian1 = new l; // 定义新指针,指向的结构体作为链表的第一个节点
cin >> lian1 -> n; // 输入第一个节点的数据
for(int i = 0; i < n - 1; ++i){
l *lian2 = new l; // 定义新指针,指向链表的下一个节点
cin >> lian2 -> n; // 输入数据
lian2 -> shang = lian1; // 赋值下一个节点指向上一个节点的指针
lian1 -> xia = lian2; // 赋值上一个节点指向下一个节点的指针
lian1 = lian2; // 更换节点
}
此时可以发现,最开始定义的指针lian1
是链表的倒数第一个节点,可以用箭头->
访问数据输出,上代码:
记得输出空格
while(lian1 != NULL){
cout << lian1 -> n <<" "; // 输出
lian1 = lian -> shang; // 指向上一个节点
}
OVER!!!
最后,放完整代码:
#include<bits/stdc++.h> // C++万能头文件
#define int long long // 宏命令,可省略
using namespace std;
struct l{ // struct是结构体的关键字,l是结构体的名称(类似变量名)
int n; // 链表中存储数据的变量
l *shang; // 指向上一个节点的指针
l *xia; // 指向下一个节点的指针
};
signed main(){ // 因为将int定义为“long long”,mian函数返回值类型要改变
int n;
cin >> n; // 输入数组个数
l *lian1 = new l; // 定义新指针,指向的结构体作为链表的第一个节点
cin >> lian1 -> n; // 输入第一个节点的数据
for(int i = 0; i < n - 1; ++i){ // 循环输入数据
l *lian2 = new l; // 定义新指针,指向链表的下一个节点
cin >> lian2 -> n; // 输入数据
lian2 -> shang = lian1; // 赋值下一个节点指向上一个节点的指针
lian1 -> xia = lian2; // 赋值上一个节点指向下一个节点的指针
lian1 = lian2; // 更换节点
}
while(lian1 != NULL){
cout << lian1 -> n <<" "; // 输出
lian1 = lian1 -> shang; // 指向上一个节点
}
}
是的,链表的初级运用就这么水灵灵的学完了,来看看数据测试
自测:
输入:
10
0 1 2 3 4 5 6 7 8 9
输出:
9 8 7 6 5 4 3 2 1 0
内存管理
注意,上面的代码示例中并没有包含内存释放的部分。在实际应用中,当链表不再需要时,我们应该释放其占用的内存,以避免内存泄漏。这通常是通过递归地删除每个节点来实现的。不过,通常在确保内存足够的前提下,并不要求一定要释放内存,释放只是对代码的优化。
总结
通过以上步骤,我们成功地利用链表反向输出了数组,也对双向链表有了一定的了解。这个过程涉及链表的创建、遍历等的基本操作,是学习和理解数据结构的重要一环。