
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法初阶》
✨逆境不吐心中苦,顺境不忘来时路! 🎬 博主简介:

前言:我们通过学习了顺序表和链表的相关知识,小编相信你已经拥有了一定的算法能力了,为了锻炼和提高对于链表知识的理解,本篇文章会带着大家一起进入算法的暴力美学当中,一起体会算法题中所用到的知识,相信你学完之后一定会有所收获,此外还会介绍一下双向链表的实现,废话不多说,下面跟着小编的节奏🎵一起学习吧!
目录
- 1.移除链表元素
- 2.反转链表
- 3.链表的中间结点
- 4.合并两个有序链表
- 5.链表的回文结构
- 6.相交链表
- 7.环形链表
- 8.环形链表||
- 9.链表分割
- 10.随机链表的复制
- 11.双向链表的概念和结构
- 12.双向链表的实现(链表初始化、头尾的增删、指定位置之前之后的插入、指定位置的删除、链表销毁)
- 13.双向链表的完整代码实现
1.移除链表元素


cpp
//思路1
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
// 处理头节点:如果头节点值为 val,直接删除头节点
while (head != NULL && head->val == val) {
struct ListNode *temp = head;
head = head->next;
free(temp);
}
if (head == NULL) {
return NULL;
}
// 遍历剩余节点:prev 跟踪前驱,cur 遍历当前节点
struct ListNode *prev = head;
struct ListNode *cur = head->next;
while (cur != NULL) {
if (cur->val == val) {
prev->next = cur->next;
struct ListNode *temp = cur;
cur = cur->next;
free(temp);
} else {
prev = cur;
cur = cur->next;
}
}
return head;
}//时间复杂度为:O(N)
cpp
//思路2
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* newHead, *newTail;
newHead=newTail=NULL;
ListNode* pcur=head;
while(pcur)
{
if(pcur->val !=val)
{
if(newHead ==NULL)
{
newHead=newTail=pcur;
}else{
newTail->next=pcur;
newTail=newTail->next;
}
}
pcur=pcur->next;
}
if(newTail)
newTail->next=NULL;
return newHead;
}//时间复杂度为:O(N)
2.反转链表


cpp
//思路1
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *newHead = NULL;
struct ListNode *cur = head;
while (cur != NULL) {
struct ListNode *nextNode = cur->next;
cur->next = newHead;
newHead = cur;
cur = nextNode;
}
return newHead;
}//时间复杂度为:O(N)
cpp
//思路2(三指针法)
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL)
{
return head;
}
ListNode* n1,*n2,*n3;
n1=NULL,n2=head,n3=n2->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
return n1;
}//时间复杂度为:O(N)
3.链表的中间结点


cpp
//思路1
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
if (head == NULL) {
return NULL; // 空链表直接返回NULL
}
// 第一步:计算链表总长度
int len = 0;
struct ListNode *cur = head;
while (cur != NULL) {
len++;
cur = cur->next;
}
// 第二步:计算中间节点的位置(从0开始计数)
int midPos = len / 2;
// 第三步:遍历到中间位置的节点
cur = head; // 重置cur到表头
for (int i = 0; i < midPos; i++) {
cur = cur->next;
}
return cur; // 返回中间节点
}//时间复杂度为:O(N)
cpp
//思路2(快慢指针法)
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* slow =head;
ListNode* fast =head;
while(fast&&fast->next)
{
slow =slow->next;
fast =fast->next->next;
}
return slow;
}//时间复杂度为:O(N)
4.合并两个有序链表


cpp
//思路1
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
ListNode* dummy = (ListNode*)malloc(sizeof(ListNode));//创建"哨兵"节点
dummy->next = NULL;
ListNode* prev = dummy;
ListNode* l1 = list1;
ListNode* l2 = list2;
while (l1 != NULL && l2 != NULL) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
if (l1 != NULL) {
prev->next = l1;
} else {
prev->next = l2;
}
ListNode* newHead = dummy->next;
free(dummy);
return newHead;
}
cpp
//思路2
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if(list1 ==NULL)
{
return list2;
}
if(list2 ==NULL)
{
return list1;
}
//创建空链表
ListNode* newHead,*newTail;
newHead =newTail = NULL;
ListNode* l1 =list1;
ListNode* l2 =list2;
while(l1 && l2)
{
if(l1->val <l2->val)
{
//l1尾插到新链表中
if(newHead == NULL)
{
newHead =newTail =l1;
}else{
//链表为空
newTail->next =l1;
newTail=newTail->next;
}
l1 =l1->next;
}else{
//l2尾插到新链表中
if(newHead == NULL)
{
newHead =newTail =l2;
}else{
newTail->next =l2;
newTail =newTail->next;
}
l2 =l2->next;
}
}
//要么l1为空 要么l2为空
if(l2)
{
newTail->next =l2;
}
if(l1)
{
newTail->next =l1;
}
return newHead;
}//当我们写完这段代码时会发现在while循环中有重复的部分,因为链表为空的情况,导致代码冗长,那么有没有办法解决这个问题呢?让代码量少一点
cpp
//思路2的修改优化:创建非空链表
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if(list1 ==NULL)
{
return list2;
}
if(list2 ==NULL)
{
return list1;
}
//创建非空链表
ListNode* newHead,*newTail;
newHead=newTail=(ListNode*)malloc(sizeof(ListNode));
ListNode* l1 =list1;
ListNode* l2 =list2;
while(l1 && l2)
{
if(l1->val <l2->val)
{
//l1尾插到新链表中
newTail->next =l1;
newTail=newTail->next;
l1 =l1->next;
}else{
//l1尾插到新链表中
newTail->next =l2;
newTail =newTail->next;
l2 =l2->next;
}
}
//要么l1为空 要么l2为空
if(l2)
{
newTail->next =l2;
}
if(l1)
{
newTail->next =l1;
}
ListNode* retHead =newHead->next;
free(newHead);
newHead =NULL;
return retHead;
}
5.链表的回文结构


