单链表的实现

97 阅读10分钟

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

   这种链表每一个节点存一个数据,以及下一个节点的地址,这样可以很好地把每个节点串起来。

单链表是针对顺序表缺点来创建的。

目录

结构体的创建

函数接口的实现

void SLTNodeDestroy(SLTNode** pphead)

SLTNode* BuyListNode(SLTDataType x)

void SLTNodePushBack(SLTNode** pphead, SLTDataType x)

void SLTNodePopBack(SLTNode** pphead)

void SLTNodePushFront(SLTNode** pphead, SLTDataType x)

void SLTNodePopFront(SLTNode** pphead)

void SLTNodePrint(SLTNode* pphead)

SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x)

void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)

void SLTNodeErase(SLTNode** pphead, SLTNode* pos)

单链表完整代码

SList.h

SList.c

代码测试test.c

结构体的创建 typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SListNode* next; }SLTNode; 函数接口的实现 //void SLTNodeInit(SLTNode** ps); //单链表不需要初始化 //对单链表的摧毁 void SLTNodeDestroy(SLTNode** pphead); //尾插 void SLTNodePushBack(SLTNode** pphead, SLTDataType x); //尾删 void SLTNodePopBack(SLTNode** pphead); //头插 void SLTNodePushFront(SLTNode** pphead, SLTDataType x); //头删 void SLTNodePopFront(SLTNode** pphead); //打印单链表 void SLTNodePrint(SLTNode* pphead); //在单链表中查找一个数据,并返回该地址 SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x); //在pos前面插入一个数据 void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x); //不是对pos位置得数据改变,不用传二级指针 //删除pos位置的数据 void SLTNodeErase(SLTNode** pphead, SLTNode* pos);

是不是发现很多函数接口都是传的二级指针。具体为什么呢?

SLTNode* plist = NULL; //在测试代码时,我们对第一个节点的初始化,指针接收

SLTNodePushBack(&plist, 1); //假设我们在这里进行头插,这样有可能改变头结点,plist      是一级指针,为了能够顺利改变,我们在这里使用二级指针(指针变量也是变量)。

否则,SLTNodePushBack(plist, 1); plist传过去后被形参接收,这里没办法找到plist指向        那块空间的地址,尽管形参对应的空间被改变,但是形参的改变不会影响实参。plist实际      指向的那块空间还是没有改变。

void SLTNodeDestroy(SLTNode** pphead) void SLTNodeDestroy(SLTNode** pphead) { SLTNode* cur = pphead; while (cur != NULL) { SLTNode next = cur->next; free(cur); cur = NULL; cur = next; } } //摧毁动态开辟的空间的链表

void SLTNodeDestroy(SLTNode** pphead) //头指针会被改变,所以要传二级指针 { SLTNode* cur = pphead; //将首元素的值赋值给cur while (cur != NULL) //当cur为空时,说明结点被删完了 { SLTNode next = cur->next; //从头开始删,删除头结点时,要提前记录下一个结点的 位置,来保证后面结点能够找到。 free(cur); //释放结点 cur = NULL; cur = next; //头结点位置变成提前记录的位置 } }

SLTNode* BuyListNode(SLTDataType x) SLTNode* BuyListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { printf("malloc fail\n"); exit(-1); } else { newnode->data = x; newnode->next = NULL; } return newnode; }

动态开辟一个结点大小的空间,来存放数据和下一个结点的地址

SLTNode* BuyListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //动态开辟空间 if (newnode == NULL) //如果开辟失败就结束进程 { printf("malloc fail\n"); exit(-1); } else { newnode->data = x; //结点的位置存放一个数据 newnode->next = NULL; //结点指向的下一个地址为空 } return newnode; //返回开辟好结点的地址 }

