链表理论基础
文章链接:
https://programmercarl.com/%E9%93%BE%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
基础知识:
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成(数据域和指针域),数据域储存节点的数据,指针域存放指向下一个节点的指针,其中最后一个节点的指针域指向 null(即空指针)。
链表的类型
单链表:指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表既可以向前查询也可以向后查询。
循环链表:链表首尾相连,可以解决约瑟夫环问题。
链表的存储方式
链表中节点在内存中不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理,各个节点通过节点中的指针域串联。
链表的定义
//单链表的定义
struct ListNode
{
int val;//节点上存储的元素
ListNode* next; //指向下一个节点的指针
ListNode(int x): val(x), next(nullptr){} //节点构造函数
};
性能分析
数据结构 | 插入/删除(时间复杂度) | 查询(时间复杂度) | 适用场景 |
---|---|---|---|
数组 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 数据量固定,频繁查询,较少增删 |
链表 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) | 数据量不固定,频繁增删,较少查询 |
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
Leetcode 203-移除链表元素
题目描述:
https://leetcode.cn/problems/remove-linked-list-elements/description/
解题思路
移除链表元素的思路是对于每一个元素,判断其是否等于目标值,如果等于,则需要将它上一个元素的指针直接指向下一个元素。
C++ 相比于 java 和 python 来说没有自动清理删除链表元素内存的机制,因此需要手动清理内存。
这里需要注意的是头节点与其他元素不同,头节点没有上一个节点,因此如果要移除头节点的话需要将其下一个节点变为头节点。
由此产生两种不同的解决思路:
(1)直接使用原来的链表进行删除操作
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//判断头节点是否需要删除
while (head != nullptr && head->val == val) {//这里是循环而非判断主要是因为这是一个连续的过程,需要不断判断新的节点的值是否等于目标值
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头节点
ListNode* cur = head;
while (cur != nullptr && cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else {
cur = cur->next;
}
}
return head;
}
};
(2)设置一个虚拟头节点进行删除操作
给链表添加一个虚拟头节点,可以实现移除原来头节点和其他节点算法的统一
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead = new ListNode(0);//新建一个虚拟头节点
dummyhead->next = head;//虚拟头节点的指针域指向原链表的头节点
ListNode* cur = dummyhead;
while (cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;//释放删除节点的内存
}
else {
cur = cur->next;
}
}
head = dummyhead->next;//不能直接返回原来的头节点是因为在操作过程中该头节点可能已经被删除
delete dummyhead;//释放虚拟头节点的内存
return head;
}
};
Leetcode 707-设计链表
题目描述:
https://leetcode.cn/problems/design-linked-list/description/
解题思路
引入虚拟头节点实现对链表的操作,实现对头节点和其他节点操作的一致性
class MyLinkedList {
public:
//定义链表节点结构体
struct LinkedNode
{
int val;
LinkedNode* next;
LinkedNode(int val) :val(val), next(nullptr) {}
};
//初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0);
_size = 0;
}
int get(int index) {
if (index < 0 || index >(_size - 1)) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
int n = index;
while (n) {
cur = cur->next;
n--;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = newNode;
newNode->next = nullptr;
_size++;
}
void addAtIndex(int index, int val) {
if (index > _size) { return; }
else if (index < 0) { index = 0; }
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
int n = index;
while (n) {
cur = cur->next;
n--;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= _size) {
return;
}
int n = index;
LinkedNode* cur = _dummyHead;
while (n) {
cur = cur->next;
n--;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
_size--;
}
private:
LinkedNode* _dummyHead;
int _size;
};
Leetcode 206-反转链表
题目描述:
https://leetcode.cn/problems/reverse-linked-list/description/
解题思路
双指针法
设置 cur 和 pre 两个指针,cur 指向链表中的节点,pre 指向 cur 节点的前一个节点,在遍历过程中修改 cur 和 pre 的指向方向以此翻转链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
while (cur) {
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
递归法
递归的算法和双指针的思路和算法类似
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre) {
if (cur == nullptr) return pre;
ListNode* tmp = cur->next;
cur->next = pre;
return reverse(tmp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(head, nullptr);
}
};