前言
在学习链表之前,已经学习过了顺序表,没有接触过的同学请点击下方链接:
单链表的定义:
- 由于顺序表的插入删除操作需要移动大量的元素,影响了运行效率,因此引入了线性表的链式存储——单链表。单链表通过一组任意的存储单元来存储线性表中的数据元素,不需要使用地址连续的存储单元,因此它不要求在逻辑上相邻的两个元素在物理位置上也相。
- 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
在链表(SListl),每个节点(SListNode)都存储着一个数据(data),还有一个指向下个节点的指针(next)。
单链表的优缺点:
- 单链表的优点:1、元素的存储单元是任意的,可连续也可不连续。2、可以按照实际所需创建结点增减链表的长度,更大程度地使用内存 。
- 单链表的缺点:1、进行尾部或者任意位置上插入或删除时时间复杂度和空间复杂度较大。2、存放元素时需要另外开辟一个指针域的空间。
顺序表的优缺点:
- 优点:可以通过下标直接访问所需要的数据。
- 缺点:不能按实际所需分配内存,只能使用malloc或者realloc函数进行扩容,容易实现频繁扩容,容易导致内存浪费与数据泄露等问题。
单链表的基本操作:
单链表的打印、尾插、头插、尾删、头删、销毁这些较为简单的操作,还有更为重要的单链表的查找、插入、在pos后插入、删除入和删除pos后的一个建立单链表。
单链表的实现
结构定义、接口函数的声明
常规定义下的单链表,一般包含数据域和指针域。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
void SListPrint(SLTNode* phead); //打印
void SListPushBack(SLTNode** phead, SLTDateType x); //尾插
void SListPushFront(SLTNode** phead, SLTDateType x); //头插
void SListPopBack(SLTNode** phead); //尾删
void SListPopFront(SLTNode** phead); //头删
SLTNode* SListFind(SLTNode* phead, SLTDateType x); //查找
void SListInsert(SLTNode** phead,SLTNode* pos, SLTDateType x); //插入
void SListInsertAfter(SLTNode* po, SLTDateType x); //在pos的后面插入
void SListErease(SLTNode** phead, SLTNode* pos); //删除
void SListEreaseAfter(SLTNode* pos); //删除pos后面一个
void SListDestory(SLTNode** phead); //销毁
封装结点
将结点封装成函数,等我们后面需要插入时调用此接口就可以了,提高代码的复用性。
SLTNode* CreateListNode(SLTDateType x) //封装结点
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
打印输出
这个不用过多解释,直接整。
void SListPrint(SLTNode* phead) //打印
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d-> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
尾插
通过遍历链表找到尾节点,并将新节点链接到尾节点之后,实现了新元素的添加。
void SListPushBack(SLTNode** phead, SLTDateType x) //尾插
{
assert(phead);
SLTNode* newnode= CreateListNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
//找到尾节点
SLTNode* tail = *phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
尾删
需要分情况进行判断
在链表为空或只有一个节点时,直接释放相应内存空间即可;否则通过遍历找到尾节点,并释放其空间,然后将前一个节点的 next 指针指向 NULL。
void SListPopBack(SLTNode** phead) //尾删
{
//分情况
assert(*phead != NULL); //没有结点
if ((*phead)->next == NULL) //一个结点
{
free(*phead);
*phead = NULL;
}
else
{
//多个结点
SLTNode* tail = *phead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
头插
void SListPushFront(SLTNode** phead, SLTDateType x) //头插
{
assert(phead);
SLTNode* newnode = CreateListNode(x);
newnode->next = *phead;
*phead = newnode;
}
头删
void SListPopFront(SLTNode** phead) //头删
{
assert(*phead);
SLTNode* next = (*phead)->next;
free(*phead);
*phead = next;
}
查找值
SLTNode* SListFind(SLTNode* phead, SLTDateType x) //查找
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
在调用查找接口函数时也可以修改值,如果找到返回地址,可将此地址的值进行修改。
SLTNode* pos=SListFind(plist,3); //查找
int i = 1;
while (pos)
{
printf("找到第%d个,地址为%p->%d\n", i++, pos, pos->data);
pos = SListFind(pos->next, 3);
}
pos = SListFind(plist, 3); //修改
while(pos)
{
pos->data = 30;
pos = SListFind(pos->next, 3);
}
SListPrint(plist);
在pos前插入
void SListInsert(SLTNode** phead, SLTNode* pos, SLTDateType x) //在pos前插入
{
assert(phead);
assert(pos);
SLTNode* newnode = CreateListNode(x);
if (*phead == pos) //第一个位置插入
{
newnode->next = *phead;
*phead = newnode;
}
else
{
//需要找到pos前一个位置
SLTNode* posPrev = *phead;
while (posPrev->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = newnode;
newnode->next = pos;
}
}
在pos后插入
//在pos的后面插入
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = CreateListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
删除
void SListErease(SLTNode** phead, SLTNode* pos) //删除
{
assert(phead);
assert(pos);
if (*phead==pos)
{
*phead = pos->next;
free(pos);
//SListPopFront(*phead); // //也可直接传函数
}
else
{
SLTNode* prev = *phead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
删除pos后面一个
void SListEreaseAfter(SLTNode* pos) //删除pos后面一个
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
销毁链表
void SListDestory(SLTNode** phead) //销毁链表
{
assert(phead);
SLTNode* cur = *phead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*phead = NULL;
}
---------------------------------------------------------------------------------------------------------------------------------
最后希望能帮助到更多同学,共同进步!