前言
上一篇文章中我们学习了顺序表
顺序表是用数组实现的,
优势是:物理空间连续并且支持下标随机访问。
劣势是:1.空间不够,需要扩容。但是扩容有一定的性能消耗,其次一般扩容2倍,存在一些空间浪费。
2.头部或者中间插入删除效率低,需要挪动数据
针对以上问题,链表正好可以解决这些劣势。
链表优点:1.任意位置插入删除O(1) 2.按需申请和释放空间
链表的缺点:不能够支持随机访问
文章目录
一、单链表的概念和结构
概念: 链表是一种物理存储结构上 非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
结构: 单链表的结构类似于火车一样链起来的。
看代码
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
首先我们定义了一个结构体,结构体里的data被叫做数据域用来存放数据,next是指针域用来存放下一个节点的地址。
链接关系的代码表示:
SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n1);
SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n2);
SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n3);
SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n4);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n1->next = n2;
n2->next = n3;
n3->next = n4;
我在这里开辟了四个节点,图示为下面的样子:
n1的data用来存放数据,next用来存放n2的地址,n2、n3、n4以此类推。
二. 单链表的接口实现
我们要实现如下的一些接口:
注意:我们这里的单链表是不带头的,一般对于Leetcode上考察比较多的也是单链表,常用于哈希桶中的。
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList** plist);
2.1 打印
*注意:头指针的位置不要轻易的去改动,所以在这定义了一个Cur来代替我们的头指针。
代码示例如下
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur=plist;
while(cur)
{
printf("%d->",cur->data);
cur=cur->next;
}
printf("NULL\n"); }
2.2 开辟节点
**当我们插入的时候需要开辟新的节点,而我们有头插、尾插和任意位置插,每次写这些插入的时候都要<br />
申请节点,所以我们不妨写一个函数就直接申请节点,后面只需要调用就好了**
代码实现:
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode=(SListNode*) malloc(sizeof(SListNode));
assert(newnode);
newnode->data=x;
newnode->next=NULL;
return newnode;
}
2.3 尾部插入节点
首先我们要思考一下传参是传链表地址呢?还是传链表就行了?
因为要改变链表的指针,所以这个时候一定要传地址,而且因为是要修改链表的指针,所以传的是二级指针。
要尾插的话,我们首先得要开辟一个结点才能插啊,其次是要找到尾结点,那么如何找到尾结点呢?很简单,一个while循环就搞定了。
1、空节点的话,直接就插入,不用找尾了。2、非空的话就要找尾之后再删除
代码示例如下:
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode=BuySListNode(x);
if(*pplist==NULL)
{
*pplist=newnode;
}
else
{
SListNode* tail= * pplist;
while(tail->next)
{
tail=tail->next;
}
tail->next=newnode;
}
}
2.4 尾部删除节点
尾删分两种情况:1、只有一个结点就不用找尾了。2、有多个结点就要找尾之后再删除
示例代码如下:
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);//空节点
if((*pplist)->next==NULL)
{
free(*pplist);
*pplist=NULL;
}
else
{
SListNode* tail=*pplist;
while(tail->next)
{
SListNode* prev=tail;
tail=tail->next;
}
free(tail);
prev->next=NULL;
}
}
2.5 头部插入节点
这里我们也是需要用到二级指针的。头插相比于尾插来说是更简单的。
示例代码如下:
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode= BuySListNode(x);
newnode->next=*pplist;
*pplist=newnode;
}
2.6 头部删除节点
这里我们也是需要用到二级指针的
示例代码如下:
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);//空节点的情况
SListNode* next=(* pplist)->next;
free(*pplist);
*pplist=next;
}
2.7 查找
这里可以不用二级指针,因为查找和打印一样它是不会改变你传过来的值的。
这个地方是可以和 修改一起共用的。
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur=plist;
while(cur)
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
2.8 在Pos位置后插入
在pos位置之后插入,不需要直接找尾,新建一个节点,直接和POS 位置链接就可以了
这里就不用考虑pos是头的情况了,反正是在pos位置之后插入。
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode= BuySListNode(x);
newnode->next=pos->next;
pos->next=newnode;
}
2.9 在Pos位置后删除
如果pos位置是尾的话,直接返回就行了,因为pos位置之后没有什么可以删的了
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
SListNode* next=pos->next->next;
free(pos->next);
pos->next=next;
}
2.10 销毁链表
注意:这里因为是要修改链表,所以要用到二级指针,,
// 单链表的销毁
void SListDestroy(SList** plist)
{
SListNode* cur=*plist;
while(cur)
{
SListNode* next=cur->next;
free(cur);
cur=next;
}
}
总结
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;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList** plist);
SList.c
完整代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode=(SListNode*) malloc(sizeof(SListNode));
assert(newnode);
newnode->data=x;
newnode->next=NULL;
return newnode;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur=plist;
while(cur)
{
printf("%d\n",cur->data);
cur=cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode=BuySListNode(x);
if(*pplist==NULL)
{
*pplist=newnode;
}
else
{
SListNode* tail= * pplist;
while(tail->next)
{
tail=tail->next;
}
tail->next=newnode;
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode= BuySListNode(x);
newnode->next=*pplist;
*pplist=newnode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
if((*pplist)->next==NULL)
{
free(*pplist);
*pplist=NULL;
}
else
{
SListNode* tail=*pplist;
while(tail->next)
{
SListNode* prev=tail;
tail=tail->next;
}
free(tail);
prev->next=NULL;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* next=(* pplist)->next;
free(*pplist);
*pplist=next;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur=plist;
while(cur)
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode= BuySListNode(x);
newnode->next=pos->next;
pos->next=newnode;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
SListNode* next=pos->next->next;
free(pos->next);
pos->next=next;
}
// 单链表的销毁
void SListDestroy(SList** plist)
{
SListNode* cur=*plist;
while(cur)
{
SListNode* next=cur->next;
free(cur);
cur=next;
}
}
其实单链表还不是最好的,还有一些缺陷,那就需要用到双链表,想知道知道如何写,请期待博主的下一篇新作。
码字不易,如果觉得内容有用的话,就给博主三连吧!!!