1.链表的定义
链表是一个空间上不一定连续(存储结构不是线性的),但逻辑结构上一定连续的结构。其结构就像火车一样,车厢之间也是一节与一节相连。
一节车厢就是我们的数据而车厢之间的链接靠的就是链,前一节车厢想找到下一节就是靠前一节车厢的链找到,我们把每一节车厢的这种结构也称为节点。
2.链表结构的实现
我们要将一块空间划分为两个部分(这里讲的是单向链表),一部分用存储数据,一部分用来定义一个指针存储下一块空间的地址。
一个节点我们就这样设计。
我们这样做就可以让空间相连:
注意最后一个节点指向的指针一定是空,这样才可以保证让链表正常结束不会指向错误的空间。
经过上面所总结的内容我们可以得出链表的结构体。
typedef int ListNodeDataType;
typedef struct ListNode
{
ListNodeDataType x;
struct ListNode* next;
}LN;
3.链表相关函数
相关基础函数的实现我们要满足基础的初始化,销毁,增,删,查,改等功能。
1.初始化链表
这个代码只需要判断phead是否为空就行,代码十分简单。
//初始化链表
void SLTInte(SLN* phead)
{
assert(phead);
phead->x = 0;
phead->next = NULL;
}
2.创建节点
在这里创建一个节点需要用到一个函数malloc,在stdlib.h这个库里面,我们需要这个函数来为我们开辟一块空间来实现这个节点结构。
函数原理如下所示:
代码如下:
//创建链表节点
SLN* SLTAddNode()
{
SLN* newnode = (SLN*)malloc(sizeof(SLN));
if (newnode == NULL)
{
perror("malloc Fail");
}
else
{
return newnode;
}
}
3.查找
查找的函数实现目的需要找到指定元素并且返回该节点的指针,如果没找到就返回NULL,我们传过去的指针的不能为空。
代码如下:
//查找
SLN* SLTFind(SLN* phead, ListNodeDataType x)
{
assert(phead->next);
SLN* pqhead = phead->next;
while (pqhead)
{
if (pqhead->x == x)
{
return pqhead;
}
pqhead = pqhead->next;
}
return NULL;
}
因为这个链表我们使用带头链表,所以实际存储数据是从下一个节点开始的。
4.输出链表元素
打印链表元素需要我们遍历这个链表意义打印,条件是需要这个链表拥有元素。
代码如下:
//输出链表元素
void SLTPrint(SLN* phead)
{
assert(phead->next);
SLN* pcur = phead->next;
while (pcur)
{
printf("%d->", pcur->x);
pcur = pcur->next;
}
printf("NULL");
}
5.插入
插入分为头部插入,尾部插入,指定位置之前插入。
1.头部插入
我们想实现尾部插入再插入的时候有两种情况,第一种该链表不为空,首先需要创建一个节点(创建节点的函数上面已经实现了),然后将头节点的next指向创建的节点,然后再将创建的节点的next指向原来的头节点的下一个节点。第二种链表为空,我们需要将头节点指向下一个节点,再将创建的节点所指向的节点置为空。
代码实现:
//尾部插入
void SLTPushBack(SLN** pphead, ListNodeDataType x)
{
assert(pphead);
SLN* newnode = SLTAddNode();
newnode->x = x;
if ((*pphead)->next != NULL)
{
SLN* nextnode = (*pphead)->next;
newnode->next = nextnode;
(*pphead)->next = newnode;
}
else
{
(*pphead)->next = newnode;
newnode->next = NULL;
}
}
测试一下代码:
int main()
{
SLN* phead = SLTAddNode();
phead->next = NULL;
phead->x = 0;
SLN** pphead = &phead;
SLTPushFront(pphead, 5);
SLTPushFront(pphead, 4);
SLTPushFront(pphead, 3);
SLTPushFront(pphead, 2);
SLTPushFront(pphead, 1);
SLTPrint(phead);
return 0;
}
测试输出因该是1->2->3->4->5。
是正确的。
2.尾部插入
我们想实现尾部插入就需要找到尾节点,再将为尾节点的下一个节点指向新创建的节点把新创建的节点所指向的下一个空间置为空。
代码如下:
//尾部插入
void SLTPushBack(SLN** pphead, ListNodeDataType x)
{
assert(pphead);
SLN* newnode = SLTAddNode();
newnode->x = x;
SLN* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
newnode->next = NULL;
}
3.指定位置之前插入
指定位置插入可以在前也可以在后 这里我们就把在前插入进行讲解,要在前插入我们需要先找到该节点的前一个节点然后把该节点和前一个节点记录下来,将前一个节点的指针指向创建的空间,创建的空间的指针指向该节点,如果修改成功就返回true,没成功就返回false(这个函数会用到布尔变量。
代码如下:
//在指定位置之前插⼊数据
bool SLTInsert(SLN** pphead, ListNodeDataType pos, ListNodeDataType x)
{
assert(pphead);
assert((*pphead)->next);
SLN* newnode = SLTAddNode();
newnode->x = x;
SLN* pcur = (*pphead);
while (pcur->next)
{
if (pcur->next->x == pos)
{
SLN* nextnode = pcur->next;
pcur->next = newnode;
newnode->next = nextnode;
return true;
}
pcur = pcur->next;
}
return false;
}
测试一下代码,我们需要在2前面再插入一个2。
结果是正确的。
再测试另一种情况,在6之前插入1,链表中不存在6所以会输出“修改失败不存在所查找的节点”。
6.删除
删除也分为很多种,比如头删,尾删,删除某个中间节点,删除某个节点之后的节点。
1.头删
我们要删除第一个节点不光是要把该节点的空间释放掉,还应该将头节点指向第二个节点,同时想要实现头删该链表有节点。
代码如下:
//首部删除
void SLTPopFront(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead)->next;
(*pphead)->next = pcur->next;
free(pcur);
pcur = NULL;
}
2.尾删
想要删除链表的尾节点就需要先找到尾节点然后把尾节点释放掉并把前面的一个节点所指向的下一个节点置为空。
代码如下:
//尾部删除
void SLTPopBack(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead);
while(pcur->next->next)
{
pcur = pcur->next;
}
free(pcur->next);
pcur->next = NULL;
}
3.删除某个中间节点
我们想要实现这个函数我们需要先找到这个节点,然后将它的上一个节点和它的下一个节点相连并释放掉该节点的空间。
代码实现如下:
//删除pos节点
void SLTErase(SLN** pphead,ListNodeDataType pos)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead);
while (pcur->next->x != pos)
{
pcur = pcur->next;
}
SLN* nextnode = pcur->next->next;
free(pcur->next);
pcur->next = nextnode;
}
4.删除某个pos节点后的所有节点
我们需要先找到这个节点然后在从后面逐个释放掉后面的每个节点空间。
//删除pos之后的节点
void SLTEraseAfter(SLN** pphead, ListNodeDataType pos)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = *pphead;
while (pcur->next->x != pos)
{
pcur = pcur->next;
}
SLN* nonode = pcur->next;
pcur->next = NULL;
pcur = nonode->next;
while (pcur)
{
free(nonode);
nonode = pcur;
pcur = pcur->next;
}
}
7.销毁链表
完成这个函数我们需要把头节点之后的节点全部释放掉再把头节点指向的空间指向NULL。
代码如下所示:
//销毁链表
void SListDesTroy(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = *pphead;
SLN* nonode = pcur->next;
pcur = nonode->next;
while (pcur)
{
free(nonode);
nonode = pcur;
pcur = pcur->next;
}
(*pphead)->next = NULL;
}
4.代码展示
test.c
#include"line.h"
int main()
{
SLN* phead = SLTAddNode();
phead->next = NULL;
phead->x = 0;
SLN** pphead = &phead;
SLTPushBack(pphead, 0);
SLTPushFront(pphead, 5);
SLTPushFront(pphead, 4);
SLTPushFront(pphead, 3);
SLTPushFront(pphead, 2);
SLTPushFront(pphead, 1);
/*if (SLTInsert(pphead, 6, 1))
{
printf("修改成功\n");
}
else
{
printf("修改失败不存在所查找的节点\n");
}*/
/*SLTPopFront(pphead);
SLTPopBack(pphead);*/
SLTEraseAfter(pphead, 3);
SListDesTroy(pphead);
SLTPrint(phead);
return 0;
}
line.c
#include"line.h"
//初始化链表
void SLTInte(SLN* phead)
{
assert(phead);
phead->x = 0;
phead->next = NULL;
}
//创建链表节点
SLN* SLTAddNode()
{
SLN* newnode = (SLN*)malloc(sizeof(SLN));
if (newnode == NULL)
{
perror("malloc Fail");
}
else
{
return newnode;
}
}
//查找
SLN* SLTFind(SLN* phead, ListNodeDataType x)
{
assert(phead->next);
SLN* pqhead = phead->next;
while (pqhead)
{
if (pqhead->x == x)
{
return pqhead;
}
pqhead = pqhead->next;
}
return NULL;
}
//首部插入
void SLTPushFront(SLN** pphead, ListNodeDataType x)
{
assert(pphead);
SLN* newnode = SLTAddNode();
newnode->x = x;
if ((*pphead)->next != NULL)
{
SLN* nextnode = (*pphead)->next;
newnode->next = nextnode;
(*pphead)->next = newnode;
}
else
{
(*pphead)->next = newnode;
newnode->next = NULL;
}
}
//输出链表元素
void SLTPrint(SLN* phead)
{
assert(phead);
SLN* pcur = phead->next;
while (pcur)
{
printf("%d->", pcur->x);
pcur = pcur->next;
}
printf("NULL");
}
//尾部插入
void SLTPushBack(SLN** pphead, ListNodeDataType x)
{
assert(pphead);
SLN* newnode = SLTAddNode();
newnode->x = x;
SLN* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
newnode->next = NULL;
}
//在指定位置之前插⼊数据
bool SLTInsert(SLN** pphead, ListNodeDataType pos, ListNodeDataType x)
{
assert(pphead);
assert((*pphead)->next);
SLN* newnode = SLTAddNode();
newnode->x = x;
SLN* pcur = (*pphead);
while (pcur->next)
{
if (pcur->next->x == pos)
{
SLN* nextnode = pcur->next;
pcur->next = newnode;
newnode->next = nextnode;
return true;
}
pcur = pcur->next;
}
return false;
}
//首部删除
void SLTPopFront(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead)->next;
(*pphead)->next = pcur->next;
free(pcur);
pcur = NULL;
}
//尾部删除
void SLTPopBack(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead);
while(pcur->next->next)
{
pcur = pcur->next;
}
free(pcur->next);
pcur->next = NULL;
}
//删除pos节点
void SLTErase(SLN** pphead,ListNodeDataType pos)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = (*pphead);
while (pcur->next->x != pos)
{
pcur = pcur->next;
}
SLN* nextnode = pcur->next->next;
free(pcur->next);
pcur->next = nextnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLN** pphead, ListNodeDataType pos)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = *pphead;
while (pcur->next->x != pos)
{
pcur = pcur->next;
}
SLN* nonode = pcur->next;
pcur->next = NULL;
pcur = nonode->next;
while (pcur)
{
free(nonode);
nonode = pcur;
pcur = pcur->next;
}
}
//销毁链表
void SListDesTroy(SLN** pphead)
{
assert(pphead);
assert((*pphead)->next);
SLN* pcur = *pphead;
SLN* nonode = pcur->next;
pcur = nonode->next;
while (pcur)
{
free(nonode);
nonode = pcur;
pcur = pcur->next;
}
(*pphead)->next = NULL;
}
line.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int ListNodeDataType;
typedef struct ListNode
{
ListNodeDataType x;
struct ListNode* next;
}SLN;
//初始化链表
void SLTInte(SLN* phead);
//输出链表元素
void SLTPrint(SLN* phead);
//创建链表节点
SLN* SLTAddNode();
//头部插?删除/尾部插?删除
//尾部插入
void SLTPushBack(SLN** pphead, ListNodeDataType x);
//首部插入
void SLTPushFront(SLN** pphead, ListNodeDataType x);
//尾部删除
void SLTPopBack(SLN** pphead);
//首部删除
void SLTPopFront(SLN** pphead);
//查找
SLN* SLTFind(SLN* phead, ListNodeDataType x);
//在指定位置之前插数据
bool SLTInsert(SLN** pphead, ListNodeDataType pos, ListNodeDataType x);
//删除pos节点
void SLTErase(SLN** pphead, ListNodeDataType pos);
//删除pos之后的节点
void SLTEraseAfter(SLN** pphead,ListNodeDataType pos);
//销毁链表
void SListDesTroy(SLN** pphead);