前言
顺序表中虽然代码简洁但是存在许多问题
- 中间/头部的插入删除,时间复杂度较高(O(N))
- 增容时需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
如何解决上述问题呢?这里我们引入链表
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
如下图
注意:
- 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
顺序表的功能实现
先创建三个文件SList.h(头文件),SList.c(函数文件),test.c(测试文件)
创建结点
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data; //存放该结点的数据
struct SListNode* next; //存放下一结点的地址
}SLTNode;
单链表打印
将所有结点的数据按顺序打印出来
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
单链表尾插
思路
- 遍历找到尾结点
- 创建新的结点
- 将新的结点连接到尾结点上
SLTNode* BuySListNode(SLTDataType x)
{
//开辟新结点
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//对新结点进行初始化
node->data = x;
node->next = NULL;
return node;
}
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
if (*pphead == NULL)
{
SLTNode* newnode = BuySListNode(x);
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
//找尾结点
while (tail->next != NULL)
{
tail = tail->next;
}
SLTNode* newnode = BuySListNode(x);
//连接
tail->next = newnode;
}
}
注意:这里可能会对首位结点进行修改需要传二级指针,如果传一级指针,在函数中修改首位结点无法返回给主程序(类似传址和传值问题)
检测
单链表头插
思路
- 创建一个新结点
- 将新结点插入链表头部
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//创建一个新结点
SLTNode* newnode = BuySListNode(x);
//头插
newnode->next = *pphead;
*pphead = newnode;
}
检测
单链表尾删
思路很简单找到尾结点释放就行了
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
//单个结点
if ((*pphead)->next == NULL)//->优先级比*高需要(*pphead)
{
free(*pphead);
*pphead = NULL;
}
//多个结点
else
{
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
//找尾结点
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
//释放
free(tail);
prev->next = NULL;
}
}
检测
单链表头删
将头结点删除释放,并把第二个结点置为头结点
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
(*pphead) = next;
}
检测
计算链表大小
遍历,计算结点个数
int SListSize(SLTNode* phead)
{
int size = 0;
SLTNode* cur = phead;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
检测
判断链表是否为空
这里需要引入一个新类型bool,如果该函数为真返回1,为假返回0,需要引入头文件<stdbool.h>
bool SListEmpty(SLTNode* phead)
{
return phead == NULL;
}
查找某个值
思路
- 遍历链表
- 将结点的值和要找的值进行比较,相同返回该结点的坐标
- 如果没找到返回空指针
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)
{
assert(pphead);
assert(pos);
//如果在第一个结点前插入就首插
if (*pphead == pos)
{
SListPushFront(pphead, x);
}
else
{
//找到pos前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
newnode->next = pos;
prev->next = newnode;
}
}
检测
指定位置删除
思路
- 找到目标结点的前一个结点,和后一个结点
- 将目标结点的前后结点相连
- 删除目标结点
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
//一个结点时相当于头删
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
检测
单链表的销毁
将单链表依次销毁
void SListDestory(SLTNode** plist)
{
assert(plist);
SLTNode* cur = *plist;
//将单链表依次销毁
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*plist = NULL;
}
检测
总结
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include <stdbool.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//计算链表大小
int SListSize(SLTNode* phead);
//判断链表是否为空
bool SListEmpty(SLTNode* phead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//pos位置前插
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// 在pos位置后面去插入x
void SListInsertAfter(SLTNode* pos, SLTDataType x);
// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos后的值
void SListEraseAfter(SLTNode* pos);
// 单链表的销毁
void SListDestory(SLTNode** plist);
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* BuySListNode(SLTDataType x)
{
//开辟新结点
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//对新结点进行初始化
node->data = x;
node->next = NULL;
return node;
}
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
if (*pphead == NULL)
{
SLTNode* newnode = BuySListNode(x);
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
//找尾结点
while (tail->next != NULL)
{
tail = tail->next;
}
SLTNode* newnode = BuySListNode(x);
//连接
tail->next = newnode;
}
}
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//创建一个新结点
SLTNode* newnode = BuySListNode(x);
//头插
newnode->next = *pphead;
*pphead = newnode;
}
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
//单个结点
if ((*pphead)->next == NULL)//->优先级比*高需要(*pphead)
{
free(*pphead);
*pphead = NULL;
}
//多个结点
else
{
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
//找尾结点
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
//释放
free(tail);
prev->next = NULL;
}
}
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
(*pphead) = next;
}
int SListSize(SLTNode* phead)
{
int size = 0;
SLTNode* cur = phead;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
bool SListEmpty(SLTNode* phead)
{
return phead == NULL;
}
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)
{
assert(pphead);
assert(pos);
//如果在第一个结点前插入就首插
if (*pphead == pos)
{
SListPushFront(pphead, x);
}
else
{
//找到pos前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
newnode->next = pos;
prev->next = newnode;
}
}
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
//一个结点时相当于头删
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
void SListDestory(SLTNode** plist)
{
assert(plist);
SLTNode* cur = *plist;
//将单链表依次销毁
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*plist = NULL;
}