cpp
//思路1
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
//创建数组->900
int arr[900]={0};
//遍历链表将链表中的值保存在数组中
ListNode* pcur =A;
int i= 0;
while(pcur){
arr[i++]=pcur->val;
pcur =pcur->next;
}
//判断数组是否为回文结构
int left =0;
int right =i-1;
while(left<right)
{
if(arr[left] !=arr[right])
{
return left;
}
left++;
right--;
}
return true;
}
};
cpp
//思路2
class PalindromeList{
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow, *fast;
slow = fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//反转链表
ListNode* reverseList(ListNode* head) {
//链表为空
if (head == NULL) {
return head;
}
//创建三个指针
ListNode* n1, *n2, *n3;
n1 = NULL, n2 = head, n3 = n2->next;
while (n2) {
n2->next = n1;
n1 = n2;
n2 = n3;
if (n3)
n3 = n3->next;
}
return n1;
}
bool chkPalindrome(ListNode* A) {
//找中间节点
ListNode* mid = middleNode(A);
//反转中间结点之后的链表
ListNode* right = reverseList(mid);
//遍历原链表和反转后的链表
ListNode* left = A;
while (right) {
if (left->val != right->val) {
return false;
}
left = left->next;
right = right->next;
}
return true;
}
};
6.相交链表


cpp
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//求两个链表的长度
ListNode * pa = headA;
ListNode * pb = headB;
int sizeA = 0, sizeB = 0;
while(pa)
{
sizeA++;
pa = pa->next;
}
while(pb)
{
sizeB++;
pb = pb->next;
}
//计算长度差
int gap = abs(sizeA-sizeB);//abs函数:用于求绝对值
//让长链表先走gap步
ListNode* shortList = headA;
ListNode* longList = headB;
if(sizeA > sizeB)
{
longList = headA;
shortList = headB;
}
while(gap--)
{
longList = longList->next;
}
//longList shortList在同一起跑线
while(shortList)
{
if(longList == shortList)
{
return longList;
}
shortList = shortList->next;
longList = longList->next;
}
return NULL;
}
7.环形链表

cpp
//方法:快慢指针,即慢指针⼀次⾛⼀步,快指针⼀次⾛两步,两个指针从链表起始位置开始运⾏,如果链表带环则⼀定会在环中相遇,否则快指针率先⾛到链表的未尾
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
//快慢指针
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//相遇
return true;
}
}
return false;
}
思考1:为什么快指针每次⾛两步,慢指针⾛⼀步可以相遇,有没有可能遇不上?
思考2:快指针⼀次⾛3步,⾛4步,...n步⾏吗?请看下面的推理证明!
温馨提示:虽然已经证明了快指针不论⾛多少步都可以满⾜在带环链表中相遇,但是在编写代码的时候会有额外的步骤引⼊,涉及到快慢指针的算法题中通常习惯使⽤慢指针⾛⼀步快指针⾛两步的⽅式.
8.环形链表||

cpp
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
//快慢指针
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//找相遇点
//相遇点和头结点到入环第一个结点的距离相等
ListNode* pcur = head;
//pcur slow/fast
while(pcur != slow)
{
pcur = pcur->next;
slow = slow->next;
}
//pcur == slow
return pcur;
}
}
return NULL;
}
通过这题我们可以得出一个结论:让⼀个指针从链表起始位置开始遍历链表,同时让⼀个指针从判环时相遇点的位置开始绕环运⾏,两个指针都是每次均⾛⼀步,最终肯定会在⼊⼝点的位置相遇.换句话说就是:快慢指针相遇点和头结点到入环起始节点的距离是相等的.
9.链表分割


