引入:
对于顺序表来说,开辟的空间是连续的,可以利用下标随机访问,而且尾插尾删时,时间复杂度是O(1),但是对于头插头删,以及中间插入和删除时,因为要挪动元素,所以时间复杂度是O(n)。而且因为空间是连续的,所以malloc开辟时,一旦空间不够,那么就要在内存空间中另找一块大的空间来存放。而且容量不够时要增容,所以会有一定的系统消耗。
而链表的特点:
1、空间按需索取
2、空间利用率提高
3、不要求物理空间连续,头部和中间的插入不需要挪动数据。
链表头插头删和中间插入和删除时间复杂度都是O(1)。
但缺点是不能随机访问,每次都只能从头开始一个个往后找,所以链表的尾插尾删的时间复杂度是O(n)。
下面是一个链表的代码(注释很详细就不再赘述了):
test.c(测试链表的接口用)
#include"SList.h"
void TestSList2()
{
SLTNode* plist = NULL;//创建一个结构体指针
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTNode* pos = SLTFind(plist, 3);
if (pos)
{
SLTEraseAfter(plist, pos);
}
SLTPrint(plist);
SLTDestory(&plist);
SLTPrint(plist);
}
int main()
{
TestSList2();
return 0;
}
SList.h(各种函数声明和一些#define定义)
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define SLTDataType int//将数据类型进行重命名,方便将来存储不同类型的数据
typedef struct SListNode
{
SLTDataType data;//数据域
struct SListNode* next;//指针域
}SLTNode;
void SLTPrint(SLTNode* phead);//打印
//打印链表不会改动头指针,所以传一级指针,函数内部形参的改变不会影响到外部的实参。
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
//链表尾插,有可能链表为空,此时就会改变头指针有,将动态开辟的新结点赋给头结点,此时函数内部的变化就需要
//传递到外部,所以用二级指针。
void SLTPopBack(SLTNode** pphead);//尾删
//尾删时可能只有头结点,所以可能会删掉头结点,所以函数内部的变化就需要
//传递到外部,所以用二级指针。
void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
//头插会改变头指针,所以要传二级指针。
void SLTPopFront(SLTNode** pphead);//头删
//头删会改变头指针,所以要传二级指针。
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//查改
//查改不会改变头指针,所以传一级指针。
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在pos位置之前插入
//之前插入会有改变头结点的可能,所以传二级指针,而pos是查找,不修改,所以传一级指针。
void SLTEraseAfter(SLTNode* phead, SLTNode* pos);//删除pos位置之后的元素
//因为是之后的位置,所以不会改变头结点所以是一级指针。
void SLTDestory(SLTNode** pphead);//销毁链表
//会删除头结点,所以传二级指针
SList.c(函数的实现)
#include "SList.h"
//新结点
SLTNode* BuyNode(SLTDataType x)
{
//动态开辟一个新结点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//判断新结点返回值是否为空,即开辟是否成功
assert(newnode);
//新开辟的结点的数据域改为传进来的值
newnode->data = x;
//新开辟的结点的指针域指向NULL。
newnode->next = NULL;
//返回该结点
return newnode;
}
//打印链表
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
//当当前指针不为空,就打印当前结构体内部的数据。
printf("%d->", cur->data);
//指针向后走
cur = cur->next;
}
//为空就打印NULL。
printf("NULL\n");
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);//断言二级指针是否为空,因为理论上来说,不管头指针是否为空,头指针的地址一定不为空
SLTNode* newnode = BuyNode(x);//申请一个新结点,因为后面各种插入都会申请新结点,所以就封装了一个函数
if (*pphead == NULL)
{
//如果头结点为空,就将申请的结点给头节点
(*pphead) = newnode;
}
else
{
//否则用一个tail指针找尾
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
//找到尾之后,将新结点链接到尾上。
tail->next = newnode;
}
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);//断言二级指针是否为空,因为理论上来说,不管头指针是否为空,头指针的地址一定不为空
assert(*pphead);//断言头指针是否为空
//定义两个指针,tail找尾,tailPrev是记录尾的前一个结点,方便最后的释放。
SLTNode* tail = *pphead;
SLTNode* tailPrev = NULL;
if (tail->next == NULL)
{
//处理一上来就是头结点的情况
free(*pphead);
*pphead = NULL;
}
else
{
while (tail->next != NULL)
{
//若tail->next != NULL,说明不是最后一个结点,更新tailPrev
tailPrev = tail;
tail = tail->next;
}
free(tail);//释放尾结点
tail = NULL;//置空
tailPrev->next = NULL;//原本指向tail的tailPrev的next指向NULL
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);//断言二级指针是否为空,因为理论上来说,不管头指针是否为空,头指针的地址一定不为空
assert(*pphead);//断言头指针是否为空
SLTNode* next = (*pphead)->next;//用一个next指针先记录头指针的下一个
free(*pphead);//释放头指针
*pphead = next;//头指针指向前面记录的next。
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNode(x);//申请一个结点
newnode->next = *pphead;//新结点的next指向头结点
*pphead = newnode;//头结点改为新结点
}
//查找,返回结点
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)//用cur指针遍历链表
{
if (cur->data == x)
{
//如果cur指针的数据域的值和所要查找的值相同,就返回该指针。
return cur;
}
else
{
//cur指针往后走
cur = cur->next;
}
}
//遍历之后发现没有符合条件的,就返回NULL。
return NULL;
}
//任意位置插入(在位置前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);//断言二级指针是否为空,因为理论上来说,不管头指针是否为空,头指针的地址一定不为空
assert(pos);//断言地址是否为空
if (pos == *pphead)//如果pos是头指针,那么就是头插
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = BuyNode(x);//申请一个新结点
SLTNode* preve = *pphead;
while (preve->next != pos)
{
//如果preve的next不为pos就继续向后走
preve = preve->next;
}
//将newnode链接到preve的next
preve->next = newnode;
//newnode的next链接到pos
newnode->next = pos;
}
}
//删除某个结点后的位置
void SLTEraseAfter(SLTNode* phead, SLTNode* pos)
{
assert(pos);//断言pos是否为空
SLTNode* tail = phead;
while (tail->next != pos)
{
tail = tail->next;
}
if (tail->next == NULL)
{
//处理pos是最后一个结点的情况
return;
}
//用delPrev来记录要删除的前一个结点
SLTNode* delPrev = tail->next;
//释放要删除的结点
free(delPrev->next);
//置空
delPrev->next = NULL;
}
//删除链表
void SLTDestory(SLTNode** pphead)
{
assert(pphead);//断言二级指针是否为空,因为理论上来说,不管头指针是否为空,头指针的地址一定不为空
while (*pphead)
{
SLTPopBack(pphead);//调用尾删即可
}
}
水平有限,欢迎指正。