1、链表的概念和结构
链表是物理存储结构上非连续,非顺序的存储结构,逻辑顺序是通过链表指针实现的。
链式结构在逻辑上是连续的,在物理上是不一定连续的。
链表的结构像火车一样,一节一节的,每节车厢都是独立存在的。
单链表结构:
struct SListNode
{
int data;//节点数据
struct SListNode*next;//存放下一个数据的地址(节点)
};
节点是从堆上申请空间的,从堆上申请的空间可能连续,可能不连续。
2、单链表的实现
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印
void SLTPrint(SLTNode*ps);
//尾插
void SLTPushBack(SLTNode**pps, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pps, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pps);
//头删
void SLTPopFront(SLTNode**pps);
//查找
SLTNode* SLTFind(SLTNode* ps, SLTDataType x);
//指定位置之前插入数据
void SLTInsert(SLTNode** pps, SLTNode* pos, SLTDataType x);
//指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pps, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pps);
#include"SList.h"
//打印
void SLTPrint(SLTNode* ps)
{
assert(ps);
SLTNode* pcur = ps;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;//动态内存只能用free释放,函数结束后并不会自动销毁
}
//尾插
void SLTPushBack(SLTNode** pps, SLTDataType x)
{
assert(pps);
SLTNode* newnode = SLTBuyNode(x);
if (*pps == NULL)
{
*pps = newnode;
}
else
{
//找尾
SLTNode* ptail = *pps;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//头插
void SLTPushFront(SLTNode** pps, SLTDataType x)
{
assert(pps);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pps;
*pps = newnode;
}
//尾删
void SLTPopBack(SLTNode** pps)
{
assert(pps&&*pps);
if ((*pps)->next == NULL)//->优先级比*高
{
free(*pps);
*pps = NULL;
}
else
{
SLTNode* prev = *pps;
SLTNode* ptail = *pps;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pps)
{
assert(pps && *pps);
if ((*pps)->next == NULL)
{
free(*pps);
*pps = NULL;
}
else
{
SLTNode* pcur = (*pps)->next;
free(*pps);
*pps = NULL;
*pps = pcur;
}
}
//查找
SLTNode* SLTFind(SLTNode* ps, SLTDataType x)
{
assert(ps);
SLTNode* pcur = ps;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//指定位置之前插入数据
void SLTInsert(SLTNode** pps, SLTNode* pos, SLTDataType x)
{
assert(pps&&*pps);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
if (pos == *pps)
{
SLTPushFront(pps, x);
}
else
{
SLTNode* prev = *pps;
while (prev->next != *pps)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = pos->next;
pos->next = newnode;
newnode->next = prev;
}
//删除pos节点
void SLTErase(SLTNode** pps, SLTNode* pos)
{
assert(pps && *pps);
assert(pos);
if (pos == *pps)
{
SLTPopFront(pps);
}
else
{
SLTNode* prev = *pps;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* prev = pos->next;
pos->next = prev->next;
free(prev);
prev = NULL;
}
//销毁链表
void SLTDestroy(SLTNode** pps)
{
assert(pps&&*pps);
SLTNode* pcur=*pps;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pps = NULL;
}
3、链表的分类
链表的结构有很多,有8种(2*2*2)链表结构:
分别为 带头/不带头 单向/双向 循环/不循环
带头就是链表一开始就有一个节点
双向就是节点可以指向下一个的节点以及上一个的节点(首尾节点除外)
循环就是尾节点的下一个节点指向首节点
链表种类有很多,但我们平常只使用无头单向非循环链表(单链表)和有头双向循环链表
4、单链表经典算法
反转链表:
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if(head==NULL)
{
return NULL;
}
ListNode*n1=NULL;
ListNode*n2=head;
ListNode*n3=head->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
return n1;
}
移除链表元素:
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode*newnode=NULL;
ListNode*tailnode=NULL;
ListNode*pcur=head;
while(pcur)
{
if(pcur->val!=val)
{
if(newnode==NULL)
{
newnode=tailnode=pcur;
}
else
{
tailnode->next=pcur;
tailnode=tailnode->next;
}
}
pcur=pcur->next;
}
if(newnode)
tailnode->next=NULL;
return newnode;
}
链表的中间节点:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
if(head==NULL)
{
return NULL;
}
ListNode*slow=head;
ListNode*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
环形链表的约瑟夫问题:
typedef struct ListNode ListNode;
ListNode*buyNode(int x)
{
ListNode*newnode=(ListNode*)malloc(sizeof(ListNode));
newnode->val=x;
newnode->next=NULL;
return newnode;
}
ListNode* createCircle(int n)
{
ListNode*phead=buyNode(1);
ListNode*ptail=phead;
for(int i=2;i<=n;i++)
{
ptail->next=buyNode(i);
ptail=ptail->next;
}
ptail->next=phead;
return ptail;
}
int ysf(int n, int m ) {
ListNode*prev=createCircle(n);
ListNode*pcur=prev->next;
int count=1;
while(prev->next!=prev)
{
if(count==m)
{
prev->next=pcur->next;
free(pcur);
pcur=prev->next;
count=1;
}
else
{
prev=pcur;
pcur=pcur->next;
count++;
}
}
return prev->val;
}
合并两个有序链表:
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=NULL;
ListNode*newtail=NULL;
ListNode*p1=list1;
ListNode*p2=list2;
while(p1&&p2)
{
if(p1->val<=p2->val)
{
if(newhead==NULL)
{
newhead=newtail=p1;
}
else
{
newtail->next=p1;
newtail=newtail->next;
}
p1=p1->next;
}
else
{
if(newhead==NULL)
{
newhead=newtail=p2;
}
else
{
newtail->next=p2;
newtail=newtail->next;
}
p2=p2->next;
}
}
if(p1==NULL)
{
while(p2)
{
newtail->next=p2;
newtail=newtail->next;
p2=p2->next;
}
}
if(p2==NULL)
{
while(p1)
{
newtail->next=p1;
newtail=newtail->next;
p1=p1->next;
}
}
return newhead;
}
5、双链表的实现
之前说双链表就是带头双向循环链表,带头与头结点不一样,这里的头结点为“哨兵位”,哨兵位节点不存储任何有效元素,只是用来放哨的,存在的意义就是防止遍历循环链表时出现死循环。
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//初始化
void LTInit(LTNode** pps);
//销毁
void LTDestroy(LTNode* ps);
//链表打印
void LTPrint(LTNode* ps);
//尾插
void LTPushBack(LTNode* ps, LTDataType x);
//头插
void LTPushFront(LTNode* ps, LTDataType x);
//尾删
void LTPopBack(LTNode* ps);
//头删
void LTPopFront(LTNode* ps);
//查找
LTNode* LTFind(LTNode* ps, LTDataType x);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos节点
void LTErase(LTNode* pos);
#include"List.h"
//初始化
LTNode* LTBuyNode(LTDataType x)
{
//申请节点
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(0);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
void LTInit(LTNode** pps)
{
//创建一个哨兵位
*pps = LTBuyNode(-1);
}
//销毁
void LTDestroy(LTNode* ps)
{
assert(ps);
LTNode* pcur = ps->next;
while (pcur != ps)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(ps);
ps = NULL;
}
//链表打印
void LTPrint(LTNode* ps)
{
assert(ps);
LTNode* pcur = ps->next;
while (pcur != ps)
{
printf("%d->", pcur->data);
pcur=pcur->next;
}
printf("\n");
}
//尾插,这里传一级是为了不改变哨兵位的地址
void LTPushBack(LTNode* ps, LTDataType x)//传二级也行,但不能修改哨兵位的地址
{
assert(ps);
LTNode* newnode = LTBuyNode(x);
newnode->prev = ps->prev;
newnode->next = ps;
ps->prev->next=newnode;
ps->prev = newnode;
}
//头插
void LTPushFront(LTNode* ps, LTDataType x)
{
assert(ps);
LTNode* newnode = LTBuyNode(x);
newnode->next = ps->next;
newnode->prev = ps;
ps->next->prev = newnode;
ps->next = newnode;
}
//尾删
void LTPopBack(LTNode* ps)
{
assert(ps&&ps->next!=ps);
LTNode* del = ps->prev;
del->prev->next = ps;
ps->prev = del->prev;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* ps)
{
assert(ps && ps->next != ps);
LTNode* del = ps->next;
ps->next = del->next;
del->next->prev = ps;
free(del);
del = NULL;
}
//查找
LTNode* LTFind(LTNode* ps, LTDataType x)
{
assert(ps);
LTNode* pcur = ps->next;
while (pcur != ps)
{
if (pcur->data = x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//删除pos节点
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
顺序表和链表的优缺点:
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上一定连续,物理上不一定连续 |
随机访问 | 支持O(1) | 不支持O(n) |
任意位置插入/删除元素 | 效率低O(n) | 只需要修改指针方向 |
插入 | 动态顺序表空间不够时,需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入/删除频繁 |
谢谢大家观看喽!