文章目录
引入
YY:顺序表的插入和删除都好麻烦哦,有没有啥简单的方法来实现呀?
当然有啦,单链表这不就来了嘛
链表的概念
单链表是链表的一个分支,我们先来看一下链表的概念
链表:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
解释:如果把顺序表比作是高楼大厦的话,那么链表就好比是小火车,小火车的每节车厢之间本身是没有关系的,而是通过一个个的挂钩,把每节车厢链接起来
单链表的声明
链表是由结点构成的,结点包括数据域和指针域,数据域用来存储数据,指针域用来链接逻辑结构相邻的结点
typedef int SLTDataType;
typedef struct SingleLinkListNode
{
SLTDataType data;
struct SingleLinkListNode* next;
}SLTNode;
初始化单链表
void createSList()
{
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
}
打印单链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
这里特意的创建了一个新的变量pcur
,目的是保留传入指针指向的位置,是良好的编程习惯
增加结点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
动态申请内存,并且判断是否成功开辟空间,返回值是开辟结点的地址
单链表尾插
void SLTPushBake(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
}
}
- 因为这里要改变链表的内容,所以要进行传址调用,又由于头结点本身就是一个指针,所以这里用到二级指针来接受头结点指针的地址,并通过解引用来访问该地址
- 先调用
SLTBuyNode
开辟空间,特殊情况是当头结点为空,此时要单独处理,直接把新申请的结点赋值给头结点 - 若头结点不为空则要找到尾结点,并且改变尾结点的指针域,使其指向新申请的
newnode
- 别忘记了 使用结构体指针的目的是能够动态的开辟内存空间,减少空间浪费,在动态顺序表中就已经体现
YY:**pphead
和*pphead
都是啥啊
创建单链表的第一步是创建一个头指针,就像这样
SLTNode* phead = NULL;
他是一个结构体类型的指针,指向的结构体就是咱们定义的结点,由于我们要对链表的内容来进行修改,这里就不能只是简单的传值调用,因为传值调用时,形参是实参的一份临时拷贝,对形参的操作,不会影响实参,而是要通过传址调用,来找到形参的地址,直接对形参进行操作
在参数中的**pphead
是用来接收头指针的地址,而在代码块中*pphead
是对pphead
解引用找到了pphead
所指向的空间
单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
记着要移动头结点的位置
单链表尾删
void SLTPopBake(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;//ptail就比prev多走一步
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
- 断言,防止传入空指针以及链表为空的情况
- 当链表中只有一个结点时要特殊处理
->
操作符的优先级比*
操作符的优先级高,因此要加括号- 尾删时影响的是最后一个结点,和最后一个结点之前的结点,因此要专门创建
prev
变量,将其指针域置为空
单链表头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
- 记录头结点的下一个结点
- 删除头结点
- 移动头结点
单链表结点查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur->next)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
找到则返回结点地址,未找到则返回NULL
在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
- 若插入的位置是头指针,则相当于头插,要特殊处理
- 重点关注插入过程中,结点之间的重新链接
在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
参数部分不需要传入头指针的地址,因为不需要向前查找
指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
- 如果删除的位置为头指针指向的位置,则相当于头删,要特殊处理
- 找到
pos
之前的结点,使其的next
链接pos
的下一个结点 - 释放
pos
,并置空
指定位置之后删除
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
指定位置pos
链接os->next->next
销毁链表
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
源码
SList.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表的结点的声明
typedef int SLTDataType;
typedef struct SingleLinkListNode
{
SLTDataType data;
struct SingleLinkListNode* next;
}SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
//增加新节点
SLTNode* SLTBuyNode(SLTDataType x);
//尾插
void SLTPushBake(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBake(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//指定位置之后删除
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);
SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
void SLTPushBake(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = newnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopBake(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur->next)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//void createSList()
//{
// SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
// node1->data = 1;
// SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
// node2->data = 2;
// SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
// node3->data = 3;
// SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
// node4->data = 4;
//
// node1->next = node2;
// node2->next = node3;
// node3->next = node4;
// node4->next = NULL;
// SLTPrint(node1);
//}
void SListTest01()
{
SLTNode* phead = NULL;
//SLTPushBake(&phead, 1);
//SLTPushBake(&phead, 2);
//SLTPushBake(&phead, 3);
//SLTPushBake(&phead, 4);
//SLTPrint(phead);
SLTPushFront(&phead, 1);
SLTPushFront(&phead, 2);
SLTPushFront(&phead, 3);
SLTPushFront(&phead, 4);
SLTPrint(phead);
//SLTPopBake(&phead);
//SLTPrint(phead);
//SLTPopBake(&phead);
//SLTPrint(phead);
//SLTPopBake(&phead);
//SLTPrint(phead);
//SLTPopBake(&phead);
//SLTPrint(phead);
SLTPopFront(&phead);
SLTPrint(phead);
SLTNode* find = SLTFind(phead, 3);
if (find == NULL)
{
printf("未找到!\n");
}
else
{
printf("找到啦!\n");
}
SLTInsert(&phead, find, 4);
SLTPrint(phead);
SLTInsertAfter(find, 9);
SLTPrint(phead);
//SLTErase(&phead, find);
//SLTPrint(phead);
SLTEraseAfter(find);
SLTPrint(phead);
SListDestroy(&phead);
SLTPrint(phead);
}
int main()
{
//createSList();
SListTest01();
return 0;
}
总结
以上是数据结构中单链表的相关知识,我们要像捡贝壳一样捡到自己的小篮子里哦,有不明白的地方欢迎留言,作者水平有限,文章不妥部分还请各位读者指正。