基本数据结构之-单链表的链式存储
链表是一种插和删除元素很便捷的数据结构,原因是它存储的数据在内存中不连续,可以根据需要自己动态开辟。
和数组比较起来主要的缺点就是不能随机访问,如果是单链表,那么要访问一个数据,必须从头开始遍历一次!
对于基本数据结构围绕增删查改等操作,单链表的改可以通过删和增的结合来操作。因此就不用代码来表示了!
任何一个数据结构都有它的基本的数据结构,这儿给出一种用void *代替的写法
基本的数据结构的定义:
// 定义指针的结构体
typedef struct _LINKLISTNODE
{
struct _LINKLISTNODE *Next;
}LinkListNode;
// 定义链表的结构体
typedef struct _LINKLIST
{
LinkListNode *head;
int LinkListLen;
}LinkList;
基本结构体有了,可以初始化一个变量了,但是需要相关的函数的支持
这个可以定义一个一个LinkList类型的变量,让后把地址传递过去
// 初始化
int Init_LinkList(void ** LINKLIST)
{
// 对传入的参数进行检查
if (LINKLIST == NULL)
{
exit(-1);
}
// 开辟空间
LinkList *linklist =(LinkList *) malloc(sizeof(LinkList));
if (linklist == NULL)
{
// 空间开辟是否成功
exit(-2);
}
// 赋初始值
linklist->head = NULL;
linklist->LinkListLen = 0;
// 指针间的间接赋值
*LINKLIST = (void *)linklist;
return 0;
}
头结点有了,接下来就是增加这个链表的数据域的指针,三个函数,指定位置插入,头插,尾插
// 指定位置插入
int InsertByPos_LinkList(void * LINKLIST, int Pos, void * Data)
{
if (LINKLIST == NULL)
{
return -1;
}
if (Data == NULL)
{
return -2;
}
// 对传入的参数进行类型强转
LinkList *linklist = (LinkList *)LINKLIST;
// 对 Pos 合理化处理
if (Pos < 0)
{
Pos = 0;
}
if (Pos > linklist->LinkListLen)
{
Pos = linklist->LinkListLen;
}
/* 这个为什么是取地址了?
* 当程序初始化成功后,直接调用InsertByPos_LinkList(void * LINKLIST, int Pos, void * Data),
* 此时 head 为空,后面读取不到next指针
* 当其他时候调用改函数时,我们将这个值赋值给current的到的是linklist 的地址
* 但是是怎么包含到next的了
* linklist 中包含两个数据,一个时head 一个时 LinkListLen 当在解析current的next的时候编译器
* 默认帮你解释到head->next; ????
*/
LinkListNode *Current = &(linklist->head);
for (int i = 0; i < Pos; ++i)
{
Current = Current->Next;
}
// 转化指针类型
LinkListNode *data = (LinkListNode *)Data;
// 将传入的值加入到链表中
data->Next = Current->Next;
Current->Next = data;
++ linklist->LinkListLen;
return 0;
}
int FrontInsert_LinkList(void * LINKLIST, void * Data)
{
if (LINKLIST == NULL)
{
return -1;
}
if (Data == NULL)
{
return -2;
}
// 直接调用函数 实现
InsertByPos_LinkList(LINKLIST, 0, Data);
return 0;
}
int BackInsert_LinkList(void * LINKLIST, void * Data)
{
if (LINKLIST == NULL)
{
return -1;
}
if (Data == NULL)
{
return -2;
}
LinkList *linklist = (LinkList *)LINKLIST;
// 直接调用函数实现
InsertByPos_LinkList(LINKLIST, linklist->LinkListLen, Data);
return 0;
}
能插入元素不行,还要能显示
因为通用性的:所以需要定义一个函数指针,也就是回调函数
// 用于打印时调用用户自己定义的打印函数,
typedef void(*Print)(void *);
int Print_LinkList(void * LINKLIST, Print print)
{
if (LINKLIST == NULL)
{
return -1;
}
if (print == NULL)
{
return -2;
}
LinkList *linklist = (LinkList *)LINKLIST;
LinkListNode *Current = linklist->head;
while (Current != NULL)
{
print(Current);
Current = Current->Next;
}
printf("……………………………………………………\n");
return 0;
}
打印有了,插入有了,现在我想删除
删除也给了三个函数,指定位置的删除,头删,尾删
int EraseByPos_LinkList(void * LINKLIST, int Pos)
{
if (LINKLIST == NULL)
{
return -1;
}
LinkList *linklist = (LinkList *)LINKLIST;
// 检测删除位置的合理性
if (Pos < 0 || Pos>linklist->LinkListLen)
{
return -2;
}
LinkListNode *Current =&(linklist->head);
/* 为什么需要检测Current->Next->Next!=NULL不为空,因为后面需要直接将Current->Next->Next赋值给Current->Next
* 如果为空的话,访问会出错
*/
for (int i = 0; i < Pos && Current->Next->Next!=NULL ; ++i)
{
Current = Current->Next;
}
// 直接越过需要删除的值
Current->Next = Current->Next->Next;
// 维护链表的长度
--linklist->LinkListLen;
return 0;
}
int FrontErase_LinkList(void * LINKLIST)
{
if (LINKLIST == NULL)
{
return -1;
}
// 调用函数实现
EraseByPos_LinkList(LINKLIST, 0);
return 0;
}
int BackErase_LinkList(void * LINKLIST)
{
if (LINKLIST == NULL)
{
return -1;
}
// 调用函数实现
LinkList *linklist = (LinkList *)LINKLIST;
EraseByPos_LinkList(LINKLIST, linklist->LinkListLen);
return 0;
}
增删有了,改就可以实现了,基本思路就是先把要修改的数据删除,让后把要系修改的数据插入。
查找函数这儿就不给出了,可以根据下面的排序改写;
突然想看看我的链表有多长了,不想看了,要等数据被遍历一次才能知道,还好教我的老师给我说了先自己维护一个变量来记录当前链表的长度,需要的时候直接返回这个值就好了
// 返回链表数据的长度
int Len_LinkList(void * LINKLIST)
{
if (LINKLIST == NULL)
{
return -1;
}
LinkList *linklist = (LinkList *)LINKLIST;
return linklist->LinkListLen;
}
下面的代码不一定你常用,但是了解一下也是不错的
单链表的逆置,前面的那种写法比较简单时间和空间效率都比较高,后面的那种办法是我看到这个问题第一时间的反应
// 将链表的数据逆置
int Retrograde_LinkList(void * LINKLIST)
{
if (LINKLIST == NULL)
{
return -1;
}
LinkList *linklist = (LinkList *)LINKLIST;
#if 0
LinkListNode *pPre = linklist->head;
LinkListNode *Current = linklist->head->Next;
/*
* 将 linklist->head->nex赋值为空,
* link->head->next 不就是 Current 吗!下面的循环不是都进不去,错了,
* LinkListNode *Current = linklist->head->Next;这段代码,
* 已经将 linklist->head->Next 赋值给Current,此时修改原来的数据对它是没有影响的,
* 因为 current 和 linklist->head->Next 用的不是同一个内存空间
*/
pPre->Next = NULL;
/*
* 逆置简单的理解就是将所有的数据的next指向他们的直接前驱,让后把最后一个数据的地址给 head
*/
while (Current!= NULL)
{
// 用一个临时变量来缓存pPre;
LinkListNode *Temp = pPre;
// 让pPre指向current
pPre = Current;
// current 向前移动到下一个元素
Current = Current->Next;
// 将next指针指向它原来的直接前驱
pPre->Next = Temp;
}
// 最后将 linklist 的 head 指向原来的最后一个数据的地址
linklist->head = pPre;
#else
// 现将最后一个数据的位置缓存下来
LinkListNode *kpBack= &(linklist->head);
while (kpBack->Next != NULL)
{
kpBack = kpBack->Next;
}
int flag = 1;
while (flag)
{
/*
* 第二种方式比较耗时间和内存,因为每次都需要循环遍历找到当前的末尾,让后将它的前驱置空,后继指向它的前驱
*/
LinkListNode *pPre = &(linklist->head);
LinkListNode *Current = pPre->Next;
// 循环遍历找到最后一个的前驱
while (Current->Next != NULL)
{
pPre = pPre->Next;
Current = Current->Next;
}
if (pPre == &(linklist->head))
{
flag = 0;
break;
}
// 前驱置空
pPre->Next = NULL;
// next 指向前驱
Current->Next = pPre;
}
// 最后将head指向原来的最后一个元素
linklist->head = kpBack;
#endif
return 0;
}
无聊的我还想给单链表排个序,排序就意味着需要比较,所以需要定义一个比较的函数指针
(某培训机构的助教说我这代码有问题,请各位帮我看看这个排序算法的问题!真心的感谢!)
// 比较函数指针
typedef int(*COMPARE)(void *, void *);
// 排序
int SortExchangePos_LinkList(void * LINKLIST,COMPARE compare)
{
if (LINKLIST == NULL)
{
return -1;
}
LinkList *linklist = (LinkList *)LINKLIST;
LinkListNode *Pre = &(linklist->head);
int IsSort=1;
// 这儿借鉴了冒泡排序的有标记位的办法
while (Pre->Next!= NULL && IsSort)
{
IsSort = 0;
for (LinkListNode *pCurrent = &(linklist->head); pCurrent!= NULL && pCurrent->Next != NULL ; pCurrent = pCurrent->Next)
{
if(compare(pCurrent,pCurrent->Next)< 0) // 需要自己写比较函数,比较函数待优化
{
LinkListNode *TempCurrent = &(linklist->head);
while (TempCurrent!=NULL && TempCurrent->Next!=NULL && TempCurrent->Next->Next!= NULL && TempCurrent->Next->Next != pCurrent->Next )
TempCurrent=TempCurrent->Next;
LinkListNode *Temp = pCurrent->Next->Next;
TempCurrent->Next = pCurrent->Next;
pCurrent->Next->Next = pCurrent;
pCurrent->Next = Temp;
IsSort = 1;
}
}
Pre = Pre->Next;
}
return 0;
}
有点时候需要你把你自己开辟的空间释放了,这儿没有去释放每一个数据域,是因为我根本不知道我要把数据放到那,还是不要想了!等到自己写测试的时候,自己在释放吧!
int Destroy_LinkList(void * LINKLIST)
{
if (LINKLIST == NULL)
{
return -1;
}
LinkList *linklist = (LinkList *)LINKLIST;
if (linklist != NULL)
{
free(linklist);
}
return 0;
}