上一章我们对顺序表的一些简单功能做了代码实现,但对于顺序存储的一些缺点,这一章我们实现一种简单的链式结构的数据结构单链表。

文章目录

  • 前言
  • 单链表打印
  • 创建新的结点
  • 单链表尾插
  • 单链表头插
  • 单链表尾删
  • 单链表头删
  • 单链表查找指定数据
  • 单链表删除指定位置数据
  • 单链表删除指定位置后面数据
  • 单链表指定位置前面插入数据
  • 单链表指定位置后面插入数据
  • 单链表销毁
  • 头文件代码
  • 源文件代码

前言

顺序表的缺点:

  1. 顺序表空间不足要扩容,如果原地扩容则只需要在顺序表后面开辟空间,而如果后面的空间不够则需要在内存中另找一块更大的地方将已有的数据全部拷贝过去
  2. 为了避免每次扩容太少导致频繁的扩容,我们一般都是把顺序表扩容到原来的二倍,而如果我们只需要增加几个数据则容易造成内存的浪费
  3. 顺序表是连续存储的,所以我们每次插入数据都要把插入位置后面所有的数据都往后挪动,最坏情况的时间复杂度是O(N)

(顺序表也有随机访问的优点)

链式结构就可以解决顺序表这种缺点,我们今天介绍的一种简单的链式结构是单链表(不带哨兵位的头节点),单链表在存储中也有一些缺点,所以在后面我们还会介绍双向链表等。

(本篇内容参考B站比特科技学习)

typedef struct sListNode {dataType num;struct sListNode* next;
}sListNode;

第一张图是我们现象出来的逻辑结构,第二张图是实际的物理结构,一个链表存数据的同时指向下一个数据的位置,所以我们用结构体来实现,在结构体里一部分存数据另一部分有一个结构体指针用来存下一个数据的地址

单链表打印

void sListPrint(sListNode* phead) {sListNode* cur = phead;while (cur != NULL) {printf("%d->", cur->num);cur = cur->next;}printf("NULL\n");
}

因为用到指针,所以我们传进来都要断言一下是不是为空下面就不再赘述了,这里没有断言是因为链表可能为空链表。定义一个临时变量cur存链表头节点的地址,如果为空我们判断一下直接打印NULL即可。如果不为空,就进入循环打印,再把cur指向下一个位置,如果为空跳出循环,不为空重复操作即可。

创建新的结点

sListNode* buySListNode(dataType x) {sListNode* newnode = (sListNode*)malloc(sizeof(sListNode));if (newnode == NULL){printf("malloc fail\n");exit(-1);}else{newnode->num = x;newnode->next = NULL;}return newnode;
}

由于每次插入数据都需要开辟一个新的结点,所以我们直接封装一个函数。进来后我们直接malloc一个结点用newnode指针接收,养成好习惯我们这里判断一下,开辟成功后我们把数据赋值进来,并让指针指向为空,返回该结点的地址。

单链表尾插

