在顺序表中实现数据的增删的操作时,都要把操作位置之后的数据全部移动一遍,操作效率低下。其次是容量固定(静态顺序表),虽然在动态顺序表中容量可变,但也会造成空间上的浪费。
单链表就完美解决了上述缺点。
这里要说明一点,单链表与顺序表没有哪个比哪个更好,只有哪个更适应场合。
介绍完上述后,现在开始单链表的实现,实现单链表需要用到结构体指针。
链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回(将体现在指定位置插入删除的操作,解决了为什么没有传址而传值的疑惑)
创建结构体:
typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{
SLTDataType data;
SLTNode* next;
}SLTNode;
结构体指针SLTNode*储存下一个节点,可以理解为一条链条或套娃。直到最后一个节点的next指向空(NULL)时
需要注意的是这里SLTNode我进行了前置声明因此可以直接用转换后的语句。
如果没有前置声明,STLNode不会被识别出来,这里需要说一下,操作系统在找SLTNode时只会向上找不会向向下找,这也就是为什么有许多代码中会有前置声明的函数或者像当先操作的转换一样。
能直观的看到前置声明的重要性。
创建一个头文件SListNode.h用来放需要实现的函数(声明)
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{SLTDataType data;SLTNode* next;
}SLTNode;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);
创建SListNode.c文件用来实现头文件的函数
首先是尾插
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
//不能直接这样用*pphead->next = NULL;加括号(*pphead)->next, 优先级问题
//链表为空
if (*pphead == NULL)
{
*pphead = newnode;
}
//链表不为空,找尾节点
SLTNode* node = *pphead;
while (node->next)
{
node = node->next;
}
node->next = newnode;
}
使用SLTNode** pphead接收,传递的phead是SLTNode*类型,而我们需要改变phead的内容,因此要传址而非传值,故用二级指针接收。
在这里我想用*pphead->next可是会报错,这是为什么呢,因为优先级,->的优先级比*的优先级高,pphead先与->结合,因此报错。
首先创建 节点(结构体指针newnode)保存需要插入的值,然后要判断两种情况,一种是*pphead为空时,直接让*pphead等于newnode。第二种*pphead不为空,创建一个node等于*pphead,循环向后找,找到node->next为空时,node->next = newnode。
头插的实现
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
if (*pphead == NULL)
{
*pphead = newnode;
newnode->next = NULL;
}
else
{
SLTNode* ret = *pphead;
newnode->next = ret;
*pphead = newnode;
}
}
头插跟尾插一样需要判断两种情况,当*pphead为空和不为空的两种情况。为空直接等于,不为空创建临时的ret保存*pphead,然后让*pphead(头节点)等于newnode。
尾删的实现
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead);
assert(pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* node = *pphead;
SLTNode* ret = node->next;while (ret->next)
{
node = node->next;
ret = ret->next;
}
free(ret);
ret = NULL;
node->next = NULL;
}
}
删除涉及到空间的释放。因此要判断*ppead和ppead是否为空,不能传递一个空值来删除释放吧,所以要用assert断言。
也是要判断两种情况,一种是只有一个节点时,另一种是多个节点时。当为一个节点时,直接释放后置空即可,当为多个节点时需要遍历到最后一个节点后进行删除。
头删的实现
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
assert(pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* node = (*pphead)->next;
free(*pphead);
*pphead = node;
}
}
头删和尾删还是差不多,需要判断两种情况,是否是一个节点或多个节点。一个节点直接删除即可,多个节点,将头节点指向第二个节点。
查找的实现
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* node = phead;
while (node)
{
if (node->data == x)
{
return node;
}
node = node->next;
}
return NULL;
}void SLTfind(SLTNode* phead, SLTDataType x)
{
SLTNode* ret = SLTFind(phead, x);
if (ret)
{
printf("找到了%d\n", ret->data);
}
else
{
printf("没找到%d\n", x);
}
}
先创建SLTFind函数返回需要查找的节点(在指定位置操作时需要用到,因此将查找节点的操作分割出来),后用SLTfind判断。
在指定位置之前插入的实现
//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(*pphead);
assert(pphead);
assert(pos);
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
if (pos == *pphead)
{
SLTNode* node = (*pphead);
*pphead = newnode;
(*pphead)->next = node;
return;
}SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回
pos节点用SLTFind函数寻找,需要断言 pos是否为空,要判断*ppead和ppead是否为空。
判断两种情况,当插入节点为头节点时和不为头节点时。当插入节点为头节点时将头节点指向newnode。当不为头节点时创建prev使用while循环,当 prev->next=pos时退出,让prev的下个节点指向newnode,newnode的下个节点指向pos。
pos节点删除的实现
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);
assert(pphead);
assert(pos);if (pos == *pphead)
{
SLTNode* ret = *pphead;
*pphead = (*pphead)->next;
free(ret);
ret = NULL;
return;
}SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* node = pos->next;
prev->next = node;
free(pos);
pos = NULL;
}
SLTFind函数寻找pos节点,判断两种情况,pos是否为头节点,然后进行删除操作。
在指定位置之后插入的实现
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;SLTNode* prve = pos->next;
pos->next = newnode;
newnode->next = prve;
}
这个更简单。
删除pos之后的节点的实现
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);
SLTNode* prve = pos->next;
SLTNode* node = prve;
while (node)
{
node = prve->next;
free(prve);
prve = node;
}
pos->next = NULL;
}
找到pos后循环释放。
销毁链表的实现
void SListDesTroy(SLTNode** pphead)
{
assert(*pphead);
assert(pphead);
SLTNode* prve = (*pphead);
while(prve)
{
prve = prve->next;
free(*pphead);
*pphead = NULL;
*pphead = prve;
}
}
一样的循环释放。
下面是SListNode.c代码
#include "SListNode.h"//打印
void SLTPrint(SLTNode* phead)
{while (phead){printf("%d->", phead->data);phead = phead->next;}printf("NULL\n");
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;//不能直接这样用*pphead->next = NULL;加括号(*pphead)->next, 优先级问题//链表为空if (*pphead == NULL){*pphead = newnode;}//链表不为空,找尾节点SLTNode* node = *pphead;while (node->next){node = node->next;}node->next = newnode;
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;if (*pphead == NULL){*pphead = newnode;newnode->next = NULL;}else{SLTNode* ret = *pphead;newnode->next = ret;*pphead = newnode;}
}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(*pphead);assert(pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* node = *pphead;SLTNode* ret = node->next;while (ret->next){node = node->next;ret = ret->next;}free(ret);ret = NULL;node->next = NULL;}
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(*pphead);assert(pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* node = (*pphead)->next;free(*pphead);*pphead = node;}
}//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* node = phead;while (node){if (node->data == x){return node;}node = node->next;}return NULL;
}void SLTfind(SLTNode* phead, SLTDataType x)
{SLTNode* ret = SLTFind(phead, x);if (ret){printf("找到了%d\n", ret->data);}else{printf("没找到%d\n", x);}
}//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(*pphead);assert(pphead);assert(pos);SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;if (pos == *pphead){SLTNode* node = (*pphead);*pphead = newnode;(*pphead)->next = node;return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead);assert(pphead);assert(pos);if (pos == *pphead){SLTNode* ret = *pphead;*pphead = (*pphead)->next;free(ret);ret = NULL;return;}SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* node = pos->next;prev->next = node;free(pos);pos = NULL;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;SLTNode* prve = pos->next;pos->next = newnode;newnode->next = prve;
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);SLTNode* prve = pos->next;SLTNode* node = prve;while (node){node = prve->next;free(prve);prve = node;}pos->next = NULL;
}//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(*pphead);assert(pphead);SLTNode* prve = (*pphead);while(prve){prve = prve->next;free(*pphead);*pphead = NULL;*pphead = prve;}
}