文件分类:
测试模块:Test.c文件
功能声明和实现模块:SList.c和SList.h文件
单链表的定义
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向;
2. 带头或者不带头;
3. 循环或者不循环。
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
示意图:
链表节点结构体声明:
typedef int SLTDateType;
//使用typedef便于后面改动单链表中储存的数据类型
typedef struct SLTNode
{
SLTDateType data;
struct SLTNode* next;
}SLTNode;
单链表各种功能的实现
动态申请一个节点
由于每次插入数据都需要动态增加一个节点,所以把申请节点写成一个函数,便于每次插入时调用该函数。
SLTNode* BuySLTNode(SLTDateType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->next = NULL;
return node;
}
单链表头插
由于单链表的头插需要改变头指针的指向,因此需要传二级指针作为参数,才能改变头指针的指向。
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
单链表尾插
单链表尾插需要分空链表和非空链表两种情况来讨论。当单链表尾插时,链表为空时,需要改变头指针的指向,因此也需要传二级指针作为参数,才能改变头指针的指向。当不为空时,直接改变尾部元素的next指针的指向即可。
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
单链表的打印
void SListPrint(SLTNode* phead)
{
assert(phead);
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
单链表尾删
单链表尾删前需要检查是否为空,使用assert断言,为空时直接报错。要分只有一个节点和两个以上节点两种情况。删除后把删除的节点使用free释放掉,然后改变前面指针的指向为NULL。
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tmp = *pphead;
while (tmp->next->next)
{
tmp = tmp->next;
}
free(tmp->next);
tmp->next = NULL;
}
}
单链表头删
单链表头删前也需要检查是否为空,使用assert断言,为空时直接报错。将头指针指向头节点的next,然后释放掉头节点。
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* head = (*pphead)->next;
free(*pphead);
*pphead = head;
}
单链表查找
查找单链表中储存的数据中是否有为x的节点,如果有,返回第一个值为x的节点的指针,如果没有,返回NULL。
SLTNode* SListFind(SLTNode* plist, SLTDateType x)
{
assert(plist);
SLTNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
在pos位置之后或之前插入x
首先断言pos位置是否存在,然后将pos节点的next指针指向新申请的节点。
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
为什么不在pos位置之前插入?
在pos之前插入需要遍历单链表一直到pos之前的位置。代码如下:
// 在pos的前面插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
assert(pphead);
assert(pos);
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos;
cur->next = newnode;
}
删除pos位置的值
首先需要检查是否为空,使用assert断言,为空时直接报错。要分pos为头节点和不为头节点两种情况。当pos为头节点,就是头删。当pos不为头节点时,遍历单链表找到pos位置之前的节点prev,将prev节点的next指针指向pos指向的下一个节点。删除后把删除的节点使用free释放掉。
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SListPopFront(pphead);
}
else
{
SLTNode* tmp = *pphead;
while (tmp->next != pos)
{
tmp = tmp->next;
}
tmp->next = pos->next;
free(pos);
pos = NULL;
}
}
删除pos位置之后的节点
首先断言pos位置之后是否存在节点,将pos的next指针指向pos->next->next。然后释放掉pos位置之后的节点。
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
销毁单链表
将单链表依次遍历并释放,最后将头指针指向NULL。
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
完整代码
SList.h文件完整代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SLTNode
{
SLTDateType data;
struct SLTNode* next;
}SLTNode;
// 动态申请一个节点
SLTNode* BuySLTNode(SLTDateType x);
// 单链表打印
void SListPrint(SLTNode* plist);
// 单链表尾插
void SListPushBack(SLTNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SLTNode** pplist);
// 单链表头删
void SListPopFront(SLTNode** pplist);
// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos);
// 在pos的前面插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos);
//单链表销毁
void SLTDestroy(SLTNode** pphead);
SList.c文件完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "slist.h"
// 动态申请一个节点
SLTNode* BuySLTNode(SLTDateType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->next = NULL;
return node;
}
// 单链表打印
void SListPrint(SLTNode* phead)
{
assert(phead);
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
// 单链表的头插
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
assert(pphead);
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
// 单链表的尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
while (cur->next->next)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
// 单链表头删
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* head = (*pphead)->next;
free(*pphead);
*pphead = head;
}
// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDateType x)
{
assert(plist);
SLTNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next->next;
free(pos->next);
pos->next = next;
}
// 在pos的前面插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
assert(pphead);
assert(pos);
SLTNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos;
cur->next = newnode;
}
// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SListPopFront(pphead);
}
else
{
SLTNode* tmp = *pphead;
while (tmp->next != pos)
{
tmp = tmp->next;
}
tmp->next = pos->next;
free(pos);
pos = NULL;
}
}
//销毁单链表
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
Test.c文件完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "slist.h"
void test1()
{
SLTNode* sl = NULL;
SListPushBack(&sl, 1);
SListPushBack(&sl, 2);
SListPushBack(&sl, 3);
SListPushBack(&sl, 4);
SListPushFront(&sl, 0);
SListPushFront(&sl, -1);
SListPushFront(&sl, -2);
SListPrint(sl);
//SListPopBack(&sl);
//SListPopBack(&sl);
//SListPopFront(&sl);
//SListPopFront(&sl);
//SListPrint(sl);
SLTNode* retnode = SListFind(sl, 0);
SListPrint(retnode);
SListInsertAfter(retnode, 9);
SListPrint(sl);
SListEraseAfter(retnode);
SListPrint(sl);
SLTInsert(&sl, retnode, 8);
SListPrint(sl);
SLTErase(&sl, retnode);
SListPrint(sl);
SLTDestroy(&sl);
}
int main()
{
test1();
return 0;
}