单链表的概念
首先链表的概念是一种在物理存储结构上非连续,非顺序的储存结构,数据元素的逻辑顺序是通过链表的指针链接次序实现。

链表结构在逻辑上是连续的,但是物理空间不一定是连续的。而现实中的节点一般都是在堆上开辟的。从堆上申请的空间,是按照一定策略来分配的,每次申请的空间位置都是随机的。
而实际中链表的结构特别多,光单链表就分为:
单向链表(单链表)和双向链表(双链表)

带头单链表和不带头单链表

循环链表和非循环

虽然有很多种结构的链表,但是我们最长用到的单链表还是无头单向非循环链表

结构非常简单,一般也不会单独存储数据,实际中更多的作为其他数据结构的子结构,如哈希桶等。
单链表的实现(无头单向非循环链表)
链表节点结构:
typedef int SLTDataType; //存储元素的类型
typedef struct SigList
{
SLTDataType a; //存储的数据
struct SigList* next; //存储指向下一个位置的地址的指针
}SLTNode;
动态申请一个节点的函数:
SLTNode* BuyNewNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //动态申请一个节点
if(newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->a = x; //将元素放进去
newnode->next = NULL; //将指向的下一个元素的指针置空
return newnode;
}
然后是最简单的遍历链表:
void Print(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL) //下一个不为空 链表就还没到结尾
{
printf("%d->", cur->a); //每个节点打印
cur = cur->next;
}
printf("NULL\n");
}
单链表的尾插(首先我们是传过去的是链表的首地址,那我们需要找到最后一个节点的位置):
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* tail = *pphead; //不能直接传过来的地址去找,这样会改变首节点指针的指向
while (tail->next != NULL) //一直向后找
{
tail = tail->next;
}
}
当我们找到这时候我们得先使用动态申请一个节点,将tail的next指向新节点
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNewNode(x); //新申请的节点
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode; //将新节点链接上
}
这样我们就链接了新的节点,但是如果我们这个链表一开始为空,那是不是tail链接下一个节点就出问题了?所以我们就需要直接将新节点变为我们的首节点(所以我们使用的是二级指针,当需要改变穿过来指针的内容,就必须使用地址,而非单指针,因为形参的改变不影响实参)
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNewNode(x);
if (*pphead == NULL) //当这个链表为空链表,新节点就为首节点
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
单链表的头插:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNewNode(x); //创建新节点
newnode->next = *pphead; //新节点指向首节点的地址
*pphead = newnode; //首节点指针指向新节点地址
}
根据上面尾插代码和头插代码可以分析出来,单链表更适合头插。
尾删(需要注意链表是否只有一个节点,如果只有一个节点就需要对头指针改变,所以我们传递是头指针的地址(形参的改变不改变实参)):
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead); //如果是个空链表 直接报错
SLTNode* tail = *pphead;
if (tail->next == NULL) //判断是否只有一个节点,如果只有一个节点,头指针置空
{
free(tail);
tail = NULL;
*pphead = NULL;
}
else //找到最后一个节点的前一个节点,释放最后一个节点
{ //将删除节点的前一个节点的next置空
while(tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
头删(只需要判断链表是否为空):
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead); //判断是否为空链表,空链表不能进行删除操作
SLTNode* head = *pphead;
*pphead = head->next;
free(head);
head = NULL;
}

不管是否有一个节点,只需要将头指针指向头节点的next即可,然后释放头节点。
所以我们对比尾删和头删,可以发现头删的效率更高,单链表也更加适合头删。
查找并返回查找节点地址(只需要遍历链表找到元素相同的节点并返回):
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{ //链表 //查找的节点元素
while (phead)
{
if (phead->a == x)
{
return phead; //找到返回地址
}
phead = phead->next;
}
return NULL; //找不到返回NULL
}
有了find函数后,我们可以创建根据find找到的(pos)位置来进行插入,我们可以在pos前插入或者pos后插入。
pos位置之前插入(使用SLTFind函数找到pos位置):
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos && *pphead); //保证链表和pos位置都不为NULL
SLTNode* newnode = BuyNewNode(x);//创建一个节点
if (pos == *pphead) //如果pos位置为头节点,那就是头插
{ //我们可以直接调用头插函数或者自己写
newnode->next = *pphead;
*pphead = newnode;
}
else //需要找到pos位置前节点
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next; //cur == pos前一个节点
}
newnode->next = pos; //新节点的next指向pos节点
cur->next = newnode; //cur的next指向新节点,断开了与pos节点的链接
}
}
pos位置后插入(使用SLTFind函数找到pos位置):
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos); //判断pos不为NULL
SLTNode* newnode = BuyNewNode(x); //创建新节点
newnode->next = pos->next; //新节点next链接pos位置后一个节点
pos->next = newnode; //pos的next链接新节点
}
pos位置后插入我们可以看到只需要考虑新节点的链接和pos的next的链接,如图:

