目录
一、链表
1、链表的概念及结构
2、分类
二、实现单向链表
1、声明链表结构体
2、输出
3、头插&尾插
4、头删尾删
5、查找
6、指定位置插入
7、删除指定节点
8、删除指定节点的后一个节点
9、单链表的销毁
完整版
LList.h
LList.c
text.c
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
这张图生动形象地呈现了链表的结构。
主要有两种类型的链表:单向链表和双向链表。在 单向链表中,每个节点包含一个数据元素和一个指向下一个节点的引用。而在 双向链表中,每个节点有两个引用,一个指向前一个节点,另一个指向后一个节点。
本次讲解基础的单向链表。
我们创建三个文件:
- 头文件LList.h用于调用库函数、声明结构体和函数。
- 源文件LList.c存储函数。
- 源文件text.c进行测试。
每个源文件都必须包含LList.h。
#include
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
SLTDataType。
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
接下来讲解为新节点开辟空间的函数 BuyLTNode
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
perror("malloc fall");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyLTNode(x);
if (*pphead == NULL){
*pphead = newnode;
}
else {
SLTNode* tail = *pphead;
while (tail->next != NULL)
tail = tail->next;
tail->next = newnode;
}
}
*pphead
指向新节点 newnode
,使其成为新的链表的头节点。void SLPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
void SLPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
}
else {
SLTNode* tail = *pphead;
while (tail->next->next) {
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur) {
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
x
的节点。cur,否则返回值为空。
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (*pphead == pos){
SLPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos){
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
prev
,然后使用循环找到要插入位置 pos
前面的节点。创建一个新的节点 newnode
并将数据值 x
存储在其中。
修改 prev
节点的 next
指针,使其指向新节点 newnode
,从而将新节点插入到 pos
前面。
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
newnode
并将数据值 x
存储在其中。void SLErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
if (pos = *pphead){
SLPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev -> next;
}
prev->next = pos->next;
free(pos);
}
}
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
pos
后面节点的指针 next
,以便稍后释放该节点的内存。修改 pos
节点的 next
指针,将其指向 next
的下一个节点,从而绕过要删除的节点,使链表不再包含它。
最后,使用 free
函数释放 next
指向的节点的内存,完成删除操作。
void SListDestroy(SLTNode* pphead)
{
SLTNode* cur = pphead;
SLTNode* tmp = NULL;
while (cur != NULL) {
tmp = cur;
cur = cur->next;
free(tmp);
}
}
cur
和 tmp
,用于遍历链表并释放内存。开始时,cur
被初始化为链表的头节点指针 pphead
。这是一个循环,它会一直执行,直到 cur
变为 NULL
,也就是遍历到链表的末尾。
在循环中,首先将 cur
赋值给 tmp
,以便稍后释放 cur
指向的节点的内存。
然后,将 cur
移动到下一个节点,即 cur = cur->next;
最后,使用 free
函数释放 tmp
指向的节点的内存,即释放链表中的一个节点,接着进行循环依次释放节点直到链表最后。
#include
#include
#include
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
//头插尾插
void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);
//头删尾删
void SLPopFront(SLTNode** pphead);
void SLPopBack(SLTNode** pphead);
// 单链表查找
SLTNode * STFind(SLTNode * phead, SLTDataType x);
// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLInsertAfter(SLTNode* pos, SLTDataType x);
// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos);
// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);
// 单链表的销毁
void SListDestroy(SLTNode* plist);
#define _CRT_SECURE_NO_WARNINGS 1
#include "LList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLTNode* BuyLTNode(SLTDataType x)//为新元素开辟空间
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
perror("malloc fall");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SLPushFront(SLTNode** pphead, SLTDataType x)//头插
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
//assert(*pphead); // 不能断言,链表为空,也需要能插入
SLTNode* newnode = BuyLTNode(x);//newnode是局部变量
newnode->next = *pphead;//头插后首节点next指向原有的首节点
*pphead = newnode;//将链表的头指针 *pphead 指向新插入的节点
}
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
SLTNode* newnode = BuyLTNode(x);
//两种情况
//空链表 非空链表
if (*pphead == NULL)//链表为空改变结构体指针
*pphead = newnode;
else {//不为空,则改变结构体的节点
SLTNode* tail = *pphead;
while (tail->next != NULL)
tail = tail->next;
tail->next = newnode;
}
}
void SLPopFront(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
SLTNode* del = *pphead;//指针del用于释放节点空间
*pphead = (*pphead)->next;
free(del);
}
void SLPopBack(SLTNode** pphead)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
//只有一个节点
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;//修改头节点为空
}
else {
//第一种增加前项变量
//SLTNode* prev = NULL;
//SLTNode* tail = *pphead;
//while (tail->next) {
// prev = tail;
// tail = tail->next;
//}
//free(tail);
//prev->next = NULL;
//第二种不新增变量
//改变结构体的节点
SLTNode* tail = *pphead;
while (tail->next->next) {
tail = tail->next;
}
free(tail->next);//将指向的最后一个节点释放
tail->next = NULL;
}
}
SLTNode* STFind(SLTNode* phead, SLTDataType x)//找到返回链表地址
{
SLTNode* cur = phead;
while (cur) {
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (*pphead == pos){//在头节点前插入等于头插
SLPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;//用于找到pos前的位置
while (prev->next != pos){
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;//pos前一个位置next指向新开辟节点
newnode->next = pos;//新节点next指向pos
}
}
// 在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
//下面两行不能调换顺序,否则无法链接新节点后项节点
newnode->next = pos->next;
pos->next = newnode;
}
void SLErase(SLTNode** pphead, SLTNode* pos)// 删除pos位置的值
{
assert(pphead);
assert(*pphead);//链表为空则不能删除
if (pos = *pphead){
SLPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos) {//找到pos前一个节点
prev = prev -> next;
}
prev->next = pos->next;//将pos前一个节点的next指向pos后一个节点
free(pos);//释放pos空间
}
}
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);//后项为空则不能删除
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
void SListDestroy(SLTNode* pphead)
{
SLTNode* cur = pphead;
SLTNode* tmp = NULL;
while (cur != NULL) {
tmp = cur;
cur = cur->next;
free(tmp);
}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "LList.h"
void test1()
{
SLTNode* plist = NULL;
SLPushFront(&plist, 5);
SLPushFront(&plist, 4);
SLPushFront(&plist, 3);
SLPushBack(&plist, 6);
//SLPopFront(&plist);
SLTNode* pos = STFind(plist, 3);
SLInsert(&plist, pos, 99);
//pos = STFind(plist, 2);
//if (pos)
//{
// SLInsertAfter(pos, 20);
//}
//SLPopBack(&plist);
//SLPopBack(&plist);
//SLPopBack(&plist);
//SLPopBack(&plist);
//SLPopBack(&plist);
SLTPrint(plist);
}
int main()
{
test1();
return 0;
}