void SLTNodePushBack(SLTNode** pphead, SLTDataType x) void SLTNodePushBack(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuyListNode(x); if (*pphead == NULL) { pphead = newnode; } else { //找到尾结点 SLTNode tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }

void SLTNodePushBack(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuyListNode(x); //插入数据时要开辟一个空间,用newnode接收这 开辟好空间个地址 if (*pphead == NULL) //*pphead==NULL为空,说明第一个结点没有数据,当链表为空时 { pphead = newnode; //这时候把newnode赋值给第一个空结点 } else { //找到尾结点 SLTNode tail = *pphead; //用一个指针记录尾结点,避免对头结点进行修改 while (tail->next != NULL)//当下一个结点为空,那么当前结点不为空,且为最后一结点 { tail = tail->next; //tail指针不断指向后面结点的地址 } tail->next = newnode; //最后一个结点的下一个结点被赋值成要插入的结点,相当于尾插 } }

void SLTNodePopBack(SLTNode** pphead) void SLTNodePopBack(SLTNode** pphead) { assert(pphead); assert(pphead); if ((pphead)->next == NULL) { SLTNode cur = pphead; free(cur); cur = NULL; } else { SLTNode prev = NULL; SLTNode tail = *pphead; while (tail->next != NULL) { prev = tail; tail = tail->next; } free(tail); tail = NULL; prev->next = NULL; } }

删除单链表最后一个结点

void SLTNodePopBack(SLTNode** pphead) { assert(pphead);//判断接收的结点是有效地址 assert(pphead);//判断第一个结点是否为空,为空,则没有结点 if ((pphead)->next == NULL) //判断链表中是否只有一个结点 { SLTNode cur = pphead; free(cur); //释放第一个结点 cur = NULL; } else { SLTNode prev = NULL; //记录最后一个结点的前一个结点的地址,第一次置为NULL SLTNode tail = *pphead;//记录最后一个结点 while (tail->next != NULL) //tail最后会指向最后一个结点 { prev = tail; //perv被赋值成(原来)tail的地址 tail = tail->next; //tail指向下一个结点的位置 } free(tail); tail = NULL; prev->next = NULL;//prev存放的是原链表倒数第二个结点的地址,这时候要当成尾结点 } }

void SLTNodePushFront(SLTNode** pphead, SLTDataType x) void SLTNodePushFront(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuyListNode(x); if (*pphead == NULL) { *pphead = newnode; } else { newnode->next = *pphead; *pphead = newnode; } } 在链表第一个位置前插入一个数据

void SLTNodePushFront(SLTNode** pphead, SLTDataType x)//传二级指针,可能修改第一 个结点 { SLTNode* newnode = BuyListNode(x); //开辟好要加入的结点 if (*pphead == NULL) //判断链表中没有结点的情况 { *pphead = newnode; //第一个空结点被赋值成newnode } else//当链表中有至少一个结点时 { newnode->next = *pphead;//新节点的下一个结点指向头结点 *pphead = newnode;//头结点再变成新节点,这时候相当于头插了 } }

void SLTNodePopFront(SLTNode** pphead) void SLTNodePopFront(SLTNode** pphead) { assert(pphead); assert(*pphead); if ((*pphead)->next == NULL) { free(*pphead); pphead = NULL; } else { SLTNode next = (*pphead)->next; free(*pphead); *pphead = next; } }

删除链表中的第一个结点

void SLTNodePopFront(SLTNode** pphead) { assert(pphead);//判断接收的结点是有效地址 assert(*pphead);//判断第一个结点是否为空,为空,则没有结点 if ((*pphead)->next == NULL)//当链表头结点的下一个结点为空,则单链表中只有一个结点 { free(*pphead); //释放头结点 pphead = NULL; //头结点置空 } else//当链表有有个以上的结点时 { SLTNode next = (*pphead)->next;//提前记录头结点下一个结点的位置,防止找不到 free(*pphead);//释放头结点,有上一个步骤,就不怕找不到后面的结点 *pphead = next; //这时候头结点变成原结点的下一个结点,这时候就是头删了 } }

void SLTNodePrint(SLTNode* pphead) void SLTNodePrint(SLTNode* pphead) { SLTNode* cur = pphead; while (cur!= NULL) { printf("%d ", cur->data); cur = cur->next; } printf("\n"); } 打印单链表中的数据

void SLTNodePrint(SLTNode* pphead) { SLTNode* cur = pphead; //用一个指针代表头结点,避免头结点的随意被改变 while (cur!= NULL) //cur为空的时候停止,说明不为空的结点的数据已经被打印了 { printf("%d ", cur->data); cur = cur->next;//cur不断指向下一个结点 } printf("\n"); }

SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x)

SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x) { SLTNode* cur = pphead; while (cur != NULL) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; //没找到 } 在单链表中查找一个数据

SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x) { SLTNode* cur = pphead;//用一个指针代表头结点,避免头结点的随意被改变 while (cur != NULL)//cur为空的时候停止,说明不为空的结点的数据已经被判断过了 { if (cur->data == x) //当找到要查找的数据时 { return cur; } cur = cur->next; } return NULL; //没找到 }

void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) //不是对pos位置得数据改变,不用传二级指针 { assert(pphead); assert(pos); SLTNode* newnode = BuyListNode(x); if (*pphead == pos) { newnode->next = *pphead; pphead = newnode; } else { SLTNode prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } }

在pos位置前插入一个数据

void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) //不是对pos位置 得数据改变,不用传二级指针 { assert(pphead); assert(pos); //判断pos传过来的值不是有效值 SLTNode* newnode = BuyListNode(x);//开辟新节点 if (*pphead == pos) //当第一个结点为pos时 { newnode->next = *pphead; pphead = newnode; } else { SLTNode prev = *pphead; while (prev->next != pos)//当pos下一个结点为pos时就停止, { prev = prev->next; } prev->next = newnode;//这时候prev记录pos前一个结点,prev下一个结点指向新节点 newnode->next = pos;//新节点下一个结点指向pos } }

void SLTNodeErase(SLTNode** pphead, SLTNode* pos) void SLTNodeErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(*pphead); assert(pos); if (pphead == pos) { SLTNodePopFront(pphead); } else { SLTNode prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }

删除单链表中pos位置的结点

void SLTNodeErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(*pphead);//判断第一个结点是否为空,为空就没有结点,就不能删 assert(pos); //判断pos传过来的值不是有效值 if (pphead == pos) //当pos值为头结点时,相当于头删 { SLTNodePopFront(pphead); //头删 } else //当pos不指向第一个结点时 { SLTNode prev = *pphead;//记录pos前一个结点,这样能够使前后结点连接上 while (prev->next != pos) //prev事项pos前一个结点时结束 { prev = prev->next; } prev->next = pos->next; //pos前一个结点pos的下一个结点 free(pos);//释放pos结点,这样pos位置的结点就删除了,前后链表连接上了 pos = NULL; } }

单链表完整代码 SList.h #define _CRT_SECURE_NO_WARNINGS 1 #pragma once

#include<stdio.h> #include<assert.h>
#include<stdlib.h>

typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SListNode* next; }SLTNode;

//void SLTNodeInit(SLTNode** ps); //单链表不需要初始化 //对单链表的摧毁 void SLTNodeDestroy(SLTNode** pphead); //尾插 void SLTNodePushBack(SLTNode** pphead, SLTDataType x); //尾删 void SLTNodePopBack(SLTNode** pphead); //头插 void SLTNodePushFront(SLTNode** pphead, SLTDataType x); //头删 void SLTNodePopFront(SLTNode** pphead); //打印单链表 void SLTNodePrint(SLTNode* pphead); //在单链表中查找一个数据,并返回该地址 SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x); //在pos前面插入一个数据 void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x); //不是对pos位置得数据改变,不用传二级指针 //删除pos位置的数据 void SLTNodeErase(SLTNode** pphead, SLTNode* pos);

SList.c #define _CRT_SECURE_NO_WARNINGS 1 #include"SList.h"

void SLTNodeDestroy(SLTNode** pphead) { SLTNode* cur = pphead; while (cur != NULL) { SLTNode next = cur->next; free(cur); cur = NULL; cur = next; } }

SLTNode* BuyListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { printf("malloc fail\n"); exit(-1); } else { newnode->data = x; newnode->next = NULL; } return newnode; }

void SLTNodePushBack(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuyListNode(x); if (*pphead == NULL) { pphead = newnode; } else { //找到尾结点 SLTNode tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }

void SLTNodePopBack(SLTNode** pphead) { assert(pphead); assert(pphead); if ((pphead)->next == NULL) { SLTNode cur = pphead; free(cur); cur = NULL; } else { SLTNode prev = NULL; SLTNode tail = *pphead; while (tail->next != NULL) { prev = tail; tail = tail->next; } free(tail); tail = NULL; prev->next = NULL; } }

void SLTNodePushFront(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = BuyListNode(x); if (*pphead == NULL) { *pphead = newnode; } else { newnode->next = *pphead; *pphead = newnode; } }

void SLTNodePopFront(SLTNode** pphead) { assert(pphead); assert(*pphead); if ((*pphead)->next == NULL) { free(*pphead); pphead = NULL; } else { SLTNode next = (*pphead)->next; free(*pphead); *pphead = next; } }

void SLTNodePrint(SLTNode* pphead) { SLTNode* cur = pphead; while (cur!= NULL) { printf("%d ", cur->data); cur = cur->next; } printf("\n"); }

SLTNode* SLTNodeFind(SLTNode* pphead, SLTDataType x) { SLTNode* cur = pphead; while (cur != NULL) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; //没找到 }

void SLTNodeInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) //不是对pos位置得数据改变,不用传二级指针 { assert(pphead); assert(pos); SLTNode* newnode = BuyListNode(x); if (*pphead == pos) { newnode->next = *pphead; pphead = newnode; } else { SLTNode prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } }

void SLTNodeErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(*pphead); assert(pos); if (pphead == pos) { SLTNodePopFront(pphead); } else { SLTNode prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }

代码测试test.c #define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h" void Test1() { SLTNode* plist = NULL; SLTNodePushBack(&plist, 1); SLTNodePushBack(&plist, 2); SLTNodePushBack(&plist, 3); SLTNodePushBack(&plist, 4); SLTNodePushBack(&plist, 3); SLTNodePushBack(&plist, 3);

SLTNodePopBack(&plist);

//SLTNodePopFront(&plist);

SLTNodePushFront(&plist, 10);
SLTNodePushFront(&plist, 20);

SLTNode* pos = SLTNodeFind(plist, 10); //不用取地址
if (pos == NULL)
{
	printf("没找到\n");
}
else
{
	SLTNodeInsert(&plist, pos, 100);
}

pos = SLTNodeFind(plist, 3);
//int i = 1;
//while (pos)
//{
//	printf("要查找的数据的第%d个的地址:%p\n", i,&(pos->data));
//	i++;
//	pos = SLTNodeFind(pos->next, 3);
//}
if(pos!=NULL)
SLTNodeErase(&plist, pos);

SLTNodePrint(plist);
SLTNodeDestroy(&plist);
plist = NULL;

} int main() { Test1(); return 0; }