void sListPushBack(sListNode** pphead, dataType x) {assert(pphead);sListNode* newnode = buySListNode(x);if (*pphead == NULL) {*pphead = newnode;}else {            sListNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

这里面需要传进来二级指针是为了解决链表为空链表的情况。

一方面传进来空指针的话我们对其解引用就造成了非法操作,另一方面向空链表插入新的结点我们肯定要改变链表头结点的地址,如果单纯传进来这个地址那么里面改变了出去这个临时变量也会被销毁,而能做到改变地址的方法就是把这个地址的地址传进来,所以就用到了二级指针

明白了这一点后剩下的事情就好办了,先开辟一个新结点,如果为空链表,那么我们直接把对二级指针解引用把链表地址改为新结点的地址newnode。如果不为空,那就定义一个临时变量tail存起始位置的地址,判断这个结点中存储的下一个结点是否为空,为空就说明这个是最后一个结点,如果不是那我们就让它变为下一个结点,这样循环直到找到最后一个结点的位置,最后让最后一个结点指向newnode即可。

(其实不止二级指针这一种方法,在这里我们只介绍二级指针这种方法)

单链表头插

void sListPushFront(sListNode** pphead, dataType x) {assert(pphead);sListNode* newnode = buySListNode(x);newnode->next = *pphead;*pphead = newnode;
}

头插相较于尾插简单很多,因为需要改变头节点的地址,所以自然也要传进来二级指针,首先开辟一个新结点,这时只需要把这个新结点指向原来的头节点,再把头节点的位置改为这个newnode的位置即可。要注意的是不能先改头节点的位置,那样会使新头节点找不到原来头节点的位置。

单链表尾删

void sListPopBack(sListNode** pphead) {assert(*pphead&&pphead);if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {sListNode* tail = *pphead;while (tail->next->next) {tail = tail->next;}free(tail->next);tail->next = NULL;}
}

尾删不仅需要判断传进来的pphead是否不为空,主要的作用是判断传进来的不能是个空链表,所以解引用看一下是不是空链表。这里用if,else分开的原因是当链表中只有一个结点跟有多个结点的情况是不一样的,因为链表有个缺点是它不能实现随机访问,而且一个结点不能访问到它上一个结点的位置。所以我们需要从头开始访问直到找到最后一个,为了解决无法访问上一个结点的问题除了这种方法这里我们也可以定义一个prev临时变量存头节点位置,跟着tail往后走,找到了让tail回到prev就行。但是这两种面对只有一个结点的情况都没办法解决,因为如果只有一个结点的话prev是什么呢,它压根就不存在上一个结点,对一个空指针解引用自然会出错,上面这种方法同样也面对这个问题。所以我们把一个结点的情况单拿出来,如果它指向空,直接free掉再把指向它的指针置为空就行。多个结点就按上面那种思路,找到最后一个结点的位置把它free掉再置为空即可。

单链表头删

void sListPopFront(sListNode** pphead) {assert(*pphead && pphead);sListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

头删就不需要考虑那么多了,我们只需要进来判断不是空,然后创建一个指针记住下一个结点的位置,把这个头结点free掉,再把临时指针复制过来就行,即使只有一个结点我们是把NULL 赋值过来的,所以不需要特殊处理。这里需要注意的是先需要把下一个结点的位置存起来,避免先free掉这个头节点从而找不到下一个结点的位置。

单链表查找指定数据

sListNode* sListFind(sListNode* phead, dataType x) {assert(phead);sListNode* cur = phead;while (cur) {if (cur->num == x) return cur;else cur = cur->next;}return NULL;
}

查找数据我们返回的是该数据的地址,因为只是查找所以我们不用传二级指针。根据链表的特性不支持随机访问,所以我们还是首先定义一个临时变量存头节点位置从头访问,如果不为空就进入循环判断是不是找到该数据,不是就指向下一个结点,如果一开始就是空链表或者到最后一个结点也没找到对应的数据,我们直接返回一个空指针即可。

单链表删除指定位置数据

void sListDeleteCurrent(sListNode** pphead, sListNode** ppos) {   //传址删除assert(pphead && ppos);if (*pphead == *ppos) {*pphead = (*ppos)->next;free(*ppos);}else {sListNode* prev = *pphead;while (prev->next != *ppos) {prev = prev->next;}prev->next = (*ppos)->next;free(*ppos);*ppos = NULL;}
}

这里头节点的位置可能会改变所以肯定还是要传二级指针,而后面的结点pos是传址删除还是传值删除那个课程学习和我看到的几个博主都是传值,但我觉得如果只是传进来值的话free的话跟出来这个函数临时变量自己销毁好像区别不大,而外面的那个pos依然记得这个地址,而这块空间已经free掉了,如果手误再使用这个指针就造成了越界访问。

删除数据同样要考虑如果一种情况就是删除的结点前面也有结点,那么我们需要把前一个结点指向后一个结点,所以这个也需要分两种情况。如果前面没有结点我们只需要直接删即可,先让头结点指向下一个结点的位置,再free掉就行了。如果前面有结点,我们就需要创建一个临时的prev指向头结点位置往后走,没有找到就继续走,找到后跳出循环,这个时候把prev指向ppos所在结点的下一个结点的位置,把该结点删除就行。

void sListDeleteCurrent0(sListNode** pphead, sListNode* pos) {   //传值删除assert(pphead && pos);if (*pphead == pos) {*pphead = pos->next;free(pos);}else {sListNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = (pos)->next;free(pos);pos = NULL;}
}

单链表删除指定位置后面数据

void sListDeleteAfter(sListNode* pos) {assert(pos && pos->next);pos->next = pos->next->next;free(pos->next);
}

我们会发现删除指定位置的数据还要考虑特殊情况,需要得到前一个位置的地址,所以有种简单的方法不考虑只有一种结点的情况,我们只删除指定位置后面的数据,进来assert判断不能只有一个结点,这个时候只需要把pos下一个结点存的next赋值过来再把pos指向的那个结点删除就行了。

单链表指定位置前面插入数据

void sListInsertBefore(sListNode** pphead, sListNode* pos, dataType x) {assert(pphead && pos);sListNode* newnode = buySListNode(x);if (*pphead == pos) {newnode->next = *pphead;*pphead = newnode;}else {sListNode* prev = *pphead;while (prev->num != x) {prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}

在指定位置插入数据同样也有特殊情况,因为链表中结点是不知道前一个结点的位置的。如果正好是在头位置插入的话我们只需要让这个新结点指向本来的头结点的位置,再把这个新结点的位置赋值给头位置即可。如果不是第一个,那么一样的道理我们也需要创建一个临时变量prev跟着走,直到遇到指定位置,让prev指向这个新结点,再让这个新结点指向pos即可。

单链表指定位置后面插入数据

void sListInsertAfter(sListNode* pos, dataType x) {assert(pos);sListNode* newnode = buySListNode(x);newnode->next = pos->next;pos->next = newnode;
}

跟指定位置后面删除数据同样道理,我们可以写一个指定位置后面插入的函数。判断不为空链表,直接让它指向本来pos指向的结点再让pos指向它即可。

单链表销毁

void sListDestroy(sListNode** pphead) {assert(pphead);sListNode* cur = *pphead;while (cur) {sListNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

单链表的销毁需要把头结点的位置也置为空,所以同样需要二级指针。因为没法随机访问,我们只能一个结点一个结点删。创建cur记住头结点地址,不为空就进行后续操作,把cur这时指向的结点free掉,再指向下一个结点,直到cur为空了说明后面的结点全部free完了,这时把头结点的指针free了即可。

头文件代码

#pragma once#define _CRT_NO_SECRUE_WARNINGS#include <stdio.h>
#include <assert.h>
#include <stdlib.h>typedef int dataType;typedef struct sListNode {dataType num;struct sListNode* next;
}sListNode;//单链表打印
void sListPrint(sListNode* phead);//单链表销毁
void sListDestroy(sListNode** phead);//创建新的结点
sListNode* buySListNode(dataType x);//单链表尾插
void sListPushBack(sListNode** pphead, dataType x);//单链表头插
void sListPushFront(sListNode** pphead, dataType x);//单链表尾删
void sListPopBack(sListNode** pphead);//单链表头删
void sListPopFront(sListNode** pphead);//单链表查找指定数据
sListNode* sListFind(sListNode* pphead, dataType x);//单链表删除指定数据
void sListDeleteCurrent(sListNode** pphead, sListNode** pos);//传址
void sListDeleteCurrent0(sListNode** pphead, sListNode* pos);//传值//单链表删除指定位置后面数据
void sListDeleteAfter(sListNode* pos);//单链表指定位置前面插入数据
void sListInsertBefore(sListNode** pphead, sListNode* pos, dataType x);//单链表指定位置后面插入数据
void sListInsertAfter(sListNode* pos, dataType x);

源文件代码

#include "singleLinkedList.h"void sListDestroy(sListNode** pphead) {assert(pphead);sListNode* cur = *pphead;while (cur) {sListNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}void sListPrint(sListNode* phead) {sListNode* cur = phead;while (cur != NULL) {printf("%d->", cur->num);cur = cur->next;}printf("NULL\n");
}sListNode* buySListNode(dataType x) {sListNode* newnode = (sListNode*)malloc(sizeof(sListNode));if (newnode == NULL){printf("malloc fail\n");exit(-1);}else{newnode->num = x;newnode->next = NULL;}return newnode;
}void sListPushBack(sListNode** pphead, dataType x) {assert(pphead);sListNode* newnode = buySListNode(x);if (*pphead == NULL) {*pphead = newnode;}else {            sListNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void sListPushFront(sListNode** pphead, dataType x) {assert(pphead);sListNode* newnode = buySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void sListPopBack(sListNode** pphead) {assert(*pphead&&pphead);if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {sListNode* tail = *pphead;while (tail->next->next) {tail = tail->next;}free(tail->next);tail->next = NULL;}
}void sListPopFront(sListNode** pphead) {assert(*pphead && pphead);sListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}sListNode* sListFind(sListNode* phead, dataType x) {assert(phead);sListNode* cur = phead;while (cur) {if (cur->num == x) return cur;else cur = cur->next;}return NULL;
}void sListDeleteCurrent(sListNode** pphead, sListNode** ppos) {   //传址删除assert(pphead && ppos);if (*pphead == *ppos) {*pphead = (*ppos)->next;free(*ppos);}else {sListNode* prev = *pphead;while (prev->next != *ppos) {prev = prev->next;}prev->next = (*ppos)->next;free(*ppos);*ppos = NULL;}
}void sListDeleteCurrent0(sListNode** pphead, sListNode* pos) {   //传值删除assert(pphead && pos);if (*pphead == pos) {*pphead = pos->next;free(pos);}else {sListNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = (pos)->next;free(pos);pos = NULL;}
}void sListDeleteAfter(sListNode* pos) {assert(pos && pos->next);pos->next = pos->next->next;free(pos->next);
}void sListInsertBefore(sListNode** pphead, sListNode* pos, dataType x) {assert(pphead && pos);sListNode* newnode = buySListNode(x);if (*pphead == pos) {newnode->next = *pphead;*pphead = newnode;}else {sListNode* prev = *pphead;while (prev->num != x) {prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}void sListInsertAfter(sListNode* pos, dataType x) {assert(pos);sListNode* newnode = buySListNode(x);newnode->next = pos->next;pos->next = newnode;
}

单链表简单功能的代码实现相关推荐

  1. java单链表查询功能,Java 实现简答的单链表的功能

    作者:林子木  博客网址:http://blog.csdn.net/wolinxuebin 參考网址:http://blog.csdn.net/sunsaigang/article/details/5 ...

  2. python怎么反转单链表_单链表反转python实现代码示例

    单链表的反转可以使用循环,也可以使用递归的方式 1.循环反转单链表 循环的方法中,使用pre指向前一个结点,cur指向当前结点,每次把cur->next指向pre即可. 代码: class Li ...

  3. C语言一趟冒泡交换最小值,C语言单链表冒泡排序为啥以下代码实现不了?

    struct node *sort(struct node *head)/*排序*/ { struct node *p,*q; struct node *temp; for(p=head;p!=NUL ...

  4. 在单链表写入一组数据代码_链表常见操作和15道常见面试题

    什么是单链表 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer),简单来说链表并不像数组那样 ...

  5. [Java数据结构][3]单链表以及双向链表Java代码实现

    单链表Java代码实现,以水浒英雄链表为例 文章目录 单链表Java代码实现,以水浒英雄链表为例 定义一个英雄链表 定义一个SingleLinkedList 用于管理结点 初始化头结点以及添加结点到单 ...

  6. 在单链表写入一组数据代码_第5章 第1节 链表 - osc_x8s7voop的个人空间 - OSCHINA - 中文开源技术交流社区...

    ● 请你说出几种基本的数据结构, 参考回答: 常见的基本的数据结构有链表.栈.队列.树(只列出面试常考的基本数据结构) 1.链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链 ...

  7. 数据结构-单链表基本操作-C语言代码

    单链表基本操作 1.头插法建立单链表 2.尾插法建立单链表 3.查找结点 3.修改结点 4.插入结点 5.删除结点 本篇只有c语言代码,具体思路讲解请看这篇博客:数据结构-线性结构-单链表 1.头插法 ...

  8. c语言用链表编写简单程序,C语言单链表简单实现(简单程序复杂化)

    ps: goto还是很好玩的. #include #include typedef struct _node{ int value; struct _node *next; } node; typed ...

  9. java 编码 正弦计算器_Java 简单功能计算器代码

    带界面的计算机: JAVA编写. package com.ALiangJie.Calculator; import javax.swing.*; import java.awt.*; import j ...

最新文章

  1. 为什么获取crm服务器信息失败,为 Outlook 配置 Microsoft Dynamics CRM 客户端时出现 与 Microsoft Dynamics CRM 服务器通信时出现问题 错误...
  2. 计算机与生命科学交叉应用,第二届 “数学、计算机与生命科学交叉研究”青年学者论坛...
  3. 人脸识别的过程和算法
  4. Generic 打印ID对应的object type的工具
  5. 5.一文搞懂MySQL的数据类型
  6. IDEA这样配置注释模板,让你高出一个逼格!!
  7. tomcat 查看当前请求数_原生线程池这么强大,Tomcat 为何还需扩展线程池?
  8. Picture Control控件图象保存为bmp,jpg,emf,tif,gif
  9. 《数据挖掘概念与技术》第三版 范明 孟小峰译 课后习题答案(一)
  10. 软件测试的测试方法及测试流程
  11. windows是第几代计算机,第几代cpu不支持win7?全面分析第几代cpu不支持win7
  12. wd移动硬盘不能识别_wd移动硬盘xp无法识别 移动硬盘无法识别的解决方法
  13. c语言毕业论文,关于c语言的毕业论文题目[word文档]
  14. Vue3中点击箭头切换图片
  15. 大唐凌烟阁二十四功臣
  16. 填坑记录——扫雷游戏的重置
  17. 深度学习经典试题29道
  18. 男程序员写代码的样子 VS 女程序员写代码的样子
  19. 奋斗群群赛2总结与心得
  20. Python pywinauto 自动操作Windows GUI

热门文章

  1. linux 蓝牙命令
  2. CANape数据保存excel格式的方法
  3. 2022年MathorCup数学建模B题无人仓的搬运机器人调度问题解题全过程文档加程序
  4. vulnhub靶场之RAGNAR LOTHBROK: 1
  5. 博弈论(1)——巴什博弈
  6. 信息学奥赛C++编程:多边形内角和
  7. 提高计算机网络可靠性开题报告,计算机网络类论文范文素材,与提高计算机网络可靠性的方法(二)相关研究生毕业论文开题报告范文...
  8. hashcat 破解RAR密码操作使用记录
  9. 【蓝桥杯真题】 Python题解
  10. unity3D实现镜头拉近拉远及视角旋转