1.单链表的定义和结构
单链表是一种链式的数据结构,它用一组不连续的储存单元存反线性表中的数据元素。链表中的数据是以节点的形式来表示的,节点和节点之间相互连接
一般来说节点有两部分组成 1.数据域 :数据域用来存储各种类型的数据(浮点数,字符串,自定义类型的数据),2.指针域: 指针域用来存储的是指针,它用来指向下一个节点
2.单链表的实现
SLlist.h
//定义单链表的节点
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType Data;
struct SListNode* next;//指向下一个节点的指针
}SLTNode;
//增加新的节点
SLTNode* SLTBuyNode(SLTDataType x);
//打印链表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
SLlist.c
//增加新的节点
SLTNode* SLTBuyNode(SLTDataType x) {
//开辟一个节点大小的空间
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->Data = x;
newnode->next = NULL;
return newnode;
}
//打印链表
void SLTPrint(SLTNode* phead) {
//循环打印,当phead指向NULL(也就是尾节点指向的下一个节点)时停止
while (phead)
{
printf("%d->", phead->Data);
//让phead指向下一个节点,并赋值给phead
phead = phead->next;
}
printf("NULL\n");
}
//尾插
//想要修改值就要传地址,不能传值。而phead是个指针,我们要拿二级指针接收
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
assert(pphead);
//*pphead说明第一个节点为NULL也就是说链表为空
if (*pphead == NULL)
{
*pphead = SLTBuyNode(x); //直接创建一个新的节点
}
//链表不为NULL时尾插
else
{
SLTNode* ptail = *pphead; //创建一个节点,从头开始遍历找尾节点
//遍历链表,找到尾节点
while (ptail->next) //下一个节点为NULL表达式为假,就说明已经找到了尾节点
{
ptail = ptail->next;
}
ptail->next = SLTBuyNode(x); //找到为节点,让尾节点指向新的节点,完成尾插
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
assert(pphead);
SLTNode* newnode = SLTBuyNode(x); //创建一个新的节点准备进行头插
newnode->next = *pphead; //直接让新节点指向原先的第一个节点
*pphead = newnode; //让新的节点成为新的第一个节点
//在是有一个节点的情况下也可以完美完成任务
}
//尾删
//可能要修改第一个节点,所以得传地址,用二级指针** pphead接收
void SLTPopBack(SLTNode** pphead) {
//列表不能为NULL不然删啥
assert(pphead && *pphead);
SLTNode* ptail; //找尾节点
SLTNode* prev; //尾节点前一个节点,它将来可能是新的尾节点
prev = ptail = *pphead;
//只有一个节点,直接删除不用找尾节点前面的节点
if (ptail->next == NULL)
{
//改变头节点需要用到pphead,对它进行解引用得到原第一个节点
free(*pphead);
*pphead = NULL;
}
//有一个以上的节点,开始找尾节点,进行尾删
else
{
//当 某个节点的下一个节点(指向)->NULL 时找到尾节点
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail); //找到了原尾节点直接释放
ptail = NULL;
prev->next = NULL; //让新的位节点(指向)->NULL
}
}
//头删
//肯定会修改到第一个节点,要传地址,拿二级指针** pphead接收
void SLTPopFront(SLTNode** pphead) {
assert(pphead && *pphead);
SLTNode* ptail = *pphead; //记录原第一个节点
//改变原第一个节点
*pphead = ptail->next; //让第二个节点成为,新的第一个节点
//直接释放原第一个节点
free(ptail);
ptail = NULL;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {
//空链表的话不用找了
assert(phead);
SLTNode* pcur = phead;
//让pcur遍历链表,直到尾节点指向的下一个节点NULL,说明没有这个元素
while (pcur)
{
if(pcur->Data == x)
{
return pcur; //有这个元素,直接返回这个元素所在节点的地址
}
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
//可能会改变第一个节点要传地址,拿二级指针** pphead接收
//pos 为指定位置
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
assert(pphead);
//创新的节点
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = *pphead; //prev用找到pos之前的位置
//如果只有一个节点,会出现prev找不到pos的情况,因为一开始这两的位置一样,prev的下一个位置永远不可能是pos
if (pos == prev)
{
SLTPushFront(pphead, x);
}
//有多个节点的情况,开始找pos节点并再此之前插入
else
{
//找pos之前的节点prev
while (prev->next != pos)
{
prev = prev->next;
}
//拿1 -> 2 -> NULL 我们要在1(prev) 2(pos)之间插入3演示
newnode->next = pos; //先让新的节点,指向pos 3 -> 2 ->NULL
prev->next = newnode; //在让prev节点指向新的节点 1 -> 3 -> 2 ->NULL
}
}
//在指定位置之后插入数据
//不存在改变第一个节点的情况,传第一个节点值过来就够用
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
//指定的位置不能为NULL,要不然谁知道你想要在谁之后插入数据
assert(pos);
创建新的节点
SLTNode* newnode = SLTBuyNode(x);
SLTNode* del = pos->next; //del为pos之后的节点
//如果只有一个节点,那么pos指向的下一个节点为NULL,这套逻辑依旧适用
//拿1 -> 2 -> 3 -> NULL 我们要再2(pos)3(del)之间插入4
newnode->next = del; //先让新的节点指向del 4 ->3 -> NULL
pos->next = newnode; //在让pos指向新的节点 1 -> 2 -> 4 -> 3 -> NULL
}
//删除pos节点
//如果删除的是第一点,得传地址,因为第一个节点会发生变化
void SLTErase(SLTNode** pphead, SLTNode* pos) {
//链表不能为NULL不然删啥
assert(pphead && pos && *pphead);
SLTNode* del = pos->next; //del为pos之后的节点
SLTNode* prev = *pphead; //prev为pos之前的节点
//如果只有一个节点,直接删即可,不用去找pos前一个节点,这样找找不到
if (pos == del)//pos = del 我搞错了,这样就赋值了
{
//头删,传头结点的地址也就是pphead
SLTPopFront(pphead);
}
//不止一个节点,找pos之前的节点prev
else
{
while (prev->next != pos)
{
prev = prev->next;
}
//把pos前一个节点prev和后一个节点del连接起来,在把pos节点释放
prev->next = del;
free(pos);
pos = NULL;
}
}
//删除pos之后的节点
//第一个节点不可能被修改,因为即使只有一个节点,第一个节点后面的节点也是NULL
void SLTEraseAfter(SLTNode* pos) {
//pos后面的节点不能为NULL不然删啥
assert(pos && pos->next);
SLTNode* del = pos->next; //del为pos之后的节点
//1->2->3,让1跟3连接起来,在释放2
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
//涉及修改第一个,需要接收头结点地址
void SListDesTroy(SLTNode** pphead) {
assert(pphead && *pphead);
SLTNode* pcur = *pphead; //pcur用来遍历链表,依次销毁
SLTNode* next; //next为当前需销毁节点的下一个节点,不然直接销毁就找不到下一个节点了
//如果pcur为NULL,说明走到了尾节点指向的NULL,链表销毁完毕
while (pcur)
{
next = pcur->next; //先保存当前需要销毁节点的下一个节点的地址
free(pcur); //销毁当前节点
pcur = next; //走向下一个节点
}
*pphead = NULL; //让第一个节点为NULL,链表销毁完毕后为NULL
}