一、链表
1.定义:
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
如图所示:
2.类型
1.单链表
单链表中的指针域只能指向节点的下一个节点。
2.双链表
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示:
3.循环链表
循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
3.链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
如图所示:
这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
4.链表的定义
C/C++
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
不定义构造函数行不行,答案是可以的,C++默认生成一个构造函数。
但是这个构造函数不会初始化任何成员变量,下面我来举两个例子:
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!
#链表的操作
#删除节点
删除D节点,如图所示:
只要将C节点的next指针 指向E节点就可以了。
那D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。
是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。
其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。
#添加节点
如图所示:
可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。
但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。
#性能分析
再把链表的特性和数组的特性进行一个对比,如图所示:
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
相信大家已经对链表足够的了解,后面我会讲解关于链表的高频面试题目,我们下期见!
#其他语言版本
二、(27. 移除元素 - 力扣(LeetCode))
1.题目
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
示例 1:
2.思路
因为链表的头节点前没有前一个结点,所以有两种解决办法:
1.将head头节点向后移动一位 2.设置一个虚拟头节点(dummy node)
先删头节点,再删非头节点
时间复杂度O(n)
空间复杂度O(1)
第一种移除方法:
C++(delete释放空间)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头结点
while(head != NULL && head->val == val){ //确保头节点不为空,并且判度当前节点的值等于目标值
ListNode* tmp = head; //创建一个临时指针 tmp,将当前节点 head 赋值给它。这样做是为了稍后删除当前节点。
head = head->next; //将当前节点的下一个节点赋值给 head,相当于移动 head 指针到下一个节点,准备继续检查下一个节点的值
delete tmp; //删除临时指针 tmp 指向的节点,即删除原来 head 指向的节点。这样就实现了删除当前节点的操作。
}
//删除非头节点
ListNode* cur = head; //创建一个指针 cur,将头节点 head 赋值给它。这样 cur 将用于遍历单链表。
while(cur != NULL && cur->next != NULL){ //这是一个 while 循环的开始,条件部分包括两个条件:第一个条件是 cur != NULL,确保当前节点不为空;第二个条件是 cur->next != NULL,确保当前节点的下一个节点不为空。这样做是为了在遍历链表时能够安全地访问下一个节点。
if(cur->next->val == val){//检查当前节点的下一个节点的值是否等于给定的值 val。
ListNode* tmp = cur->next; //创建一个临时指针 tmp,将当前节点的下一个节点赋值给它。这样做是为了稍后删除当前节点的下一个节点。
cur->next = cur->next->next; //将当前节点的下一个指针指向下下个节点,即跳过当前节点的下一个节点,实现删除操作。
delete tmp; //删除临时指针 tmp 指向的节点,即删除原来当前节点的下一个节点。
}
else{ //如果当前节点的下一个节点的值不等于 val,则将指针 cur 移动到下一个节点,继续遍历。
cur = cur -> next;
}
}
return head; //返回头节点 head,表示删除操作完成后的新链表。
}
};
C(free释放空间)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
//删除头节点
while(head != NULL && head->val == val){ //判度头节点非空。并且头节点的值等于val
struct ListNode* tmp = head;
head = head->next;
free(tmp);
}
//删除非头节点
struct ListNode* cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
struct ListNode* tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
}
else{
cur = cur->next;
}
}
return head;
}
第二种虚拟节点(dummy node):
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummynode = new ListNode(0);
dummynode->next = head;
ListNode* cur = dummynode;
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur= cur->next;
}
}
head = dummynode->next;
delete dummynode;
return head;
}
};
C
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* dummyNode = (struct ListNode*)malloc(sizeof(struct ListNode));
dummyNode->next = head;
struct ListNode* cur = dummyNode;
while (cur->next != NULL) {
if (cur->next->val == val) {
struct ListNode* tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
} else {
cur = cur->next;
}
}
head = dummyNode->next;
free(dummyNode);
return head;
}
三、(707. 设计链表 - 力扣(LeetCode))(还在学习)
四、(206. 反转链表 - 力扣(LeetCode))
1.双指针法
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp ; //创建临时指针存放cur的下一个节点
ListNode*cur = head; //初始化头节点为head
ListNode*pre = NULL; //初始化cur反转的下一个节点的
while(cur){ //但cur指向NUlL跳出循环
temp = cur->next; //存放cur的下一个节点
cur->next=pre; //第一个节点反转指向NULL
pre=cur; //pre指针向后移动
cur=temp; //cur指针向后移动指向NULL
}
return pre; //返回反转链表之后的头节点
}
};
C
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* tmp; //创建存放cur下一个节点的临时指针
struct ListNode* cur = head; //初始化头节点给cur指针
struct ListNode* pre = NULL; //初始化尾节点为NULL
while(cur){//当cur为NULL时跳出循环
tmp = cur->next;//存放cur下一个节点的临时指针
cur->next = pre;//翻转
pre = cur; //pre向后移动
cur = tmp; //cur向后移动
}
return pre; //pre为新链表的头节点
}
2.递归方法
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
// 反转链表的递归函数,pre表示当前节点的前一个节点,cur表示当前节点
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur==nullptr) return pre; // 如果当前节点为空,说明已经到达链表末尾,返回pre作为新链表的头节点
ListNode* temp = cur->next; // 如果当前节点为空,说明已经到达链表末尾,返回pre作为新链表的头节点
cur->next = pre; // 将当前节点指向前一个节点,实现反转
return reverse(cur,temp); // 递归反转下一个节点
}
// 反转链表的入口函数,head为原始链表的头节点
ListNode* reverseList(ListNode* head) {
return reverse(nullptr,head);// 调用反转函数
}
};
C
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverse(struct ListNode* pre,struct ListNode* cur) {
if(cur == NULL) return pre;
struct ListNode* temp = cur->next;
cur->next = pre;
return reverse(cur,temp);
}
struct ListNode* reverseList(struct ListNode* head) {
return reverse(NULL,head);
}