分享内容
- 循环链表
- 双向链表
- 操作双向链表
虚拟内存空间
进程的虚拟内存空间通常分为两部分:用户空间:这是普通程序可以访问的内存区域,包括代码段、数据区、栈区、堆区等。内核空间:这是操作系统内核专用的内存区域,普通程序无法直接访问。内核空间用于存储内核代码、内核数据结构以及驱动程序等。用户空间和内核空间的划分是为了保护内核的稳定性,防止用户程序直接操作内核数据。

循环链表
上节课学习的链表也称为单向链表。单向链表的尾节点指针指向空地址NULL。

如果把尾节点指针是指向链表的头节点,就得到了循环链表(也叫环形链表)

循环链表的突现
循环链表的创建步骤和单向链表相同
①定义节点
struct node{
int data;
node *next;
}

②声明、初始化节点变量
node a = {1, NULL} ;
node b = {2, NULL} ;
node c = {3, NULL} ;

③ 构建节点之间的关系,建立循环
a.next = &b;
b.next = &c;
c. next = &a;
和单向链表的唯一区别在于要把尾节点指针指向头节点


循环链表-练习
循环链表与单链表相比,最大的区别在于?B
A、循环链表的头节点存储了链表的长度信息。
B、循环链表的最后一个节点的指针指向头节点。
C、循环链表只能单向遍历。
D、循环链表的头节点不存储数据。
循环链表的遍历
在循环链表中,可以从任意节点出发进行(正向)遍历。
遍历结束的条件是回到最初出发的节点。
node *P = &b;
do {
cout << P->data << " ";
P = P->next;
} while (P != &b) ;
cout << endl;
循环链表的插入、删除
在循环链表中插入、删除节点和单向链表相同。除了以下情况:
①循环链表初始为空,插入一个节点。
这个节点的next指针需要指向它自己。

②要删除的是循环链表中唯一的节点。
这个节点的next指针指向NULL即可。

循环链表的应用
● 循环链表由于其环形结构的特点,在需要模拟环形顺序、频繁插入和删除节
点的场景中具有独特的优势。
● 常见的应用包括约瑟夫环问题、环形队列、多线程调度、音乐播放列表和游
戏角色轮换等。
循环链表-练习1
1、在不为空的循环链表中插入一个新节点时,以下说法正确的是?B
A、必须找到链表的头节点才能插入。
B、可以在任意已知节点后插入新节点,无需找到头节点。
C、插入操作的时间复杂度为O(n)。
D、插入操作必须从头节点开始遍历。
解析:在循环链表中,插入一个新节点时,只需要找到一个已知节点,然后在该节点后插
入新节点即可,无需找到头节点。选项A和D错误;选项c错误,和单向链表一样,插入操
作的时间复杂度为O(1)。
2、在至少有2个节点的循环链表中,如果要删除一个节点,以下步骤正
确的是?B
A、只需要将该节点的指针指向NULL即可。
B、需要找到该节点的前驱节点,然后调整指针。
C、只需要将该节点的指针指向下一个节点即可。
D、需要重新调整整个链表的指针。
解析:和单向链表一样,在循环链表中,删除一个节点需要找到该节点的前驱节点,然
后将前驱节点的指针指向被删除节点的下一个节点。选项A和c都忽略了前驱节点的调整;
双向链表
单向链表,每个节点只能访问后继节点。

有时候只知每个元素后面的元素是不够的,还需要知道前面的元素是什么。
在单向链表的每个节点添加一个指向前驱节点的指针,就得到双向链表。

单向链表变成双向链表只需要两步:

1 每个节点添加 prev指针
2 prev指针指向前一个节点


建立双向链表
1定义双向链表的节点(结构体)
struct node{
int data;// 数据
node *next;// 指向后继节点的指针
node *prev;// 指向前驱节点的指针
}
- 创建节点
node a = {1, NULL, NULL} ;
node b = {2, NULL, NULL} ;
node c = {3, NULL, NULL} ;
- 通过指针实现双向链接
a.next = &b;
b.next = &c;连接后继节点
c.prev = &b;
b.prev = &a;连接前驱节点
每个节点都可以访问前驱节点和后继节点

遍历双向链表
- 正向遍历:
node *P = &a;从头节点开始
while (P != NULL) {直到后继节点为空
cout << P->data << " ";
P = P->next;找后继节点
}
- 反向遍历:
node *P = &C;从尾节点开始
while(P != NULL){直到前驱节点为空
cout << P->data << " ";
P=P->prev; 找前驱节点找前驱节点
}

与单向链表相比
● 双向链表的节点可以方便的访问前驱(前一个)节点和后继节点。
● 双向链表可以双向遍历(单向链表只能单向遍历)。
● 每个节点增加了一个指针,所以双向链表内存占用比单链表多。
适用场景
● 如果你的应用场景主要集中在单向顺序处理数据,且对内存占用敏感,单向链表是更好的选择。
● 如果你需要频繁双向遍历数据,或者需要快速定位节点的前驱和后继,双向链表更适合。
双向链表(插八节点)
插入节点分:在双向链表的头部、中间、尾部插入节点
- 在头部插入(e插入到a前面)


2. 在中间插入新节点(e插入到a后面)

3. 在尾部插入新节点(e插入到c后面)

插入节点-练习1
【真题】以下哪组操作能完成在双向链表中,在p指向的节点之后插入s
指向的节点。(其中,next为节点的直接后继,prev为节点的直接前驱)D
A
p->next->prev =s;
s->prev = p;
p->next =s;
s->next = p->next;
B
p->next->prev = s;
p->next = s;
s->prev = p;
s->next = p->next;
C,
s->prev = p;
s->next = p->next;
p->next =s;
p->next->prev = s;
D.
s->next = p->next;
p->next->prev = s;
s->prev = p;
p->next = s;




