链表的概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
单链表是链表很重要的一种,如果理解了它,那么对于其他链表的理解,就会事半功倍。
目录
3.6 查找数据函数:给一个数据,返回这个数据所在节点的地址的函数
3.7 随即删除节点:给数据返回其所在节点地址,删除这个节点的函数
3.8 修改数据函数:给一个数据,找到这个数据所在的节点,并用新数据修改
一、单链表的处理
1、节点的设置
因为单链表里面每个节点,都会存储一个数据和一个指向下一个节点的指针,所以用结构体来表示节点。
typedef int SLdatetype;//重定义类型名,这样可以修改想要的类型
typedef struct SListNode SLNode;//重定义结构体类型的名字
struct SListNode //单链表节点
{
SLdatetype date;
SLNode* next;
};
2、节点(结构体)内存空间的开辟
因为每个节点都是结构体,为了不会导致内存空间的浪费,需要用一个节点,就开辟一个节点。所以用动态内存开辟函数malloc即可,每次开辟一个结构体大小的内存空间。但是要注意,每个节点创建的时候,其里面的指针都要先置为空指针,方便后面的修改。
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = x;//将需要的数据x放入节点里
newnode->next = NULL;//创建一个新的节点
二、单链表功能的实现
1、整体框架
int main()
{
menu();
int input = 0;
do
{
fprintf(stdout, "请输入:>");
fscanf(stdin,"%d", &input);
switch (input)
{
case 1:
testSList1();//进入尾插数据操作
fprintf(stdout, "尾插数据成功\n");
break;
case 2:
testSList2();//进入头插数据操作
fprintf(stdout, "头插数据成功\n");
break;
case 3:
testSList3();//进入头删数据操作
fprintf(stdout, "头删数据成功\n");
break;
case 4:
testSList4();//进入尾删数据操作
fprintf(stdout, "尾删数据成功\n");
break;
case 5:
testSList5();//进入随机插入数据操作
fprintf(stdout, "随机插入数据成功\n");
break;
//testSList6();
case 6:
testSList7();//进入随机删除数据操作
fprintf(stdout, "随机删除数据成功\n");
break;
case 7:
testSList8();//进入修改数据操作
break;
case 0:
printf("退出\n");
break;
default:
fprintf(stdout, "选择错误,请重新选择\n");
}
} while (input);
}
用do......while循环语句和swith分支语句来进入相应的功能。
2、主菜单
void menu()
{
printf("*************************************\n");
printf("***** 1、尾插 2、头插 *****\n");
printf("***** 3、头删 4、尾删 *****\n");
printf("***** 5、随机插 6、随机删 *****\n");
printf("***** 7、修改 0、退出 *****\n");
printf("*************************************\n");
}
3、功能的实现
3.1 打印单链表数据功能的实现
//打印单链表
void SListPrint(SLNode* phead)//实参是单链表的第一个节点的地址
{
while (phead != NULL)
{
printf("%d->", phead->date);
phead = phead->next;
}
printf("NULL\n");
}
形参phead接受的是单链表第一个节点的地址(即plist),通过每次循环,打印对应节点的数据
3.2 从单链表尾部插入节点功能的实现
//尾插函数
void SListPushBack(SLNode** pphead, SLdatetype x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = x;
newnode->next = NULL;//创建一个新的节点
if (*pphead == NULL)//当没有节点的时候,这时候开辟的内存就给plist
{
*pphead = newnode;
}
else
{
SLNode* cru = *pphead;
while (cru->next != NULL)//这里必须改变节点里面的指针next存放的地址
{
cru = cru->next;
}
cru->next = newnode;//所以这里必须用cru->next访问这个指针才可以改变next存放的地址
}
}
3.3 从单链表头部插入节点功能的实现
//头插函数
void SListPushFront(SLNode** pphead, SLdatetype x)
{
SLNode* newcode = (SLNode*)malloc(sizeof(SLNode));
newcode->date = x;
newcode->next = NULL;
//if (*pphead == NULL)//当单链表里没有节点的时候
//{
// *pphead = newcode;
//}
//else
//{
newcode->next = *pphead;//这里已经包括了单链表没有节点的时候,所以可以不用上面if
*pphead = newcode;
//}
}
从单链表头部和尾部插入节点函数都需要先创建一个新的节点。
3.4 从单链表头部删除节点功能的实现
//头删函数 因为节点是动态开辟的,所以直接用free函数就能删除,
//一般来说释放空间后,指向这块空间的指针要置为空指针
void SListPopFront(SLNode** pphead)
{
if (*pphead != NULL)//如果空指针,即没有节点,那么就不需要删除
{
SLNode* next0 = (*pphead)->next;//这里也满足只有一个节点的情况
free(*pphead);
*pphead = next0;
}
}
3.5 从单链表尾部删除节点功能的实现
//尾删函数 因为同样节点是动态开辟的,所以直接用free函数释放空间
void SListPopBack(SLNode** pphead)
{
if (*pphead)//如果单链表没有节点,即plist是空指针,就不进入尾删
{
SLNode* cru = *pphead;
SLNode* prev = NULL;
if((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
while (cru->next != NULL)//新定义一个指针,得到尾删节点的前一个节点的地址
{
prev = cru;
cru = cru->next;
}
prev->next = NULL;
free(cru);
cru = NULL;
}
}
3.6 查找数据函数:给一个数据,返回这个数据所在节点的地址的函数
//给一个数据,返回这个数据所在节点的地址的函数
SLNode* SListFindDate(SLNode* phead, SLdatetype x)
//因为不需要改变指向第一个节点的指针的值,
//即不需要改变这个指针存放的地址
{
while (phead != NULL)//如果没有节点,根本不会进入循环去找
{
if (phead->date == x)
{
return phead;
}
else
{
phead = phead->next;
}
}
return NULL;
}
3.7 随即插入节点:在返回的节点地址前插入一个节点的函数
//在返回的节点地址pos前插入一个数据的函数
void SListPushpos(SLNode** pphead,SLdatetype x,SLdatetype y)
{
SLNode* prev = NULL;
SLNode* cru = *pphead;
SLNode* pos = SListFindDate(*pphead, x);
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = y;
newnode->next = NULL;
if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机插
{
while (cru != pos)
{
prev = cru;
cru = cru->next;
}
if (prev != NULL)
{
newnode->next = pos;
prev->next = newnode;
}
else
{
newnode->next = pos;
*pphead = newnode;
}
}
}
随机插入一个节点函数:是先通过查找函数,返回提供的数据其所在节点的地址,这里用指针pos接收,再将这个字节的前面插入一个新的节点;想在哪个节点前插入,就通过查找函数返回那个节点的地址。
3.7 随即删除节点:给数据返回其所在节点地址,删除这个节点的函数
//给数据返回其所在节点地址,删除这个节点的函数
void SListPopdate(SLNode** pphead, SLdatetype x)
{
SLNode* cru = *pphead;
SLNode* prev = NULL;
SLNode* pos = SListFindDate(*pphead, x);
if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机删
{
while (cru != pos)
{
prev = cru;
cru = cru->next;
}
if (prev != NULL)
{
prev->next = pos->next;
free(pos);
pos = NULL;
}
else
{
*pphead = pos->next;
free(cru);
cru = NULL;
}
}
}
随机删除一个节点函数:同样,是先通过查找函数,返回一个节点的地址,这里用指针pos接收,再将这块空间释放掉,因为是动态开辟的,所以就删除该节点了;想删除哪个节点,就通过查找函数返回那个节点的地址。
3.8 修改数据函数:给一个数据,找到这个数据所在的节点,并用新数据修改
//给一个数据,找到这个数据所在的节点,并用新数据修改
void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y)
//因为不需要改变节点的地址,所以值传递即可
//x是查找的数据,y是新数据,用来修改查找的数据
{
SLNode* cru = phead;
while (cru != NULL)//如果没有节点,根本不会进入循环去找
{
if (cru->date == x)
{
cru->date = y;
break;//修改完数据后,就跳出循环
}
else
{
cru = cru->next;
}
}
if (cru == NULL)//如果循环完单链表,没有找到要修改的那个数据
{
fprintf(stdout, "要修改的数据不存在,请重新修改数据\n");
}
else
{
fprintf(stdout, "修改成功\n");
}
}
修改数据函数:同样,是先通过查找函数,返回一个节点的地址,这里用指针pos接收,再用新的数据y修改之前的数据x;想修改哪个节点里的数据,就通过查找函数返回那个节点的地址。
三、总代码
1、放置主框架和菜单等代码的源函数test.c
2、防止函数声明和结构体声明等代码的头文件SList.h
3、实现各种功能函数设计的代码的源文件SList.c
1、源函数代码test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//主菜单
void menu()
{
printf("*************************************\n");
printf("***** 1、尾插 2、头插 *****\n");
printf("***** 3、头删 4、尾删 *****\n");
printf("***** 5、随机插 6、随机删 *****\n");
printf("***** 7、修改 0、退出 *****\n");
printf("*************************************\n");
}
//验证尾插函数
void testSList1()
{
SLNode* plist = NULL;//指针一定要初始化,不然会变成野指针
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
}
//验证头插函数
void testSList2()
{
SLNode* plist = NULL;
SListPushFront(&plist, 5);
SListPushFront(&plist, 6);
SListPushFront(&plist, 7);
SListPushFront(&plist, 8);
SListPrint(plist);
}
//验证头删函数
void testSList3()
{
SLNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
}
//验证尾删函数
void testSList4()
{
SLNode* plist = NULL;
SListPushFront(&plist, 5);
SListPushFront(&plist, 6);
SListPushFront(&plist, 7);
SListPushFront(&plist, 8);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
}
//验证给数据返回其所在节点地址的函数,和删除数据节点函数或插入数据节点之前的函数配合使用
void testSList5()
{
SLNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListFindDate(plist,2);
}
//验证在返回的节点地址pos前插入一个数据的函数
void testSList6()
{
SLNode* plist = NULL;
SListPushFront(&plist, 5);
SListPushFront(&plist, 6);
SListPushFront(&plist, 7);
SListPushFront(&plist, 8);
SListPushpos(&plist, 8, 1);
SListPrint(plist);
}
//验证给数据返回其所在节点地址,删除这个节点的函数
void testSList7()
{
SLNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPopdate(&plist, 1);
SListPopdate(&plist, 4);
SListPrint(plist);
}
//验证给一个数据,找到这个数据所在的节点,并用新数据修改的函数
void testSList8()
{
SLNode* plist = NULL;
SListPushFront(&plist, 5);
SListPushFront(&plist, 6);
SListPushFront(&plist, 7);
SListPushFront(&plist, 8);
SListChangeDate(plist, 6, 9);
SListPrint(plist);
}
int main()
{
menu();
int input = 0;
do
{
fprintf(stdout, "请输入:>");
fscanf(stdin,"%d", &input);
switch (input)
{
case 1:
testSList1();//进入尾插数据操作
fprintf(stdout, "尾插数据成功\n");
break;
case 2:
testSList2();//进入头插数据操作
fprintf(stdout, "头插数据成功\n");
break;
case 3:
testSList3();//进入头删数据操作
fprintf(stdout, "头删数据成功\n");
break;
case 4:
testSList4();//进入尾删数据操作
fprintf(stdout, "尾删数据成功\n");
break;
case 5:
testSList6();//进入随机插入数据操作
fprintf(stdout, "随机插入数据成功\n");
break;
case 6:
testSList7();//进入随机删除数据操作
fprintf(stdout, "随机删除数据成功\n");
break;
case 7:
testSList8();//进入修改数据操作
break;
case 0:
printf("退出\n");
break;
default:
fprintf(stdout, "选择错误,请重新选择\n");
}
} while (input);
}
2、头文件SList.h代码
#include<stdio.h>
#include<stdlib.h>
typedef int SLdatetype;//重定义类型名,这样可以修改想要的类型
typedef struct SListNode SLNode;//重定义结构体类型的名字
struct SListNode //单链表节点
{
SLdatetype date;
SLNode* next;
};
void SListPrint(SLNode* phead);//打印单链表
void SListPushBack(SLNode** pphead, SLdatetype x);//尾插函数
void SListPushFront(SLNode** pphead, SLdatetype x);//头插函数
void SListPopFront(SLNode** pphead);//头删函数
void SListPopBack(SLNode** pphead);//尾删函数
SLNode* SListFindDate(SLNode* phead, SLdatetype x);//验证给数据返回其所在节点地址的函数
void SListPushpos(SLNode** pphead, SLdatetype x, SLdatetype y);//在返回的节点地址pos前插入一个数据的函数,x是找的数据,y是插入的数据
void SListPopdate(SLNode** pphead, SLdatetype x);//给数据返回其所在节点地址,删除这个节点的函数
void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y);//因为不需要改变节点的地址,所以值传递即可
//x是查找的数据,y是新数据,用来修改查找的数据
3、函数功能实现源文件 SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印单链表
void SListPrint(SLNode* phead)//实参是单链表的第一个节点的地址
{
while (phead != NULL)
{
printf("%d->", phead->date);
phead = phead->next;
}
printf("NULL\n");
}
//尾插函数
void SListPushBack(SLNode** pphead, SLdatetype x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = x;
newnode->next = NULL;//创建一个新的节点
if (*pphead == NULL)//当没有节点的时候,这时候开辟的内存就给plist
{
*pphead = newnode;
}
else
{
SLNode* cru = *pphead;
while (cru->next != NULL)//这里必须改变节点里面的指针next存放的地址
{
cru = cru->next;
}
cru->next = newnode;//所以这里必须用cru->next访问这个指针才可以改变next存放的地址
}
}
//头插函数
void SListPushFront(SLNode** pphead, SLdatetype x)
{
SLNode* newcode = (SLNode*)malloc(sizeof(SLNode));
newcode->date = x;
newcode->next = NULL;
//if (*pphead == NULL)//当单链表里没有节点的时候
//{
// *pphead = newcode;
//}
//else
//{
newcode->next = *pphead;//这里已经包括了单链表没有节点的时候,所以可以不用上面if
*pphead = newcode;
//}
}
//头删函数 因为节点是动态开辟的,所以直接用free函数就能删除,
//一般来说释放空间后,指向这块空间的指针要置为空指针
void SListPopFront(SLNode** pphead)
{
if (*pphead != NULL)//如果空指针,即没有节点,那么就不需要删除
{
SLNode* next0 = (*pphead)->next;//这里也满足只有一个节点的情况
free(*pphead);
*pphead = next0;
}
}
//尾删函数 因为同样节点是动态开辟的,所以直接用free函数释放空间
void SListPopBack(SLNode** pphead)
{
if (*pphead)//如果单链表没有节点,即plist是空指针,就不进入尾删
{
SLNode* cru = *pphead;
SLNode* prev = NULL;
if((*pphead)->next==NULL)
{
free(*pphead);
*pphead = NULL;
}
while (cru->next != NULL)//新定义一个指针,得到尾删节点的前一个节点的地址
{
prev = cru;
cru = cru->next;
}
prev->next = NULL;
free(cru);
cru = NULL;
}
}
//给一个数据,返回这个数据所在节点的地址的函数
SLNode* SListFindDate(SLNode* phead, SLdatetype x)
//因为不需要改变指向第一个节点的指针的值,
//即不需要改变这个指针存放的地址
{
while (phead != NULL)//如果没有节点,根本不会进入循环去找
{
if (phead->date == x)
{
return phead;
}
else
{
phead = phead->next;
}
}
return NULL;
}
//在返回的节点地址pos前插入一个数据的函数
void SListPushpos(SLNode** pphead,SLdatetype x,SLdatetype y)
{
SLNode* prev = NULL;
SLNode* cru = *pphead;
SLNode* pos = SListFindDate(*pphead, x);
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = y;
newnode->next = NULL;
if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机插
{
while (cru != pos)
{
prev = cru;
cru = cru->next;
}
if (prev != NULL)
{
newnode->next = pos;
prev->next = newnode;
}
else
{
newnode->next = pos;
*pphead = newnode;
}
}
}
//给数据返回其所在节点地址,删除这个节点的函数
void SListPopdate(SLNode** pphead, SLdatetype x)
{
SLNode* cru = *pphead;
SLNode* prev = NULL;
SLNode* pos = SListFindDate(*pphead, x);
if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机删
{
while (cru != pos)
{
prev = cru;
cru = cru->next;
}
if (prev != NULL)
{
prev->next = pos->next;
free(pos);
pos = NULL;
}
else
{
*pphead = pos->next;
free(cru);
cru = NULL;
}
}
}
//给一个数据,找到这个数据所在的节点,并用新数据修改
void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y)
//因为不需要改变节点的地址,所以值传递即可
//x是查找的数据,y是新数据,用来修改查找的数据
{
SLNode* cru = phead;
while (cru != NULL)//如果没有节点,根本不会进入循环去找
{
if (cru->date == x)
{
cru->date = y;
break;//修改完数据后,就跳出循环
}
else
{
cru = cru->next;
}
}
if (cru == NULL)//如果循环完单链表,没有找到要修改的那个数据
{
fprintf(stdout, "要修改的数据不存在,请重新修改数据\n");
}
else
{
fprintf(stdout, "修改成功\n");
}
}