我们之前实现了顺序表,但顺序表有些功能不是完美的:
1.中间和尾部增加删除的时间复杂度是O(N);
2.增容用到realloc可能会进行,数据的拷贝,释放旧空间,消耗会很大;
3.增容时一般以原空间的2倍进行增容,如果原空间为100,我们现在只需要再用5个空间,但顺序表依旧会申请200个空间,有95个空间就会浪费。
链表对这些问题就可以得到很好的解决。
一、链表的概念及其结构
概念:链表在内存上不是连续存储的,每个元素都通过指针链接在一起。
二、链表的分类
1.单向和双向
2.带头和不带头
3.循环和非循环
在实际中我们常常只使用两种:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三、无头单向非循环链表的实现
类型定义:
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
SLDateType:与顺序表相同我们存储值data的类型typedef一下,这样在使用链表存储其他类型时,只需要改变被typedef修改的类型就行。
next:存储下一个结构体的地址。
动态申请一个节点:
SListNode* BuySListNode(SLTDateType x)
{
SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
在顺序表中我们只有在将realloc申请的空间使用完时才会再申请空间,而这可能会导致空间的浪费。可在链表中我们是每次插入值时才会为它申请一块空间,删除时就会释放掉不会导致空间的浪费。这也导致会在其他函数内常申请节点,对于这种一个程序内出现两次以上在功能,我们就可以封装一个函数来使用,方便修改。
申请节点时可以将要插入的值直接赋值进去,next可能是空或其他地址,这个函数也不需要考虑过多的功能,在其他函数内改变next更好。
尾插:
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = tmp;
return;
}
//找尾
SListNode* tail = *pplist;
while (tail->next)
{
tail = tail->next;
}
tail->next = tmp;
}
不带头链表要先创建一个头指针永久性的指向链表的头节点来控制链表。这个头指针在创建时是为空的,在插入节点时有两种情况:一、表里没有值,头节点就是尾节点也为空,就需要修改头指针的值;二、表里有值,在尾节点后插入。发生第一种情况就需要头节点的地址,要用二级指针来接收。
先确认pplist不为空,然后申请一块空间,再进行判断链表是否为空,是则将tmp赋值给*pplist结束函数;不是找到尾节点,将tmp赋值给尾节点的next。
尾删:
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
return;
}
SListNode* tail = *pplist;
while (tail->next->next)//找下个节点的下个节点为NULL的节点
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
尾删也是有两种情况:一、链表只有一个节点;二、链表有多个节点。
确认pplist不为空,判断是否只有一个值,是则释放*pplist,并置为空;不是就先找到尾节点的上一个节点,然后通过这个上一个节点将尾节点释放掉,并将next置为空。
头插:
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
tmp->next = *pplist;
*pplist = tmp;
}
将tmp的next赋值*pplist,然后*pplist赋值tmp,tmp做头节点。这段代码对一个节点也同时适用。
头删:
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* tmp = (*pplist)->next;
free(*pplist);
*pplist = tmp;
}
头节点是不可以为空的,先储存*pplist头节点的下一个节点,再对头节点进行释放,让头节点的下一个节点成为头节点。对一个节点同样适用。
查找:
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
while (plist)
{
if (plist->data == x)
{
return plist;
}
plist = plist->next;
}
printf("没有该值\n");
return NULL;
}
查找不需要改变头指针的值,参数有一级指针就可以,将链表遍历一遍,其中一个节点的data为x返回这个节点的地址,出了循环提醒没有该值,并返回NULL。
在pos位置之后插入x:
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* tmp = BuySListNode(x);
tmp->next = pos->next;
pos->next = tmp;
}
申请一个节点,然后将pos的下一个节赋值给tmp的next,tmp的下一个节点就是pos的下一个节点,再将pos的下一个节点next赋值tmp,就完成了插入,对pos的下一个节点是NULL也同样适用。
删除pos位置之后的值:
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
SListNode* nnext = pos->next->next;
free(pos->next);
pos->next = nnext;
}
判断pos的下一个节点是否为空,是那就没有意义,直接返回;不是,存储pos下一个节点的下一个节点,释放掉pos的下一个节点,pos的next赋值nnext(pos下一个节点的下一个节点)。
在pos的前面插入:
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
if (*pplist == pos)
{
*pplist = tmp;
(*pplist)->next = pos;
return tmp;
}
SListNode* cur = *pplist;
while (cur->next != pos && cur->next!=NULL)
{
cur = cur->next;
}
if (cur->next == NULL && pos != NULL)
{
printf("链表中没有pos\n");
return;
}
cur->next = tmp;
tmp->next = pos;
}
在pos前插入就要找到pos的前一个节点,我们就需要头节点,从头开始遍历。有三种情况要考虑:一、pos就是头节点;二、pos是链表中的其他节点;三、pos不在链表中。
先对pos是头节点进行判断,是,*pplist直接赋值tmp,tmp做头节点,*pplist的next在赋值pos将其链接起来,返回头节点;不是,那就先遍历一遍找到pos的前节点或尾节点(cur),在判断找到的是不是尾节点,是并且pos不是NULL,那么链表中就没有pos直接返回;否则直接在cur后面插入tmp。
删除pos位置:
void SLTErase(SListNode** pplist, SListNode* pos)
{
assert(*pplist);
assert(pos);
if (*pplist == pos)
{
*pplist = pos->next;
free(pos);
return;
}
SListNode* cur = *pplist;
while (cur->next != pos && cur)
{
cur = cur->next;
}
if (cur==NULL)
{
printf("链表中没有pos\n");
return;
}
cur->next = pos->next;
free(pos);
}
要先确认pos和*pplist不为空,和pos前插一样有三种情况,先判断pos是否为头节点,是,*pplist赋值它的下一个节点,释放掉pos,然后返回;不是,就找到pos的前节点或走到NULL,如果走到了NULL,那么链表中就没有pos,直接返回,否则cur的next赋值pos的next,也就是cur赋值它的下一个节点的下一个节点,然后释放pos。
链表的销毁:
void SLTDestroy(SListNode** pplist)
{
assert(*pplist);
while(*pplist)
{
SListNode* cur = (*pplist)->next;
free(*pplist);
*pplist = cur;
}
}
链表不能为空,首先从头节点开始,每次存储*pplist的下一个节点给cur,然后释放*pplist,*pplist在赋值cur,直到*pplist为空,链表就销毁完了。
全部代码:
slist.h:
#pragma once
#include<string.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
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
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 在pos的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x);
// 删除pos位置
void SLTErase(SListNode** pphead, SListNode* pos);
//链表销毁
void SLTDestroy(SListNode** pphead);
slist.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"slist.h"
SListNode* BuySListNode(SLTDateType x)
{
SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
void SListPrint(SListNode* plist)
{
while (plist)
{
printf("%d->", plist->data);
plist = plist->next;
}
printf("NULL\n");
}
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = tmp;
return;
}
//找尾
SListNode* tail = *pplist;
while (tail->next)
{
tail = tail->next;
}
tail->next = tmp;
}
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
tmp->next = *pplist;
*pplist = tmp;
}
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
return;
}
SListNode* tail = *pplist;
while (tail->next->next)//找下个节点的下个节点为NULL的节点
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
/*SListNode* prve = NULL;
while (tail->next)
{
prve = tail;
tail = tail->next;
}
free(tail);
prve->next = NULL;*/
}
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* tmp = (*pplist)->next;
free(*pplist);
*pplist = tmp;
}
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
while (plist)
{
if (plist->data == x)
{
return plist;
}
plist = plist->next;
}
printf("没有该值\n");
return NULL;
}
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
assert(pplist);
SListNode* tmp = BuySListNode(x);
if (*pplist == pos)
{
*pplist = tmp;
(*pplist)->next = pos;
return;
}
SListNode* cur = *pplist;
while (cur->next != pos && cur->next!=NULL)
{
cur = cur->next;
}
if (cur->next == NULL && pos != NULL)
{
printf("链表中没有pos\n");
return;
}
cur->next = tmp;
tmp->next = pos;
}
void SLTErase(SListNode** pplist, SListNode* pos)
{
assert(*pplist);
assert(pos);
if (*pplist == pos)
{
*pplist = pos->next;
free(pos);
return;
}
SListNode* cur = *pplist;
while (cur->next != pos && cur)
{
cur = cur->next;
}
if (cur==NULL)
{
printf("链表中没有pos\n");
return;
}
cur->next = pos->next;
free(pos);
}
void SLTDestroy(SListNode** pplist)
{
assert(*pplist);
/*SListNode* cur = *pplist;
while (cur)
{
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
return;
}
while (cur->next->next != NULL)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
cur = *pplist;
}*/
while(*pplist)
{
SListNode* cur = (*pplist)->next;
free(*pplist);
*pplist = cur;
}
}
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* tmp = BuySListNode(x);
tmp->next = pos->next;
pos->next = tmp;
}
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
SListNode* nnext = pos->next->next;
free(pos->next);
pos->next = nnext;
}
测试用例:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"slist.h"
void test()
{
SListNode* plist = NULL;
int i = 0;
for(i=0;i<4;i++)
{
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 10);
SListPushFront(&plist, 20);
SListPushFront(&plist, 30);
SListPushFront(&plist, 40);
SListPrint(plist);
}
for(i=0;i<3;i++)
{
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
}
SListNode* p = SListFind(plist, 10);
if (p != NULL)
{
SLTInsert(&plist, p, 0);
SListPrint(plist);
}
p = SListFind(plist, 1);
SListInsertAfter(p, 0);
SListPrint(plist);
p = SListFind(plist, 3);
SListEraseAfter(p);
SListPrint(plist);
p = SListFind(plist, 3);
SLTInsert(&plist, p, 90);
SListPrint(plist);
SLTInsert(&plist, plist, 100);
SListPrint(plist);
/*SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);*/
/*p = NULL;
SLTInsert(&plist, p, 80);
SListPrint(plist);
p = SListFind(plist, 10);
SLTErase(&plist, p);*/
/*SListPrint(plist);
SLTDestroy(&plist);*/
//SListPrint(plist);
}
int main()
{
test();
return 0;
}