2、【真题】在一个双向链表中,p指向链表中的一个节点,s指向一待插入
节点,现要求在p之前插入s,则正确的操作是?D
A,
p->prev = s;
s->next = p;
p->prev->next =s;
s->prev = p->prev;

B.
s->prev = p->prev;
p->prev->next =s;
s->next = p;
p->prev = s->next;

C.
s->next = p;
p->next = s;
p->prev->next =s;
s->next = p;

D.
s->prev = p->prev;
p->prev->next = s;
s->next = p;
p->prev =s;

双向链表(删除节点)
删除节点分:删除头节点、删除中间节点、删除尾节点
- 删除头节点a
2. 删除中间节点b

3. 删除尾节点c

删除节点-练习
【真题】设p指向双向链表中的一个节点,它的左右节点均为非空。现要
求删除节点p,则下列语句序列中不正确的是?D
A,
p->prev->next = p->next;
p->prev->next->prev = p->prev;

B,
p->prev->next = p->next;
p->next->prev = p->prev;

C.
p->next->prev = p->prev;
p->next->prev->next = p->next;

D,
p->next->prev = p->next;
p->prev->next = p->prev;

倒背如流
乐乐想要编写一个程序,实现持续输入数字,直到输入数字0时停止。在输入结束后,
需要将输入的数字按顺序从头到尾和从尾到头各背诵一次。
【输入格式】
一行包含多个整数数字,其中最后一个数字为0,每个整数的值均小于2^31。
【输出格式】
两行,第一行将按照正序输出这些数字,第二行倒序输出这些数字,数字之间用空格分隔。
【输入样例】1 3 3 1 4 6 2 0
【输出样例】1 3 3 1 4 6 2
2 6 4 1 3 3 1
题目要求正序、倒序输出,推荐使用双向链表,方便双向遍历
struct node {
int data;// 数据
node *next;// 指向后继节点的指针
node *prev;// 指向前驱节点的指针
}
正序、倒序输出需要知道头节点和尾节点在哪。
node *head, *tail;


创建节点的两种方式




完整代码
#include<iostream>
using namespace std;
struct node{
int data;// 数据
node *next;// 指向后继节点的指针
node *prev;// 指向前驱节点的指针
}
int main(){
int n;
cin >> n;
//1、读入第一个数,创建第一个节点,头指针指向这个节点
node *head= new node;//动态分配内存
head->data = n;//新结点的数据
head->prev= head->next=NULL;//初始化指针
// 2、重复插入新节点直到读到o
node *p=head;//新节点总是插入p后一个位置
while (1) {
cin >> n;
if (n == 0)
break;//读入的数据为0时,停止输入数据
node *s=new node;//分配新结点s
s->data = n;// 新结点存储数字n
s->prev= s->next=NULL;//初始化指针
// 把s插入p后面
s->prev = p;
p->next = s;
// p后移1
p = p->next;
}
//3、保存指向尾节点的指针
node *tail = p;
// 4、正序遍历输出
p=head;//p指向头节点
while (p != NULL) {
cout << p->data << '';
p=p->next;// 指向后继节点
}
cout << endl;
// 5、逆序遍历输出
p= tail;//p指向尾节点
while (p != NULL) {
cout << p->data << '';
p=p->prev;//指向前驱节点
}
cout << endl;
return 0;
}
本次课程的知识点
-
循环链表的概念
-
循环链表与单向链表的对比
-
双向链表的概念
-
双向链表与单向链表的对比
-
双向链表的操作:插入节点、删除节点
1、以下关于双向链表的说法错误的是?C
A、双向链表可以方便地实现从任意节点向前或向后遍历
B、在双向链表中,删除一个节点需要修改两个节点的指针
C、相同数据量时,双向链表比单链表少占内存空间
D、双向链表的头节点的前驱指针通常为空
2、在双向链表中,每个节点包含的指针域指向的内容是?A
A、前驱节点的地址和后继节点的地址
B、前驱节点的地址和数据域
C、后继节点的地址和数据域
D、只有后继节点的地址
动态创建链表
创建n个节点的单向链表,按顺序存储1~n。请用动态分配内存的方式
创建节点。遍历输出每个节点的值。
【输入格式】一个正整数n
【输出格式】输出单向链表每个节点的值,数值之间用空格隔开。
【输入样例】10
【输出样例】1 2 3 4 5 6 7 8 9 10
#include<iostream>
using namespace std;
struct node{
int data;// 数据
node *next; // 指向后继节点的指针
};
int main() {
int n;
cin >> n;
//1、创建第一个节点,头指针指向这个节点
node *head= new node;//动态分配内存
head->data = 1;//新结点的数据
head->next = NULL;//初始化指针
// 2、插入值为2~n的新节点(在尾部插入)
int i = 2;
node *p=head;//新节点总是插入p后一个位置
for(int i = 2; i < n ; i++) {
// 分配新结点s
node *s = new node;
s->data = i;// 新结点存储数字i
s->next = NULL; //初始化指针
// 把s插入p后面
p->next = s;
// p后移1
p = p->next;
}
// 正序遍历输出
p=head;//p指向头节点
while (p != NULL) {
cout << p->data << '';
p=p->next;//指向后继节点
}
cout << endl;
return 0;
}
&spm=1001.2101.3001.5002&articleId=162333081&d=1&t=3&u=1190bcc256f14967b81c78fb3d3b4a08)
3311

被折叠的 条评论
为什么被折叠?



