链表就是许多节点在逻辑上串起来的数据存储方式 是通过结构体中的指针将后续的节点串联起来
typedef int SLTDataType;//数据类型
typedef struct SListNode//节点
{
SLTDataType data;//存储的数据
struct SListNode* next;//指向下一个节点地址的指针
}SLTNode;//结构体类型的简化
上面的代码只是声明 并没有定义 也就是没有创建结构体变量
SListNode* pc = NULL;//定义结构体
下面开始实现链表的各个接口 让链表可以存储和删除数据
链表的打印
void SListPrint(SListNode* phead)
{
while (phead != NULL)
{
printf("%d->", phead->val);
phead = phead->next;
}
printf("NULL");
}
链表的插入会向内存申请空间 去创造一个新的节点 所以后续的 头部插入 尾部插入 还是任意位置的插入 都会进行相同的操作 所以这里将开辟节点这个过程单独分装为一个函数 方便后面的操作
static SListNode* CreatNode(DataType x)
{
SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
if (tmp == NULL)
{
printf("failed to allocate memory.");
exit(-1);
}
tmp->val = x;
tmp->next = NULL;
return tmp;
}
这里使用了static将函数的作用域限制在该函数所在的项目中 在其它项目中无法访问
链表的头部插入
void SListFront(SListNode** phead, DataType x)
{
SListNode* tmp = CreatNode(x);
tmp->next = *phead;
tmp->val = x;
*phead = tmp;
}
链表的尾部插入
链表的尾部插入相比于头部插入就显得不是那么好写
因为你要考虑 链表中是否有数据 也就是*phead是否为空 并且还要考虑当链表不为空时 尾插该怎样插入 我们可以使用if语句进行判断
void SListBackPush(SListNode** phead, DataType x)
{
SListNode* newnode = CreatNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SListNode* tail = *phead;//定义一个变量去向后寻找尾部
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
任意位置的插入(从1位置向后插 超出范围 将该节点插入到尾部)
当链表有数据时 尾部插入和头部插入是一样的流程 用一个临时变量tmp 向后寻找pos位置 再将该位置的下一个节点的地址给 要插入节点的指针 最后将要插入节点的地址给前一个节点的指针 这样就将该节点插进去了
但是头部插入不太一样 因为头部之前没有节点了 就只能将头节点先给要插入的节点 再将要插入的节点作为头部 这样就完成了头部插入
void SListPosPush(SListNode** phead, DataType x , int pos)
{
SListNode* newnode = CreatNode(x);
if (pos == 1)
{
newnode->next = *phead;
*phead = newnode;
}
else
{
SListNode* tmp = *phead;
//如果所给的pos位置超出了链表的长度就将其插入到尾部
while (tmp->next!=NULL && pos>2)
{
tmp = tmp->next;
--pos;
}
newnode->next = tmp->next;
tmp->next = newnode;
}
}
运行结果如下 以上就是插入接口函数的实现
删除的相关节点
头删
void SListPopFront(SListNode** phead)
{
assert(*phead);
SListNode* tmp = *phead;
*phead = (*phead)->next;
free(tmp);
tmp = NULL;
}
尾删
尾删和头删不一样 因为当链表只有一个节点时 尾删就变成了头删 这样就和有多个数据时 尾部删除不一样 这就分情况 讨论
void SListPopBack(SListNode** phead)
{
assert(*phead);
if ((*phead)->next == NULL)
{
free(*phead);
*phead = NULL;
}
else
{
//找尾
SListNode* tmp = *phead;//防止头节点丢失
SListNode* str = *phead;
while (tmp->next != NULL)
{
str = tmp;
tmp = tmp->next;
}
free(tmp);
tmp = NULL;
str->next = NULL;
}
}
上面是两个指针配合删除 下面是一个指针去完成
void SListPopBack(SListNode** phead)
{
assert(*phead);
if ((*phead)->next == NULL)
{
free(*phead);
*phead = NULL;
}
else
{
//找尾
SListNode* tmp = *phead;//防止头节点丢失
SListNode* str = *phead;
while (tmp->next->next!=NULL)
{
tmp = tmp->next;
}
free(tmp->next);
tmp->next = NULL;
}
}
链表pos位置删除
void SListPopPos(SListNode** phead, int pos)
{
assert(*phead);//空链表就不用删除了
if (pos == 1)
{
SListNode* tmp = *phead;
*phead = (*phead)->next;
free(tmp);
tmp = NULL;
}
else
{
SListNode* tmp = *phead;
SListNode* str = tmp;
while (pos > 1 && tmp->next != NULL)
{
str = tmp;
tmp = tmp->next;
--pos;
}
//找到pos位置之后将 pos的下一个节点的地址给pos前面的节点的指针
//再将pos所在的节点进行释放
str->next = tmp->next;
free(tmp);
tmp = NULL;
}
}
对链表的查找 修改 其实就是将链表遍历一遍 上面的寻找尾部 打印链表就是遍历链表 所以大家可以将其修改修改
对单链表就介绍到这里 后续还有双向链表
带头结点的单链表 没动态开辟头节点 只是定义了一个 结构体变量 用来存储后续插入的节点 用一级指针去传参 你也可以动态开辟头节点 传二级指针 这里只是说 带头结点的很好实现链表的增删查改
头文件 函数各个接口
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int DataType;
typedef struct SList
{
DataType val;
struct SList* next;
}SListNode;
//单链表打印
void SListPrint(SListNode* pc);
//单链表的尾部插入
void SListPushBack(SListNode* pc, DataType x);
//链表的头部插入
void SListPushFront(SListNode* pc, DataType x);
//单链表头删
void SListPopFront(SListNode* pc);
//单链表尾删
void SListPopBack(SListNode* pc);
//删除pos位置
void SListPopPos(SListNode* pc, int pos);
//pos之后插入
void SListPushPos(SListNode* pc, int pos, DataType x);
//链表的查找
void SListFind(SListNode* pc, DataType x);
//链表的销毁
void SListDestroy(SListNode* pc);
函数实现 接口实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
static SListNode* CreatNode(DataType x)
{
SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
if (tmp == NULL)
{
printf("Failed to allocate SListNode.");
exit(-1);
}
tmp->next = NULL;
tmp->val = x;
return tmp;
}
void SListPrint(SListNode* pc)
{
SListNode* tmp = pc->next;
while (tmp)
{
printf("%d->", tmp->val);
tmp = tmp->next;
}
printf("NULL\n");
}
void SListPushBack(SListNode* pc, DataType x)
{
while (pc->next != NULL)
{
pc = pc->next;
}
pc->next = CreatNode(x);
}
void SListPushFront(SListNode* pc, DataType x)
{
SListNode* tmp = pc->next;
pc->next = CreatNode(x);
pc->next->next = tmp;
}
void SListPopFront(SListNode* pc)
{
SListNode* tmp = pc->next;
pc->next = pc->next->next;
free(tmp);
tmp = NULL;
}
void SListPopBack(SListNode* pc)
{
SListNode* tmp = pc,*cur = pc;
while (cur->next != NULL)
{
tmp = cur;
cur = cur->next;
}
if (cur != pc)//不释放哨兵卫
{
free(cur);
cur = NULL;
}
tmp->next = NULL;
}
void SListPopPos(SListNode* pc, int pos)
{
SListNode* tmp = pc->next,*str = pc;
while (tmp->next != NULL&&pos>1)
{
str = tmp;
tmp = tmp->next;
--pos;
}
if (pos != 1)
{
printf("pos位置超出链表范围.");
return;
}
str->next = tmp->next;
free(tmp);
tmp = NULL;
}
void SListPushPos(SListNode* pc, int pos, DataType x)
{
SListNode* newnode = CreatNode(x);
SListNode* tmp = pc;
while (tmp->next != NULL&&pos>1)
{
tmp = tmp->next;
--pos;
}
if (pos != 1)
{
printf("要插入的位置超出了链表的长度.\n");
return;
}
newnode->next = tmp->next;
tmp->next = newnode;
}
void SListFind(SListNode* pc, DataType x)
{
SListNode* tmp = pc->next;
int pos = 0;
while (tmp)
{
pos++;
if (tmp->val == x)
{
printf("找到了该元素在第%d个\n", pos);
return;
}
tmp = tmp->next;
}
printf("没有找到.\n");
}
void SListDestroy(SListNode* pc)
{
//所有申请的空间都要释放
SListNode* tmp = pc->next;
pc = pc->next->next;
while (pc)
{
free(tmp);
tmp = NULL;
tmp = pc;
pc = pc->next;
}
}
测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void test1()
{
//不是哨兵卫 就是一个结构体变量
SListNode pc;
pc.next = NULL;
SListPushBack(&pc, 1);
SListPushBack(&pc, 2);
SListPushBack(&pc, 3);
SListPushFront(&pc, -1);
SListPushFront(&pc, -2);
SListPopFront(&pc);
SListPopBack(&pc);
SListPushPos(&pc, 1, 5);
SListPushPos(&pc, 5, 6);
SListPopPos(&pc, 1);
SListFind(&pc, 6);
SListPrint(&pc);
SListDestroy(&pc);
}
int main()
{
test1();
return 0;
}