cpp
#include <cstddef>
#include <cstdlib>
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
//创建两个带头的空链表
ListNode* lessHead,*lessTail;
lessHead =lessTail=(ListNode*)malloc(sizeof(ListNode));
ListNode* greaterHead,*greaterTail;
greaterHead=greaterTail=(ListNode*)malloc(sizeof(ListNode));
ListNode*pcur =pHead;
while(pcur){
if(pcur->val<x){
lessTail->next=pcur;
lessTail=lessTail->next;
}else{
greaterTail->next=pcur;
greaterTail=greaterTail->next;
}
pcur=pcur->next;
}
//大链表尾结点的next指针置为NULL(避免死循环)
greaterTail->next=NULL;
//大小链表首尾相连
lessTail->next=greaterHead->next;
ListNode* ret =lessHead->next;
free(lessHead);
free(greaterHead);
return ret;
}
};
10.随机链表的复制


cpp
typedef struct Node Node;
Node* buyNode(int x)
{
Node* newnode =(Node*)malloc(sizeof(Node));
newnode->val=x;
newnode->next=newnode->random=NULL;
return newnode;
}
void AddNode(Node*head)
{
Node* pcur =head;
while(pcur)
{
Node* newnode=buyNode(pcur->val);
Node* next=pcur->next;
newnode->next=next;
pcur->next=newnode;
pcur=next;
}
}
void setRandom(Node* head)
{
Node* pcur =head;
while(pcur)
{
Node* copy =pcur->next;
if(pcur->random)
copy->random =pcur->random->next;
pcur=copy->next;
}
}
struct Node* copyRandomList(struct Node* head) {
if(head==NULL)
{
return head;
}
//在原链表基础上拷贝结点并插入到原链表中
AddNode(head);
//设置random
setRandom(head);
//断开新链表
Node* pcur =head;
Node* copyHead,*copyTail;
copyHead =copyTail=pcur->next;
while(copyTail->next)
{
pcur =copyTail->next;
copyTail->next =pcur->next;
copyTail =copyTail->next;
}
return copyHead;
}
11.双向链表的概念和结构
双向链表是一种链表数据结构,其中每个节点包含三个部分:前驱指针、数据域和后继指针.前驱指针指向前一个节点,后继指针指向下一个节点.
双向链表的节点结构:
1️⃣前驱指针域:用于存放指向上一个节点的指针.
2️⃣数据域:用于存储节点的数据元素.
3️⃣后继指针域:用于存放指向下一个节点的指针.
注意:这⾥的"带头"跟前⾯我说的"头结点"是两个概念,实际前⾯的在单链表阶段称呼不是严谨,但是为了更好的理解就直接称为单链表的头结点.带头链表⾥的头结点,实际为"哨兵位",哨兵位结点不存储任何有效元素,只是站在这⾥"放哨的".所以准确来说单链表是:不带头单向不循环链表.
12.双向链表的实现(链表初始化、头尾的增删、指定位置之前之后的插入、指定位置的删除、链表销毁)
cpp
//双向链表的创建新节点
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为新节点分配内存空间
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;//存储数据x
newnode->next = newnode->prev = newnode;//双向指针均指向自己
return newnode;
}//链表的初始化
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);//调用方法创建-1的节点作为头节点
return phead;
}
cpp
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev(尾结点) newnode
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}

cpp
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}

cpp
//尾删
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}

cpp
//头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
//phead del del->next
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}

cpp
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}

cpp
// 在 pos位置之前插入数据
void LTInsertBefore(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
LTNode* pos_prev = pos->prev;
newnode->prev = pos_prev;
newnode->next = pos;
pos_prev->next = newnode;
pos->prev = newnode;
}
cpp
//删除pos位置的结点
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}

cpp
//销毁
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
13.双向链表的完整代码实现
List.c
cpp
#include"List.h"
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev(尾结点) newnode
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
//只有一个头结点的情况下,双向链表为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->next;
//phead del del->next
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没找到
return NULL;
}
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//删除pos位置的结点
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
List.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//双向链表的结构
typedef int LTDataType;
typedef struct ListNode {
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
void LTPrint(LTNode* phead);
//双向链表的初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//传二级:违背了接口一致性
//void LTDesTroy(LTNode** pphead);
//传一级:调用完成之后将实参手动置为NULL(推荐)
void LTDesTroy(LTNode* phead);
//头结点要发生改变,传二级
// 头结点不发生改变,传一级
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
bool LTEmpty(LTNode* phead);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的结点
void LTErase(LTNode* pos);
test.c
cpp
#include"List.h"
void test01()
{
LTNode* plist = NULL;
LTInit(&plist);
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTNode* find = LTFind(plist, 1);
if (find == NULL)
{
printf("δҵ\n");
}
else {
printf("ҵˣ\n");
}
LTInsert(find, 100);
LTErase(find);
LTPrint(plist);
LTDesTroy(&plist);
LTDesTroy(plist);
plist = NULL;
}
int main()
{
test01();
return 0;
}
敬请期待下一篇文章内容:数据结构之栈和队列!
以上就是今天的博客内容了,希望能够帮助到读者朋友们!
我们一起继续加油努力💪!一键三连🙏!!!
本篇完结,点赞收藏加关注,找到小编不迷路,感谢大家🙏🤝!