从图可以看出,pos相当于头节点,在头节点后一个位置插入,不需要像pos前插入新节点再去找前一个节点,跟头插有相似处。
插入我们了解,而删除我们可以删除pos位置也可以删除pos后面的位置,所以我们实现这两个函数。
pos位置删除(需要找到pos的前一个节点):
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos && *pphead); //首先还是判断链表和pos位置不为NULL
if (*pphead == pos) //判断头节点是否为pos
{ //pos等于头节点,代表我们需要修改链表头指针
SLTPopFront(pphead); //所以我们传的地址,对头指针进行修改
} //直接调用头删函数
else
{ //pos不为头节点
SLTNode* cur = *pphead;
while (cur->next != pos) //找到pos位置前一个节点的地址
{
cur = cur->next;
}
cur->next = cur->next->next; //将pos前一个位置节点指向pos后一个位置节点
free(pos); //释放pos空间
}
}

pos位置后删除:
void SLTEraseAfter(SLTNode* pos)
{
assert(pos); //只需要判断pos不为NULL
if (pos->next == NULL) //pos位置后为空直接返回,没有可以删除的节点
{
return;
}
else
{
SLTNode* cur = pos->next;
pos->next = pos->next->next; //将pos的next指向pos的next的next;
free(cur); //释放提前存好的pos的next节点
cur = NULL;
}
}
其实观察下来可以发现,删除pos后一个节点跟头删有相似之处,不需要查找其他节点,比删除pos位置效率更高一些。
最后我们只需要在结束使用链表时,释放所有节点空间,不造成内存泄漏
void SLTDestroy(SLTNode** pphead)
{
assert(*pphead);
SLTNode* cur = *pphead;
SLTNode* tail = cur;
while (cur)
{
tail = cur->next;
free(cur);
cur = tail;
}
}
注意,我们所使用二级指针的函数判断了指针是否为空,其实我们还需要判断指针是否无效地址。
比如:
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNewNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
int main()
{
SLTNode* plist = NULL;
SLTPushFront(plist); //错误代码
return 0;
}
这样很容出错,但是又不好找到错误,所以我们可以加上断言;
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead); //代码运行时会为我们准确的报错
SLTNode* newnode = BuyNewNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
int main()
{
SLTNode* plist = NULL;
SLTPushFront(plist);
return 0;
}

无头单向非循环链表实现完整代码
//SigList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SigList
{
SLTDataType a;
struct SigList* next;
}SLTNode;
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//打印
void Print(SLTNode* phead);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//pos位置后面删除
void SLTEraseAfter(SLTNode* pos);
void SLTDestroy(SLTNode** pphead);
//SigList.c
#include "SigList.h"
SLTNode* BuyNewNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if(newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->a = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyNewNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead); //报错 说明指针错误 空指针
assert(*pphead); //如果是个空链表 直接报错
SLTNode* tail = *pphead;
if (tail->next == NULL)
{
free(tail);
tail = NULL;
*pphead = NULL;
}
else
{
while(tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
void Print(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->a);
cur = cur->next;
}
printf("NULL\n");
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyNewNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* head = *pphead;
*pphead = head->next;
free(head);
head = NULL;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
while (phead)
{
if (phead->a == x)
{
return phead;
}
phead = phead->next;
}
return NULL;
}
//pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos && *pphead);
SLTNode* newnode = BuyNewNode(x);
if (pos == *pphead)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
newnode->next = pos;
cur->next = newnode;
}
}
//pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos && *pphead);
if (*pphead == pos)
{
SLTPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = cur->next->next;
free(pos);
}
}
//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyNewNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//pos位置后面删除
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SLTNode* cur = pos->next;
pos->next = pos->next->next;
free(cur);
cur = NULL;
}
}
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
SLTNode* tail = cur;
while (cur)
{
tail = cur->next;
free(cur);
cur = tail;
}
}
1338

被折叠的 条评论
为什么被折叠?



