在学习单链表之前我们已经学习了顺序表相关的基本操作,顺序表访问元素更加方便,物理地址是连续的;但是也有一些缺点:
1.在头部插入或者从中间插入或删除元素时需要搬移数据,效率较低
2.在插入数据时可能存在空间不足的情况,需要扩容
因此就会出现另一种线性表---链表
1.链表的概念
链表顾名思义就是链式的存储结构,元素的逻辑顺序是由指针来依次连接的。
链表中有多个节点,每一个节点里储存着数据,还有指向下一个节点的指针 。
2.单链表的定义
我们要实现的是不带头结点的非循环单向链表
单链表的结构包括数值域和指针域
typedef int DataType;
typedef struct SListNode
{
DataType data;
struct SListNode* next;//指向下一个节点
}SListNode;
3.单链表节点的创建
SListNode* BuySListNode(DataType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));//这个动态内存空间是用来存放这个结构体类型的
if (NULL == newNode)
{
printf("创建节点失败!!!!");
exit(0);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
4.单链表的尾插
void SListPushBack(SListNode** pplist,DataType x)
{
assert(*pplist);
//如果pplist为空则需要让pplist指向刚刚插入的节点
if (NULL == *pplist)
{
*pplist = BuySListNode(x);
}
else
{
//此时链表不为空
//1.构建一个新节点
SListNode* NewNode = BuySListNode(x);
//2.找出原链表中的最后一个节点
SListNode* cur = *pplist;
while (cur->next)
{
//获取cur的下一个节点
cur = cur->next;
}
//3.插入新节点
cur->next = NewNode;
}
}
5.单链表的尾删
//单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);//保证pplist指向实参
//1.空链表
if (NULL == *pplist)
{
return;
}
else if (NULL == (*pplist)->next)
{
//2.链表中只有一个节点
free(*pplist);
*pplist = NULL;
}
else
{
//3.链表中有多个节点
//a.找倒数第一个节点
SListNode* cur = *pplist;
SListNode* prev = NULL;//保存cur前一个节点
while (cur->next)
{
prev = cur;
cur = cur->next;
}
prev->next = NULL;
free(cur);
//b.找倒数第二个节点
/*
SListNode* cur = *pplist;
while(cur->next->next)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
*/
//c.错误写法
/*while (cur->next)
{
cur = cur->next;
}
free(cur);*/
}
}
6.单链表打印
//单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur)
{
printf("&d--->", cur->data);
cur = cur->next;
}
printf("NULL\n");//单链表最后一个节点指向空
}
7.单链表头插
// 单链表的头插
//时间复杂度:O(1)
void SListPushFront(SListNode** pplist, SLTDataType x)
{
//assert(pplist);
//SListNode* newNode = BuySListNode(x);
1.链表为空
//if (NULL == *pplist)
//{
// *pplist = newNode;
//}
//else
//{
// //2.链表不空
// newNode->next = *pplist;
// *pplist = newNode;
//}
assert(pplist);
//表面看上需要分情况讨论,但是在分析之后情况一可以与情况二合并
SListNode* newNode = BuySListNode(x);
newNode->next = *pplist;
*pplist = newNode;
}
8.单链表头删
//单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
//1.链表为空
if (NULL == *pplist)
{
return;
}
//else if (NULL == (*pplist)->next)
//{
// //2.链表只有一个节点
// free(*pplist);
// *pplist = NULL;
//}
else
{
//3.链表中有多个节点(分析后可知也包含一个节点的情况)
SListNode* del = *pplist;//将要删除的节点保存起来
*pplist = (*pplist)->next;
free(del);
}
}
9.单链表删除任意位置pos之后的值
// 单链表删除pos位置之后的值
//时间复杂度O(1)
void SListEraseAfter(SListNode* pos)
{
SListNode* delNode = NULL;
if (NULL == pos)
{
return;
}
delNode = pos->next;
if (NULL == delNode)
{
return;
}
//pos->data = delNode->data;
pos->next = delNode->next;
free(delNode);
}
代码中加一句pos->data = delNode->data就可以实现删除任意位置的值
但是不能删除最后一个节点位置的元素
10.单链表在任意位置pos之后插入
//单链表在任意位置pos之后插入
//时间复杂度:O(1)
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
SListNode* newNode = NULL;
if (NULL == pos)
{
return;
}
newNode = BuySListNode(x);
newNode->next = pos->next;//必须先让新节点接入到链表内然后将pos位置的节点指向新节点
pos->next = newNode;
}
新节点无法插入到pos之前,因为该方法未提供plist头节点
11.单链表在任意位置pos之前插入
//单链表在任意位置pos之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
assert(pplist);
assert(pos);
SListNode* newNode = NULL;
if (pos == *pplist)
{
newNode = BuySListNode(x);
}
else
{
SListNode* cur = *pplist;//将头节点保存起来,**pplist是plist的地址,*plist才是解引用后的指针
while (cur->next != pos)
{
cur = cur->next;
}
newNode = BuySListNode(x);
newNode->next = pos;
cur->next = newNode;
}
}
11.单链表的销毁
//单链表销毁
void SListDestroy(SListNode** pplist)
{
assert(pplist);
SListNode* cur = *pplist;
while (cur)
{
*pplist = cur->next;
free(cur);
cur = *pplist;
}
*pplist = NULL;
}
单链表的销毁很简单,只需要用cur把链表遍历一遍,每次让pplist头指针指向cur->next,释放cur所指向的当前节点,然后更新pplist指针的值,最后将pplist指向NULL。
12.单链表操作的测试
void TestSList1()
{
SListNode* plist = NULL;//单链表的起始位置保存
//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
SListPrint(plist);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);//尾删
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushFront(&plist, 1);//依次头插1,2,3,4,5
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushFront(&plist, 5);
SListPrint(plist);
SListInsertAfter(SListFind(plist, 3), 7);//在3后面插入7
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertAfter(SListFind(plist, 1), 8);//在1后面插入8
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertAfter(SListFind(plist, 2), 9);//在2后面插入9
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 3));//删除3后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 1));//删除1后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 2));//删除2后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertBefore(&plist, SListFind(plist, 1), 6);//在1之前插入元素6
SListPrint(plist);
printf("节点数为 %d\n",SListSize(plist));
}
void TestSList2()
{
SListNode* plist = NULL;//单链表的起始位置保存
//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
SListPrint(plist);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);//尾删
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushFront(&plist, 1);//依次头插1,2,3,4,5
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushFront(&plist, 5);
SListPrint(plist);
SListPopFront(&plist);//头删
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
}
本文全部代码:
SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SListNode;
//动态申请一个节点
SListNode* BuySListNode(SLTDataType x);
//单链表的尾插
void SListPushBack(SListNode** pplist, SLTDataType x);
//单链表的尾删
void SListPopBack(SListNode** pplist);
//链表打印
void SListPrint(SListNode* plist);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDataType x);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** plist);
SList.c
#include "SList.h"
//动态申请一个节点
SListNode* BuySListNode(SLTDataType x)
{
SListNode* newNode = (SListNode *) malloc(sizeof(SListNode));//这个动态内存空间是用来存放这个结构体类型的
if (NULL == newNode)
{
printf("创建节点失败!!!!");
exit(0);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
//单链表的尾插
void SListPushBack(SListNode** pplist, SLTDataType x)
{
//若为空链表则需要让pplist指向刚刚插入的节点
if (NULL == *pplist)
{
*pplist = BuySListNode(x);
}
else
{
//此时链表不为空
//1.构建一个新节点
SListNode* newNode = BuySListNode(x);
//2.找原链表中最后一个节点
SListNode* cur = *pplist;
while (cur->next)
{
//获取cur的下一个节点
cur = cur->next;
}
//3.插入新节点
cur->next = newNode;
}
}
//单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
//1.空链表
if (NULL == *pplist)
{
return;
}
else if(NULL == (*pplist)->next)
{
//2.链表中只有一个节点
free(*pplist);
*pplist = NULL;
}
else
{
//3.链表中有多个节点
SListNode* cur = *pplist;//*pplist为第一个节点
while (cur->next->next)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
//方法2
/*
SListNode* cur = *pplist;//*pplist为第一个节点
SListNode* prev = NULL;
while (cur->next)
{
prve = cur;
cur = cur->next;
}
free(cur);
prve->next = NULL;
*/
}
}
//链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur)
{
printf("%d--->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表的头插
//时间复杂度:O(1)
void SListPushFront(SListNode** pplist, SLTDataType x)
{
assert(pplist);
SListNode* newNode = BuySListNode(x);
1.链表为空
//if (NULL == *pplist)
//{
// *pplist = newNode;
//}
//else
//{
// //2.链表不空
// newNode->next = *pplist;
// *pplist = newNode;
//}
//表面看上需要分情况讨论,但是在分析之后情况一可以与情况二合并
newNode->next = *pplist;
*pplist = newNode;
}
//单链表查找
SListNode* SListFind(SListNode* plist, SLTDataType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//单链表中的有效节点数
int SListSize(SListNode* plist)
{
int size = 0;
SListNode* cur = plist;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
//单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
SListNode* del = NULL;
//1.链表为空
if (NULL == *pplist)
{
return;
}
//else if (NULL == (*pplist)->next)
//{
// //2.链表只有一个节点
// free(*pplist);
// *pplist = NULL;
//}
else
{
//3.链表中有多个节点(分析后可知也包含一个节点的情况)
SListNode* del = *pplist;//将要删除的节点保存起来
*pplist = (*pplist)->next;
free(del);
}
}
//单链表在任意位置pos之后插入
//新节点无法插入到pos之前,因为该方法未提供plist头节点
//时间复杂度:O(1)
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
SListNode* newNode = NULL;
if (NULL == pos)
{
return;
}
newNode = BuySListNode(x);
newNode->next = pos->next;//必须先让新节点接入到链表内然后将pos位置的节点指向新节点
pos->next = newNode;
}
//单链表在任意位置pos之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
assert(pplist);
assert(pos);
SListNode* newNode = NULL;
if (pos == *pplist)
{
newNode = BuySListNode(x);
}
else
{
SListNode* cur = *pplist;//将头节点保存起来,**pplist是plist的地址,*plist才是解引用后的指针
while (cur->next != pos)
{
cur = cur->next;
}
newNode = BuySListNode(x);
newNode->next = pos;
cur->next = newNode;
}
}
// 单链表删除pos位置之后的值
//时间复杂度O(1)
void SListEraseAfter(SListNode* pos)
{
SListNode* delNode = NULL;
if (NULL == pos)
{
return;
}
delNode = pos->next;
if (NULL == delNode)
{
return;
}
//pos->data = delNode->data;
pos->next = delNode->next;
free(delNode);
}
//单链表销毁
void SListDestroy(SListNode** pplist)
{
assert(pplist);
SListNode* cur = *pplist;
while (cur)
{
*pplist = cur->next;
free(cur);
cur = *pplist;
}
*pplist = NULL;
}
void TestSList1()
{
SListNode* plist = NULL;//单链表的起始位置保存
//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
SListPrint(plist);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);//尾删
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushFront(&plist, 1);//依次头插1,2,3,4,5
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushFront(&plist, 5);
SListPrint(plist);
SListInsertAfter(SListFind(plist, 3), 7);//在3后面插入7
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertAfter(SListFind(plist, 1), 8);//在1后面插入8
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertAfter(SListFind(plist, 2), 9);//在2后面插入9
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 3));//删除3后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 1));//删除1后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListEraseAfter(SListFind(plist, 2));//删除2后面的元素
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListInsertBefore(&plist, SListFind(plist, 1), 6);//在1之前插入元素6
SListPrint(plist);
printf("节点数为 %d\n",SListSize(plist));
}
void TestSList2()
{
SListNode* plist = NULL;//单链表的起始位置保存
//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
SListPrint(plist);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);//尾删
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushFront(&plist, 1);//依次头插1,2,3,4,5
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushFront(&plist, 5);
SListPrint(plist);
SListPopFront(&plist);//头删
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
SListPopFront(&plist);
SListPrint(plist);
printf("节点数为 %d\n", SListSize(plist));
}
int main()
{
//TestSList1();
TestSList2();
return 0;
}