下面是本项目的大体思路梳理:
一、实现单链表
全部接口一览:
void SLTPrint(SLTNode* phead);//打印 void SLTPushBack(SLTNode** pphead,SLTDateType x);//尾插 void SLTPushiFront(SLTNode** pphead, SLTDateType x);//头插 void SLTPopBack(SLTNode** pphead);//尾删 void SLTPopFront(SLTNode** pphead);//头删 void SLTInsert(SLTNode** pphead,SLTNode* pos,SLTDateType x);//任意位置之前插入 SLTNode* SLTFind(SLTNode** pphead, SLTDateType x);//查找接口 void SLTInsertAfter(SLTNode* pos, SLTDateType x);//任意位置之后插入 void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos指向的节点 void SLTEraseAfter(SLTNode* pos);//删除pos指向之后的节点 void SLTDestroy(SLTNode** pphead);//销毁链表接口
1.创建链表的结构体
//定义单链表结构体 #define SLTDateType int typedef struct SListNode { SLTDateType date;//每个节点的数据 struct SListNode* next;//每个节点存储下一个节点的地址 }SLTNode;
2.打印链表的接口实现
首先制造一些节点并进行相连操作
打印函数实现的思路:
void SLTPrint(SLTNode* phead) { //定义一个新的指针 SLTNode* pcur = phead; //循环打印即可,什么时候停止?pcur不指向空便不停止 while (pcur) { //打印数据 printf("%d->", pcur->date); //更新指针 pcur = pcur->next; } //为了完善,我给最后也加一个NULL printf("NULL\n"); }
3.尾插接口实现
思路:定义一个新指针,找到原链表的尾节点,找到了接上即可。
这里为什么要分类进行讨论?这由思路决定的,因为如果是空链表的情况下,ptail->next根本就是错误的,因为不存在。
新空间开辟接口:
void SLTPushBack(SLTNode** pphead, SLTDateType x) { assert(pphead); //申请一块新的空间 SLTNode* newnode = SLTBuyNode(x); //分两种情况 //链表为空 if (*pphead == NULL) { *pphead = newnode; } else//链表不为空 { SLTNode* ptail = *pphead;//用于找到链表的尾节点 while (ptail->next) { ptail = ptail->next; }//找到尾节点 ptail->next = newnode;//给他接上 } }
SLTNode* SLTBuyNode(SLTDateType x) { //申请一块新的空间 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //放内容 newnode->date = x; newnode->next = NULL; //返回地址 return newnode; }
4.头插接口实现
思路:
尾插需要分情况讨论而头插怎么不需要呢?还是思路决定的,这个思路没有用到ptail->next所以就不需要考虑空链表的情况
void SLTPushiFront(SLTNode** pphead, SLTDateType x) { assert(pphead); //申请一块新的空间 SLTNode* newnode = SLTBuyNode(x); //把原来第一个结点的地址交给新节点的next newnode->next = *pphead; *pphead = newnode;//把头部指针更新一下 }
5.尾删接口实现
思路:
这里为什么要分只有一个节点和多个节点的不同情况?因为涉及到prev问题如果是只有一个节点,ptail指向第一个节点,那prev指针指向哪里?所以要对只有一个节点的链表单独进行处理。
void SLTPopBack(SLTNode** pphead) { //检验 assert(pphead);//原本的指针不能是空 assert(*pphead);//传过来的不能是空指针给我删除啊 //删除 //这里分两种情况 //1.只有一个节点的情况 if (*pphead == NULL) { free(*pphead); *pphead = NULL; return; } else//多个节点的情况 { //需要找到最后一个节点和倒数第二个节点 //最后一个节点是为了释放空间 //倒数第二个节点是为了把他的next部分置为空 SLTNode* ptail = *pphead; SLTNode* prev = *pphead; while (ptail->next) { prev = ptail; ptail = ptail->next; } //第二个节点next置为空 prev->next = NULL; //销毁第一个节点 free(ptail); ptail = NULL; } }
6.头删接口的实现
思路:
void SLTPopFront(SLTNode** pphead) { //检验 assert(pphead);//检验你别给我传个空 assert(*pphead);//检验链表不为空,空链表删除什么? //开始准备删除 SLTNode* next = (*pphead)->next;//记录一下要接上的地址 free(*pphead);//释放第一个节点 *pphead = next; }
7.查找接口实现
查找接口是做什么的呢?找一个节点的地址的。
思路实现:
SLTNode* SLTFind(SLTNode** pphead, SLTDateType x) { //检查 assert(pphead);//别传空指针进来 //这里可以传空链表吗?可以!顶多找不到给你返回一个NULL嘛 //遍历链表 SLTNode* pcur = *pphead; while (pcur)//当pcur找到NULL时候就会停止 { if (pcur->date == x) { return pcur;//满足条件,返回该节点的地址 } pcur = pcur->next;//更新指针 } //如果什么都没有找到的话,是不是会不太合适\ // 所以我们规定如果没有找到,返回NULL地址 return NULL; }
8.在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) { //检验 assert(pphead);//传入地址不得为空 assert(pos);//pos不得为空 assert(*pphead);//要加上链表不得为空,\ 这是因为pos是链表的一个节点的地址,如 \ 果链表不存在,那么pos也会不存在 //这里要分情况进行处理 //1.pos刚好是头节点 if (pos == *pphead) { //直接调用头插 SLTPushiFront(pphead,x); return; } else//2.如果pos不是头节点 { SLTNode* prev = *pphead; //让prev找到pos之前的节点的地址 while (prev->next != pos) { prev = prev->next; } SLTNode* newnode = SLTBuyNode(x);//申请空间 prev->next = newnode;//把新空间链接到我们前一个节点 newnode->next = pos;//把新空间与pos指向的空间链接在一起 } }
9.在指定位置之后插入数据的接口实现
思路:
思考:上面说的关联新节点的错误是如何引起的呢?
答:因为新节点的后面的节点的地址表示是靠的pos->next
void SLTInsertAfter(SLTNode* pos, SLTDateType x)//之后 { //检查 assert(pos); //申请空间 SLTNode* newnode = SLTBuyNode(x); //关联新节点的错误写法,原因:pos值的改变 //pos->next = newnode; //newnode->next = pos->next; //关联新节点的正确写法: //先接后面,再接前面 newnode->next = pos->next; pos->next = newnode; }
10.删除指定位置的节点
思路:
void SLTErase(SLTNode** pphead, SLTNode* pos) { //检查 assert(pphead);//不得传空 assert(*pphead);//不得为空链表 assert(pos); //pos刚好是头节点的情况,头删 if (*pphead == pos) { //头删 SLTPopFront(pphead); return; } //如果不是头节点 else { //找到pos之前的节点 SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }
11.删除指定位置之后的节点接口实现
思路:
void SLTEraseAfter(SLTNode* pos) { //检查 assert(pos);//传过来的地址不可以为空 assert(pos->next);//传过来的地址的下一个地址不得为空 SLTNode* del = pos->next;//记录要删除的节点的地址 pos->next = pos->next->next;//更新pos中next的地址 free(del);//释放空间 del = NULL;//临时指针及时置为空 }
12.销毁链表接口
思路:
void SLTDestroy(SLTNode** pphead) { assert(pphead);//传过来的指针不得为空 assert(*pphead);//链表不得为空,空链表不用销毁 //创建遍历指针 SLTNode* pcur = *pphead; //循环销毁 while (pcur)//啥时候停止?pcur指向NULL { SLTNode* next = pcur->next; free(pcur); pcur = next; } //最后把头指针也置为NULL *pphead = NULL; }
全部代码合集
//SList.h #pragma once #include<stdlib.h> #include<stdio.h> #include<assert.h> //定义单链表结构体 #define SLTDateType int typedef struct SListNode { SLTDateType date;//每个节点的数据 struct SListNode* next;//每个节点存储下一个节点的地址 }SLTNode; void SLTPrint(SLTNode* phead);//打印 void SLTPushBack(SLTNode** pphead,SLTDateType x);//尾插 void SLTPushiFront(SLTNode** pphead, SLTDateType x);//头插 void SLTPopBack(SLTNode** pphead);//尾删 void SLTPopFront(SLTNode** pphead);//头删 void SLTInsert(SLTNode** pphead,SLTNode* pos,SLTDateType x);//任意位置之前插入 SLTNode* SLTFind(SLTNode** pphead, SLTDateType x);//查找接口 void SLTInsertAfter(SLTNode* pos, SLTDateType x);//任意位置之后插入 void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos指向的节点 void SLTEraseAfter(SLTNode* pos);//删除pos指向之后的节点 void SLTDestroy(SLTNode** pphead);//销毁链表接口
//SList.c #include"SList.h" SLTNode* SLTBuyNode(SLTDateType x) { //申请一块新的空间 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //放内容 newnode->date = x; newnode->next = NULL; //返回地址 return newnode; } SLTNode* SLTFind(SLTNode** pphead, SLTDateType x) { //检查 assert(pphead);//别传空指针进来 //这里可以传空链表吗?可以!顶多找不到给你返回一个NULL嘛 //遍历链表 SLTNode* pcur = *pphead; while (pcur)//当pcur找到NULL时候就会停止 { if (pcur->date == x) { return pcur;//满足条件,返回该节点的地址 } pcur = pcur->next;//更新指针 } //如果什么都没有找到的话,是不是会不太合适\ // 所以我们规定如果没有找到,返回NULL地址 return NULL; } void SLTPrint(SLTNode* phead) { //定义一个新的指针 SLTNode* pcur = phead; //循环打印即可,什么时候停止?pcur不指向空便不停止 while (pcur) { //打印数据 printf("%d->", pcur->date); //更新指针 pcur = pcur->next; } //为了完善,我给最后也加一个NULL printf("NULL\n"); } void SLTPushBack(SLTNode** pphead, SLTDateType x) { assert(pphead); //申请一块新的空间 SLTNode* newnode = SLTBuyNode(x); //分两种情况 //链表为空 if (*pphead == NULL) { *pphead = newnode; } else//链表不为空 { SLTNode* ptail = *pphead;//用于找到链表的尾节点 while (ptail->next) { ptail = ptail->next; }//找到尾节点 ptail->next = newnode;//给他接上 } } void SLTPushiFront(SLTNode** pphead, SLTDateType x) { assert(pphead); //申请一块新的空间 SLTNode* newnode = SLTBuyNode(x); //把原来第一个结点的地址交给新节点的next newnode->next = *pphead; *pphead = newnode;//把头部指针更新一下 } void SLTPopBack(SLTNode** pphead) { //检验 assert(pphead);//原本的指针不能是空 assert(*pphead);//传过来的不能是空指针给我删除啊 //删除 //这里分两种情况 //1.只有一个节点的情况 if (*pphead == NULL) { free(*pphead); *pphead = NULL; return; } else//多个节点的情况 { //需要找到最后一个节点和倒数第二个节点 //最后一个节点是为了释放空间 //倒数第二个节点是为了把他的next部分置为空 SLTNode* ptail = *pphead; SLTNode* prev = *pphead; while (ptail->next) { prev = ptail; ptail = ptail->next; } //第二个节点next置为空 prev->next = NULL; //销毁第一个节点 free(ptail); ptail = NULL; } } void SLTPopFront(SLTNode** pphead) { //检验 assert(pphead);//检验你别给我传个空 assert(*pphead);//检验链表不为空,空链表删除什么? //开始准备删除 SLTNode* next = (*pphead)->next;//记录一下要接上的地址 free(*pphead);//释放第一个节点 *pphead = next; } void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) { //检验 assert(pphead);//传入地址不得为空 assert(pos);//pos不得为空 assert(*pphead);//要加上链表不得为空, //这是因为pos是链表的一个节点的地址,如 //果链表不存在,那么pos也会不存在 //这里要分情况进行处理 //1.pos刚好是头节点 if (pos == *pphead) { //直接调用头插 SLTPushiFront(pphead,x); return; } else//2.如果pos不是头节点 { SLTNode* prev = *pphead; //让prev找到pos之前的节点的地址 while (prev->next != pos) { prev = prev->next; } SLTNode* newnode = SLTBuyNode(x);//申请空间 prev->next = newnode;//把新空间链接到我们前一个节点 newnode->next = pos;//把新空间与pos指向的空间链接在一起 } } void SLTInsertAfter(SLTNode* pos, SLTDateType x)//之后 { //检查 assert(pos); //申请空间 SLTNode* newnode = SLTBuyNode(x); //关联新节点的错误写法,原因:pos值的改变 //pos->next = newnode; //newnode->next = pos->next; //关联新节点的正确写法: //先接后面,再接前面 newnode->next = pos->next; pos->next = newnode; } void SLTErase(SLTNode** pphead, SLTNode* pos) { //检查 assert(pphead);//不得传空 assert(*pphead);//不得为空链表 assert(pos); //pos刚好是头节点的情况,头删 if (*pphead == pos) { //头删 SLTPopFront(pphead); return; } //如果不是头节点 else { //找到pos之前的节点 SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } } void SLTEraseAfter(SLTNode* pos) { //检查 assert(pos);//传过来的地址不可以为空 assert(pos->next);//传过来的地址的下一个地址不得为空 SLTNode* del = pos->next;//记录要删除的节点的地址 pos->next = pos->next->next;//更新pos中next的地址 free(del);//释放空间 del = NULL;//临时指针及时置为空 } void SLTDestroy(SLTNode** pphead) { assert(pphead);//传过来的指针不得为空 assert(*pphead);//链表不得为空,空链表不用销毁 //创建遍历指针 SLTNode* pcur = *pphead; //循环销毁 while (pcur)//啥时候停止?pcur指向NULL { SLTNode* next = pcur->next; free(pcur); pcur = next; } //最后把头指针也置为NULL *pphead = NULL; }
//test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"SList.h" void test1() { //头指针 SLTNode* phead = NULL; //造一点结点 SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode)); node1->date = 1; SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode)); node2->date = 2; SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode)); node3->date = 3; SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode)); node4->date = 4; SLTNode* node5 = (SLTNode*)malloc(sizeof(SLTNode)); node5->date = 5; //链接这些节点 phead = node1; node1->next = node2; node2->next = node3; node3->next = node4; node4->next = node5; node5->next = NULL; //调用一下打印函数 SLTPrint(phead); } void test2() { SLTNode* phead = NULL; SLTPushBack(&phead,6); SLTPrint(phead); SLTPushiFront(&phead,0); SLTPushiFront(&phead,1); SLTPushiFront(&phead,2); SLTPushiFront(&phead,3); SLTPushiFront(&phead,4); SLTPushiFront(&phead,5); SLTPrint(phead);//5->4->3->2->1->0->6->NULL SLTPopBack(&phead); SLTPrint(phead);//5->4->3->2->1->0->NULL SLTPopFront(&phead); SLTPopFront(&phead); SLTPopFront(&phead); SLTPopFront(&phead); SLTPrint(phead);//1->0->NULL SLTNode* find = SLTFind(&phead,1); SLTInsert(&phead, find, 2); SLTPrint(phead);//2->1->0->NULL SLTInsertAfter(find, 8); SLTPrint(phead);//2->1->8->0->NULL SLTEraseAfter(find); SLTPrint(phead);//2->8->0->NULL SLTDestroy(&phead); SLTPrint(phead);//NULL } int main() { //测试我的打印函数 //test1(); //测试我的插入与删除函数 test2(); return 0; }