1.链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
2. 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了
3. 单链表的实现
首先在VS里面的源文件建立test.c和SList.c,在头文件里面建立SList.h
SList.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
3.1 打印
SList.h:
void SListPrint(SLTNode* phead);//打印
SList.c:
#include "SList.h"
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
3.2 申请结点
SList.h:
SLTNode* BuyListNode(SLTDataType x);//动态申请结点
SList.c:
SLTNode* BuyListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3.3 尾插
SList.h:
void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
SList.c:
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
assert(*pphead != NULL);
//找到尾结点
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
test.c:
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
}
int main()
{
TestSList1();
return 0;
}
运行结果:
3.4 头插
SList.h:
void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
SList.c:
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newcode = BuyListNode(x);
newcode->next = *pphead;
*pphead = newcode;
}
test.c:
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
}
int main()
{
TestSList1();
return 0;
}
运行结果:
3.5 尾删
SList.h:
void SLisePopBack(SLTNode** pphead);//尾删
SList.c:
void SLisePopBack(SLTNode** pphead)
{
assert(*pphead != NULL);
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;//一定要置空
}
test.c:
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
SLisePopBack(&plist);
SListPrint(plist);
}
int main()
{
TestSList1();
return 0;
}
运行结果:
3.6 头删
SList.h:
void SListPopFront(SLTNode** pphead);//头删
SList.c:
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
test.c:
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
SLisePopBack(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPrint(plist);
}
int main()
{
TestSList1();
return 0;
}
运行结果:
3.7 查找
SList.h:
SLTNode* SListFind(SLTNode* phead, SLTDataType x);//查找
SList.c:
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
test.c:
#include "SList.h"
void TestSList2()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 2);
//Find接口有两个作用
//1.找 2.修改
SLTNode* pos = SListFind(plist, 2);
int i = 1;
while (pos)
{
printf("第%d个pos结点:%p->%d\n", i++, pos, pos->data);
pos = SListFind(pos->next, 2);
}
}
int main()
{
TestSList2();
return 0;
}
3.8 插入
3.8.1 前插
SList.h:
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//前插
SList.c:
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
//相当于头插
if (*pphead == pos)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
//找到pos前一个位置
SLTNode* posPrew = *pphead;
while (posPrew->next != pos)
{
posPrew = posPrew->next;
}
posPrew->next = newnode;
newnode->next = pos;
}
}
test.c:
#include "SList.h"
void TestSList3()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SLTNode* pos = SListFind(plist, 2);
if (pos)
{
SListInsert(&plist, pos,20);
}
SListPrint(plist);
}
int main()
{
TestSList3();
return 0;
}
运行结果:
3.8.2 后插
c++的STL库支持后插,一般我们单链表使用的都是后插。其次前插有一定的效率损失,而后插就没有损失。
SList.h:
void SListInsertAfter(SLTNode* pos, SLTDataType x);//后插
SList.c:
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
test.c:
#include "SList.h"
void TestSList4()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListInsertAfter(plist->next, 20);
SListPrint(plist);
}
int main()
{
TestSList4();
return 0;
}
运行结果:
3.9删除
SList.h:
void SListErase(SLTNode** pphead, SLTNode* pos);//删除
SList.c:
void SListErase(SLTNode** pphead, SLTNode* pos)
{
//头删,也可直接调用SListPopFront接口
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
pos = NULL;//也可以不置空
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;//也可以不置空
}
}
test.c:
#include "SList.h"
void TestSList4()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListInsertAfter(plist->next, 20);
SListPrint(plist);
SListErase(&plist, plist->next->next);
SListPrint(plist);
}
int main()
{
TestSList4();
return 0;
}
3.10 销毁
SList.h:
void SListDestory(SLTNode** pphead);//销毁
SList.c:
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
4.完整代码展示
SList.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SListPrint(SLTNode* phead);//打印
SLTNode* BuyListNode(SLTDataType x);//动态申请结点
void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLisePopBack(SLTNode** pphead);//尾删
void SListPopFront(SLTNode** pphead);//头删
SLTNode* SListFind(SLTNode* phead, SLTDataType x);//查找
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//前插
void SListInsertAfter(SLTNode* pos, SLTDataType x);//后插
void SListErase(SLTNode** pphead, SLTNode* pos);//删除
void SListDestory(SLTNode** pphead);//销毁
SList.c:
#include "SList.h"
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLTNode* BuyListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找到尾结点
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newcode = BuyListNode(x);
newcode->next = *pphead;
*pphead = newcode;
}
void SLisePopBack(SLTNode** pphead)
{
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
if (*pphead == pos)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
//找到pos前一个位置
SLTNode* posPrew = *pphead;
while (posPrew->next != pos)
{
posPrew = posPrew->next;
}
posPrew->next = newnode;
newnode->next = pos;
}
}
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
//头删,也可直接调用SListPopFront接口
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
pos = NULL;//也可以不置空
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;//也可以不置空
}
}
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
test.c:
#include "SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);
SLisePopBack(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPrint(plist);
}
void TestSList2()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 2);
//Find接口有两个作用
//1.找 2.修改
SLTNode* pos = SListFind(plist, 2);
int i = 1;
while (pos)
{
printf("第%d个pos结点:%p->%d\n", i++, pos, pos->data);
pos = SListFind(pos->next, 2);
}
}
void TestSList3()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SLTNode* pos = SListFind(plist, 2);
if (pos)
{
SListInsert(&plist, pos,20);
}
SListPrint(plist);
}
void TestSList4()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);
SListInsertAfter(plist->next, 20);
SListPrint(plist);
SListErase(&plist, plist->next->next);
SListPrint(plist);
}
int main()
{
//TestSList1();
//TestSList2();
//TestSList3();
TestSList4();
return 0;
}
5.链表OJ 题
5.1 移除链表元素
删除链表中等于给定值 val 的所有节点。
链接:https://leetcode.cn/problems/remove-linked-list-elements/description/
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* prev=NULL,*cur=head;
while(cur)
{
if(cur->val==val)
{
if(cur==head)
{
//头删或者中间删除
head=cur->next;
free(cur);
cur=head;
}
else
{
prev->next=cur->next;
free(cur);
cur=prev->next;
}
}
else
{
//迭代向后走
prev=cur;
cur=cur->next;
}
}
return head;
}
5.2 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
链接:https://leetcode.cn/problems/reverse-linked-list/description/
法一:画图+调试
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL)
{
return NULL;
}
struct ListNode* n1,*n2,*n3;
n1=NULL;
n2=head;
n3=head->next;
while(n2)
{
//核心逻辑
n2->next=n1;
//迭代
n1=n2;
n2=n3;
if(n3!=NULL)
n3=n3->next;
}
return n1;
}
法二:头插法
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* cur=head;
struct ListNode* newhead=NULL;
while(cur)
{
struct ListNode* next =cur->next;
//头插
cur->next=newhead;
//迭代
newhead=cur;
cur=next;
}
return newhead;
}
5.3 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
链接:https://leetcode.cn/problems/middle-of-the-linked-list/description/
//步幅法
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
5.4 链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。
//千分尺法
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode* fast=pListHead,*slow=pListHead;
while(k--)
{
if ((fast==NULL)) {
return NULL;
}
fast=fast->next;
}
while(fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;;
}
5.5 合并链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
链接:https://leetcode.cn/problems/merge-two-sorted-lists/description/
思路:依次比较链表中的结点,每次取小的结点,尾插到新链表即可
法一:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
//如果链表为空,返回另一个
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
struct ListNode* head=NULL,*tail=NULL;
//先取小的做第一个
if(l1->val<l2->val)
{
head=tail=l1;
l1=l1->next;
}
else
{
head=tail=l2;
l2=l2->next;
}
while(l1&&l2)
{
if(l1->val<l2->val)
{
tail->next=l1;
tail=l1;
l1=l1->next;
}
else
{
tail->next=l2;
tail=l2;
l2=l2->next;
}
}
if(l1)
{
tail->next=l1;
}
if(l2)
{
tail->next=l2;
}
return head;
}
法二:哨兵结点法
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
//如果链表为空,返回另一个
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
struct ListNode* head=NULL,*tail=NULL;
//哨兵位的头结点
head=tail=(struct ListNode*)malloc(sizeof(struct ListNode));
while(l1&&l2)
{
if(l1->val<l2->val)
{
tail->next=l1;
tail=l1;
l1=l1->next;
}
else
{
tail->next=l2;
tail=l2;
l2=l2->next;
}
}
if(l1)
{
tail->next=l1;
}
if(l2)
{
tail->next=l2;
}
struct ListNode* list = head->next;
free(head);
return list;
}
5.6 链表分割
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail;
//开一个哨兵位头结点,方便尾插
lessHead=lessTail=(struct ListNode*)malloc((sizeof(struct ListNode)));
lessTail->next=NULL;
greaterHead=greaterTail=(struct ListNode*)malloc((sizeof(struct ListNode)));
greaterTail->next=NULL;
struct ListNode* cur=pHead;
while(cur)
{
if(cur->val<x)
{
lessTail->next=cur;
lessTail=cur;
}
else
{
greaterTail->next=cur;
greaterTail=cur;
}
cur=cur->next;
}
lessTail->next=greaterHead->next;
greaterTail->next=NULL;//易丢
struct ListNode* newHead=lessHead->next;
free(lessHead);
free(greaterHead);
return newHead;
}
};
5.7 链表的回文结构
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
struct ListNode* middleNode(struct ListNode* head) {
struct ListNode* slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
while (cur) {
struct ListNode* next = cur->next;
//头插
cur->next = newhead;
//迭代
newhead = cur;
cur = next;
}
return newhead;
}
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
struct ListNode* mid=middleNode(A);
struct ListNode* rHead=reverseList(mid);
struct ListNode* curA=A;
struct ListNode* curR=rHead;
while(curA&&curR)
{
if(curA->val!=curR->val)
{
return false;
}
else
{
curA=curA->next;
curR=curR->next;
}
}
return true;
}
};
5.8 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
链接:https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* tailA=headA;
struct ListNode* tailB=headB;
int lenA=1;
while(tailA->next)
{
lenA++;
tailA=tailA->next;
}
int lenB=1;
while(tailB->next)
{
lenB++;
tailB=tailB->next;
}
//不相交
if(tailA!=tailB)
{
return NULL;
}
int gap=abs(lenA-lenB);
//长的先走,之后再同时走
struct ListNode* longList=headA;
struct ListNode* shortList=headB;
if(lenA<lenB)
{
shortList=headA;
longList=headB;
}
while(gap--)
{
longList=longList->next;
}
while(longList!=shortList)
{
longList=longList->next;
shortList=shortList->next;
}
return longList;
}