前言
单向链表示最基础的数据结构之一,它也是我们学习开始学习数据结构的第一个必须要掌握的数据结构,学习数据结构一定是由浅到深,所以我们最好是先学习简单的在学习有难度的,因为直接学习难的数据结构很容易劝退,让我们来深入了解单向链表。
目录
- 前言
- 一、什么是单向链表
- 二、链表结构
- 三、基本操作
- 四、性能分析
- 五、优点和缺点
- 六、删除、插入元素动态图解
- 七、代码模版
- 八、经典例题
-
- 1.数列有序!
- 2.二进制链表转整数
- [[3.面试题 02.02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/description/)](#3.面试题 02.02. 返回倒数第 k 个节点)
- [[4.LCR 140. 训练计划 II](https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/description/)](#4.LCR 140. 训练计划 II)
- 九、总结
- 十、结语
一、什么是单向链表
单向链表(Singly Linked List)是一种线性数据结构,由一系列节点组成,每个节点包含数据域和指向下一个节点的指针。
二、链表结构
1. 节点结构
每个节点通常包含两个部分:
数据域 :存储节点的值或数据。
指针域(或称为链接域):存储指向下一个节点的指针。如果节点是链表的最后一个节点,则指针通常为空(NULL 或 nullptr)。
2. 链表结构
头节点 :链表的第一个节点,通常用于方便地访问链表。有时也使用"哑节点"(dummy node)或"哨兵节点"(sentinel node)作为头节点,以简化边界情况的处理。
尾节点:链表的最后一个节点,其指针域为空
三、基本操作
1.插入操作
头插法 :在链表头部插入新节点。新节点的指针指向当前头节点,然后更新头指针指向新节点。
尾插法 :在链表尾部插入新节点。需要遍历链表找到尾节点,然后将尾节点的指针指向新节点,并更新尾指针(如果链表有尾指针引用的话)。
中间插入:在链表的中间位置插入新节点。需要找到插入位置的前一个节点,然后调整指针。
2.删除操作
删除头节点 :更新头指针指向第二个节点(如果存在),并释放头节点的内存。
删除尾节点 :需要遍历链表找到倒数第二个节点,然后将其指针设为空,并释放尾节点的内存。
删除中间节点:找到要删除的节点的前一个节点,然后调整其指针指向要删除节点的下一个节点,并释放要删除节点的内存。
3.查找操作
按值查找 :从头节点开始遍历链表,比较每个节点的值,直到找到匹配的节点或遍历到链表末尾。
按位置查找 :从头节点开始遍历链表,直到达到指定位置或遍历到链表末尾。
遍历操作
从头节点开始,依次访问每个节点,直到遍历到链表末尾(即遇到空指针)。
四、性能分析
时间复杂度:
插入、删除和查找操作在最坏情况下的时间复杂度为 O(n),其中 n 是链表的长度。因为可能需要遍历整个链表才能找到插入、删除或查找的位置。
空间复杂度:
链表的空间复杂度取决于节点数量和每个节点所需的空间。除了存储数据的空间外,还需要额外的空间来存储指针。
五、优点和缺点
优点
插入和删除操作不需要移动大量数据,只需要调整指针。
不需要预先知道数据的大小。
链表在内存中的分配是动态的,可以灵活地调整大小。
缺点
访问某个节点需要从头节点开始遍历,时间复杂度较高。
需要额外的空间来存储指针。
链表节点的内存分配和释放相对复杂,可能导致内存碎片问题。
六、删除、插入元素动态图解
插入元素
删除元素
七、代码模版
cpp
#include<iostream>
#include<stdexcept>
using namespace std;
#define eType int
struct ListNode {
eType data;//数据域
ListNode* next;//指针域,指向下一个节点
ListNode(eType x):data(x),next(NULL){}
};
class LinkedList {
private:
ListNode* head;
int size;
public:
LinkedList() :size(0), head(NULL) {}//构造函数
~LinkedList();//析构函数
void inser(int i, eType x);//元素插入
void remove(int i);//删除元素
void update(int i, eType x);//更新元素
ListNode* find(eType x);//按值查找元素
ListNode* get(int i);//按序号查找
bool isempty();//判空
void print();//打印链表元素
void append(eType x);//在链表尾部插入元素
void ascInsert(eType x);//顺序插入元素
};
LinkedList::~LinkedList() {
ListNode* curr = head;//游标节点,表示表里链表到当前节点
while (curr) {
ListNode* t = curr;
curr = curr->next;
delete t;
}
}
void LinkedList::inser(int i, eType x) {
if (i<0 || i>size) {
throw std::out_of_range("Invalid position");
}
ListNode* newNode = new ListNode(x);
if (!i) {
newNode->next = head;
head = newNode;
}
else {
ListNode* curr = head;
for (int j = 0; j < i - 1; j++) {
curr = curr->next;
}
newNode->next = curr->next;
curr->next = newNode;
}
size++;
}
void LinkedList::remove(int i) {
if (i<0 || i>=size) {
throw std::out_of_range("Invalid position");
}
if (!i) {
ListNode* t = head;
head = head->next;
delete t;
}
else {
ListNode* curr = head;
for (int j = 0; j < i - 1; j++) {
curr = curr->next;
}
ListNode* t = curr->next;
curr->next = curr->next->next;
delete t;
}
size--;
}
void LinkedList::update(int i, eType x) {
get(i)->data = x;
}
ListNode* LinkedList::find(eType x) {
ListNode* curr = head;
while (curr && curr->data != x) {
curr = curr->next;
}
return curr;
}
ListNode* LinkedList::get(int i) {
if (i < 0 || i >= size) {
throw std::out_of_range("Invalid position");
}
ListNode* curr = head;
for (int j = 0; j < i; j++) {
curr = curr->next;
}
return curr;
}
void LinkedList::print() {
ListNode* curr = head;
while (curr) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
void LinkedList::append(eType x) {
inser(size, x);
}
void LinkedList::ascInsert(eType x) {
ListNode* curr = head;
if (!size) {//如果链表为空
inser(0,x);
return;
}
for (int i = 0; i < size; i++) {
if (x <= curr->data) {
inser(i, x);
return;
}
curr = curr->next;
}
inser(size, x);//在链表中找不到比插入的值大
}
bool LinkedList::isempty() {
return size == 0;
}
int main() {
return 0;
}
八、经典例题
1.数列有序!
(帅哥们这个蓝色字体可以点进去看原题)
cpp
#include<iostream>
#include<stdexcept>
using namespace std;
#define eType int
struct ListNode {
eType data;//数据域
ListNode* next;//指针域,指向下一个节点
ListNode(eType x):data(x),next(NULL){}
};
class LinkedList {
private:
ListNode* head;
int size;
public:
LinkedList() :size(0), head(NULL) {}//构造函数
~LinkedList();//析构函数
void inser(int i, eType x);//元素插入
void remove(int i);//删除元素
void update(int i, eType x);//更新元素
ListNode* find(eType x);//按值查找元素
ListNode* get(int i);//按序号查找
bool isempty();//判空
void print();//打印链表元素
void append(eType x);//在链表尾部插入元素
void ascInsert(eType x);//顺序插入元素
};
LinkedList::~LinkedList() {
ListNode* curr = head;//游标节点,表示表里链表到当前节点
while (curr) {
ListNode* t = curr;
curr = curr->next;
delete t;
}
}
void LinkedList::inser(int i, eType x) {
if (i<0 || i>size) {
throw std::out_of_range("Invalid position");
}
ListNode* newNode = new ListNode(x);
if (!i) {
newNode->next = head;
head = newNode;
}
else {
ListNode* curr = head;
for (int j = 0; j < i - 1; j++) {
curr = curr->next;
}
newNode->next = curr->next;
curr->next = newNode;
}
size++;
}
void LinkedList::remove(int i) {
if (i<0 || i>=size) {
throw std::out_of_range("Invalid position");
}
if (!i) {
ListNode* t = head;
head = head->next;
delete t;
}
else {
ListNode* curr = head;
for (int j = 0; j < i - 1; j++) {
curr = curr->next;
}
ListNode* t = curr->next;
curr->next = curr->next->next;
delete t;
}
size--;
}
void LinkedList::update(int i, eType x) {
get(i)->data = x;
}
ListNode* LinkedList::find(eType x) {
ListNode* curr = head;
while (curr && curr->data != x) {
curr = curr->next;
}
return curr;
}
ListNode* LinkedList::get(int i) {
if (i < 0 || i >= size) {
throw std::out_of_range("Invalid position");
}
ListNode* curr = head;
for (int j = 0; j < i; j++) {
curr = curr->next;
}
return curr;
}
void LinkedList::print() {
ListNode* curr = head;
while (curr) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
void LinkedList::append(eType x) {
inser(size, x);
}
void LinkedList::ascInsert(eType x) {
ListNode* curr = head;
if (!size) {//如果链表为空
inser(0,x);
return;
}
for (int i = 0; i < size; i++) {
if (x <= curr->data) {
inser(i, x);
return;
}
curr = curr->next;
}
inser(size, x);//在链表中找不到比插入的值大
}
bool LinkedList::isempty() {
return size == 0;
}
int main() {
int n, m;
while (cin >> n >> m) {
LinkedList l;
if (!n && !m) break;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
l.inser(i, x);
}
l.ascInsert(m);
l.print();
}
return 0;
}
2.二进制链表转整数
(帅哥们这个蓝色字体可以点进去看原题)
cpp
/**
* 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:
int getDecimalValue(ListNode* head) {
int sum=0;
ListNode*curr=head;
while(curr){
sum=sum*2+curr->val;
curr=curr->next;
}
return sum;
}
};
3.面试题 02.02. 返回倒数第 k 个节点
(帅哥们这个蓝色字体可以点进去看原题)
这个就是经典双指针快慢指针的链表题
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
int kthToLast(ListNode* head, int k) {
ListNode*fast=head;
while(k--){
fast=fast->next;
}
ListNode*slow=head;
while(fast){
slow=slow->next;
fast=fast->next;
}
return slow->val;
}
};
4.LCR 140. 训练计划 II
cpp
/**
* 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* trainingPlan(ListNode* head, int cnt) {
ListNode*fast=head;
while(cnt--){
fast=fast->next;
}
ListNode*slow=head;
while(fast){
slow=slow->next;
fast=fast->next;
}
return slow;
}
};
九、总结
单向链表是数据结构中的基础内容,理解其工作原理和基本操作对于进一步学习更高级的数据结构(如双向链表、循环链表、栈、队列等)非常重要
十、结语
我发的这些题目都是很简单很基础的题目,我不想一上来就发难题,先做简单题,后续我会整理题库,每个板块都会发有难度的题。学习编程是一个很艰难,漫长的过程,我们要克服一切困难,学习算法本就是由易到难,不会的地方就问,我相信通过我们坚持不懈的努力一定能理解并熟练掌握其中细节,加油,我相信你一定能行。
想看更多内容可以关注我,看我作品,关注我让我们一起学习编程,希望大家能点赞关注支持一下,让我有持续更新的动力,谢谢大家。