初级数据结构——单向链表

前言

单向链表示最基础的数据结构之一,它也是我们学习开始学习数据结构的第一个必须要掌握的数据结构,学习数据结构一定是由浅到深,所以我们最好是先学习简单的在学习有难度的,因为直接学习难的数据结构很容易劝退,让我们来深入了解单向链表。

目录

一、什么是单向链表

单向链表(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;
    }
};

九、总结

单向链表是数据结构中的基础内容,理解其工作原理和基本操作对于进一步学习更高级的数据结构(如双向链表、循环链表、栈、队列等)非常重要

十、结语

我发的这些题目都是很简单很基础的题目,我不想一上来就发难题,先做简单题,后续我会整理题库,每个板块都会发有难度的题。学习编程是一个很艰难,漫长的过程,我们要克服一切困难,学习算法本就是由易到难,不会的地方就问,我相信通过我们坚持不懈的努力一定能理解并熟练掌握其中细节,加油,我相信你一定能行。

想看更多内容可以关注我,看我作品,关注我让我们一起学习编程,希望大家能点赞关注支持一下,让我有持续更新的动力,谢谢大家。

相关推荐
韭菜盖饭1 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode
♡喜欢做梦1 小时前
【数据结构】ArrayList与LinkedList详解!!!——Java
java·开发语言·数据结构·链表
好心的小明2 小时前
【深圳大学】数据结构A+攻略(计软版)
数据结构
熬夜学编程的小王2 小时前
【初阶数据结构篇】插入、希尔、选择、堆排序
数据结构·c++·插入排序·选择排序·希尔排序
三小尛2 小时前
快速排序(C语言)
数据结构·算法·排序算法
椅子哥2 小时前
数据结构--排序算法
java·数据结构·算法·排序算法
DDDiccc2 小时前
JAVA学习日记(十五) 数据结构
数据结构·学习
Nydia.J2 小时前
【学习笔记】数据结构(七)
数据结构·考研
2301_799084672 小时前
超全排序C语言实现
c语言·数据结构·算法·排序算法
苏唱!!!!2 小时前
二叉树-堆
数据结构