今天我们学习数据结构中的列表,当然这个链表是非常基础的列表,不带头节点的单项非循环列表。

我们首先创建好文件,仍然和之前一样,一个.h文件和两个.c文件(一个用来实现接口,一个用来测试)

我们首先来实现列表的定义,以及为了之后更换数据类型方便,我们使用#define定义数据类型

typedef int SLTDataType;
struct SListNode {SLTDataType data;struct SListNode* next;
};typedef struct SListNode SLTNode;

同时,我们为了之后写代码方便,给他重命名一下。

再来看看我们要实现的接口

void SListPrint(SLTNode* phead);//打印
void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
void SListPopBack(SLTNode** pphead);//尾删
void SListPopFront(SLTNode** pphead);//头删
SLTNode* SListFine(SLTNode* phead,SLTDataType x);//查找
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//任意节点位置插入
void SListErase(SLTNode** pphead, SLTNode* pos);//任意节点位置删除

我们首先来实现列表的尾插

void SListPushBack(SLTNode* phead, SLTDataType x) {SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;if (phead == NULL) {phead = newnode;}else {SLTNode* tail = phead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}

我们先给一个新节点分配一个空间,将数据传给这个节点的data,并且将他的next置为null,同时我们还得判断一下我们的列表是不是空链表,如果列表是空链表,那么就直接将这个新节点赋给我们的链表,如果不是空链表,就创建一下新指针,让它不断的向后,直到走到我们链表的最后一个元素,然后把此时的next指向我们的新节点。

我们来画一下列表的物理结构(平时书上的以及讲的都是逻辑结构,物理结构更容易我们理解,而很多人感觉数据结构难就有可能是因为是只知道逻辑结构,把自己绕晕了)

链表里边并我们想象的一个箭头,可以指向下一个节点,而是存放的地址,如上图所示(画工不好23333)。

接着我们来写遍历的函数,方便我们打印链表里的内容,观察链表

void SListPrint(SLTNode* phead) {SLTNode* cur = phead;while (cur != NULL) {printf("%d ", cur->data);cur = cur->next;}
}

代码结合物理结构来看,只需定义一个新的指针,让它指向phead,只要不为NULL,就不断的向后走,并且打印当前的data

接下来我们来测试一下我们的尾插

可以看到,什么也没打印出来,说明我们的代码是错误的,这是为什么呢?不要着急,我们来调试看看

我使用了f11进行调试,不断的只执行下一条语句,根据这两张图大家就会发现,我们的plist和phead好像没有关系呀,这是因为我们传过去的是phead是形参,而plist是实参,我们知道,形参是实参的拷贝,形参的改变不影响实参,所以我们的列表一直都是空的,所以我们应该传址,而非传值,我们的代码要改成这样的

void SListPushBack(SLTNode** pphead, SLTDataType x) {SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;if (*pphead == NULL) {*pphead = newnode;}else {SLTNode* tail = *pphead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}

我们这次传过来的是地址,*pphead是解引用(名字无所谓,如果大家不习惯可以继续写成phead,但是因为是二级指针,所以使用pphead更合适),同时为了更形象的打印列表,我们修改一下遍历的函数

void SListPrint(SLTNode* phead) {SLTNode* cur = phead;while (cur != NULL) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

我们每打印一个数据就打印一个->,并且在结尾打印NULL,大家在修改完代码后别忘了在.h文件中修改声明。

我们现在再来测试一下

这才是我们想要的结果,pphead是plist的地址,*pplist就是plist,大家可以对比一下我们学习函数时的函数传参就明白了。另外,在c++里这里可以使用引用解决,这里就不多介绍。我们再给大家调试看一看

&plist,就是pphead,所以*pphead就是plist,另外,监视窗口只能看当前函数的变量,所以大家在自己调试时可能会发现上边的变化和自己想象的不一样

接着我们来完成头插,在写头插时,大家会发现,我们最开始还是要申请一个节点空间,这和我们的尾插是一样的,并且之后也说不准要用到,对于这种重复的代码,我们将它提取出来封装成一个函数。

SLTNode* BuySListNode(SLTDataType x) {//申请新的节点,data为传入的xSLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;return newnode;
}

这样,我们完成的头插看起来也清爽了很多

void SListPushFront(SLTNode** pphead, SLTDataType x) {SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}

和尾插一样,我们要传入二级指针,到这里,大家可能就会有疑惑,为什么print函数传入的是一级指针呢?

我们发现:1.对于可能会改变链表的头指针,我们传入二级指针;

2.不会改变链表的头指针,我们传入一级指针。

接着我们来实现头删

void SListPopFront(SLTNode** pphead)
{if (*pphead == NULL) {return;}else {SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;}
}

我们要进行一次判断,如果传入的链表是空链表,那么就直接返回,否则我们进行节点删除,在删除节点时,大家会发现这句:(*pphead)->next,*pphead上括号是因为*和->是相同优先级的,会产生冲突,所以我们需要给*pphead加上括号。

然后是尾删

void SListPopBack(SLTNode** pphead) {if (*pphead == NULL) {return;}else if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL) {prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}

尾删我们要进行两次判断,如果是空链表,我们就直接返回,第二种是传入的链表只有一个节点,我们就直接释放它,这里为什么要分第二种情况是因为,如果链表只有一个节点,那么while语句的tail->next就会出现访问空的问题

接着我们来完成查找

SLTNode* SListFine(SLTNode* phead, SLTDataType x) {SLTNode* cur = phead;while (cur) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}

可能有人好奇为什么我们不用下标这些东西,而直接返回SLTNode*类型呢?因为有些比较难的题目,它会有限制不让我们创建多余的东西,比如链表只能有data和next,所以我们这里就不使用那些东西。

那么这个查找怎么用呢?这个查找可以结合我们的任意位置插入来使用

SLTNode* pos = SListFine(plist, 2);if (pos) {SListInsert(&plist, pos, 20);}

就像我们想要在2前面插入一个20,就可以这样来写

那么我们就要来完成任意位置的插入(任意节点之前)

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead) {SListPushFront(pphead, x);}else {SLTNode* newnode = BuySListNode(x);SLTNode* perv = *pphead;while (perv->next!=pos) {perv = perv->next;}perv->next = newnode;newnode->next = pos;}
}

我们在完成插入时,可以直接使用头插,不用我们再写一遍,因为他们的逻辑是一样的。

当然,我们还要进行一次判断,如果pos这个节点刚好是我们的头节点,那就直接头插,否则的话,在else里边的perv是不会判断最开始时perv是否等于pos的,会不停的往后走,少判断一个头节点,从而导致插入失败。

接着是任意节点的删除

void SListErase(SLTNode** pphead, SLTNode* pos) {if (*pphead == pos) {SListPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);}
}

和任意节点插入,要进行一次判断以及直接使用头删即可,也是可以搭配查找使用

可以看到,最开始链表里是89123,接着我在3前面插入了30,然后在9前面插入了10,然后删除了30。

最后,附上全部代码

#pragma once
//SList.h
#include <stdio.h>
typedef int SLTDataType;struct SListNode {SLTDataType data;struct SListNode* next;
};typedef struct SListNode SLTNode;void SListPrint(SLTNode* phead);//打印
void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
void SListPopBack(SLTNode** pphead);//尾删
void SListPopFront(SLTNode** pphead);//头删
SLTNode* SListFine(SLTNode* phead,SLTDataType x);//查找void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//任意节点位置插入
void SListErase(SLTNode** pphead, SLTNode* pos);//任意节点位置删除
//SList.c
#include"SList.h"void SListPrint(SLTNode* phead) {SLTNode* cur = phead;while (cur != NULL) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x) {//申请新的节点,data为传入的xSLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;return newnode;
}void SListPushBack(SLTNode** pphead, SLTDataType x) {SLTNode* newnode = BuySListNode(x);if (*pphead == NULL) {*pphead = newnode;}else {SLTNode* tail = *pphead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}void SListPushFront(SLTNode** pphead, SLTDataType x) {SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void SListPopFront(SLTNode** pphead)
{if (*pphead == NULL) {return;}else {SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;}
}void SListPopBack(SLTNode** pphead) {if (*pphead == NULL) {return;}else if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL) {prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}SLTNode* SListFine(SLTNode* phead, SLTDataType x) {SLTNode* cur = phead;while (cur) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead) {SListPushFront(pphead, x);}else {SLTNode* newnode = BuySListNode(x);SLTNode* perv = *pphead;while (perv->next!=pos) {perv = perv->next;}perv->next = newnode;newnode->next = pos;}
}void SListErase(SLTNode** pphead, SLTNode* pos) {if (*pphead == pos) {SListPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);}
}
//test.c
#include"SList.h"void test1() {SLTNode* plist = NULL;SListPushBack(&plist, 1);SListPushBack(&plist, 2);SListPushBack(&plist, 3);SListPushFront(&plist, 9);SListPushFront(&plist, 8);SListPrint(plist);/*SListPopFront(&plist);SListPrint(plist);*///SListPopBack(&plist);//SListPrint(plist);SLTNode* pos = SListFine(plist, 3);if (pos) {SListInsert(&plist, pos, 30);}SListPrint(plist);SLTNode* pos1 = SListFine(plist, 9);if (pos1) {SListInsert(&plist, pos1, 10);}SListPrint(plist);pos1 = SListFine(plist, 30);if (pos1) {SListErase(&plist, pos1);}SListPrint(plist);
}int main() {test1();return 0;
}

以上就是我们的单项不带头节点的非循环链表,希望大家可以有所收获。

如有错误,还请指正。

链表与其多种接口实现1相关推荐

  1. sdio接口_多种接口的谷歌Coral模块,总有一款适合您~

    大家好,我是人见人爱的小月月.今天我们继续聊聊人工智能开发板. 一提到Coral这个名字,大家就会想到谷歌的各种黑科技.去年Google已经发布过一块秒天秒地的人工智能开发板,名字叫Coral Dev ...

  2. FC协议功能子模块,实现FC-1553协议,ASM协议,AV协议的应用,多种接口可定制

    功能子模块的介绍  采用 FMC 接口与载板连接,支持 PCIE/Aurora 数据总线, 预留高速并行数据接口:  可支持国产化核心器件:  线路速率 1.0625Gb/s.2.125Gb/s ...

  3. 双指针找链表中点多种写法

    方法一: ListNode *FindMid(ListNode* head){ListNode *p=head,*q=head;while(q->next!=NULL&&q-&g ...

  4. 通用单向链表设计(三)——接口的测试

    2019独角兽企业重金招聘Python工程师标准>>> 接口的测试: /***************test.c**********************/ #include & ...

  5. 电脑接口自动测试软件,通过多种接口总线与计算机实现自动检定/校准测试系统的设计...

    2.1 多总线仪器硬件兼容的实现方案 如图2中VISA接口子层是实现仪器统一编程接口的核心.VISA详细规范了虚拟仪器I/O接口软件的组成.内部结构与实现规则,而符合此规范的虚拟仪器I/O接口函数就是 ...

  6. 通用单向链表设计(一)——接口的设计

    2019独角兽企业重金招聘Python工程师标准>>> 1.common头文件 #ifndef TYPEDEF_H #define TYPEDEF_H#include <std ...

  7. 数据结构 —— 单链表(超详细图解 接口函数实现)

    系列文章目录 数据结构 -- 顺序表 数据结构 -- 单链表 数据结构 -- 双向链表 数据结构 -- 队列 数据结构 -- 栈 数据结构 -- 堆 数据结构 -- 二叉树 数据结构 -- 八大排序 ...

  8. Python如何写接口,以及请求多种外部接口的方法

    前言 本文是该专栏的第7篇,后面会持续分享python的各种干货知识,值得关注. 对于使用python来写get或post接口,在本专栏上一篇有详细介绍过. Python如何写get接口或者post接 ...

  9. java链表list_java集合之linkedList链表基础

    LinkedList链表: List接口的链接列表实现.允许存储所有元素(包含null).使用频繁增删元素. linkedList方法: void addFirst(E e) 指定元素插入列表的开头 ...

最新文章

  1. 谷歌提出纯 MLP 构成的视觉架构,无需卷积、注意力 !
  2. 这 8 篇文章告诉你:未来的软件研发是怎样的?
  3. 嗓子痛引发大抢救!33岁程序员的垂死经历,为所有人敲响警钟!
  4. 一叶知秋:基于“单目标域样本”的领域自适应方法
  5. android ndk 段错误,android crash之段错误原因及分析方法
  6. sql backup database备份d盘_Oracle-备份与恢复(二)RMAN备份-自动备份计划任务脚本...
  7. linux防火墙保存报错,29.Linux防火墙-firewalled
  8. 简单的shell命令
  9. 智能优化算法:类电磁机制算法 - 附代码
  10. java “lambda expressions not supported at this language level“
  11. 编程基本功:如果可能,不用if,尽量使用switch
  12. OpenCV(二)---朴素贝叶斯分类器 NormalBayesClassifier
  13. 3款电脑必备的常用运行库合集,你值得拥有
  14. 挂一张表,省的再瞎眼
  15. 计算机网络 期末复习
  16. iOS之小功能模块--彩虹动画进度条学习和自主封装改进
  17. kettle使用命令行来运行ktr和kjb文件
  18. hdu 1757【A Simple Math Problem】
  19. Failed to introspect Class from LaunchedURLClassLoader
  20. OCM_第二天课程:Section1 —》配置 Oracle 网络环境

热门文章

  1. 明源云童继龙:围绕地产生态链,做好数字化转型加速器
  2. 【大数据】五、链接分析(PageRank、Topic-sensetive PageRank)
  3. CHIL-ORACLE-创建表
  4. 【ACWing】1540. 主导颜色
  5. Scala学习(五)练习
  6. matlab 信号插零,【 MATLAB 】MATLAB 实现模拟信号采样后的重建(二)零阶保持(ZOH)...
  7. 民事诉讼法学类毕业论文文献包含哪些?
  8. 按键精灵打怪学习-前台和内网发送后台验证码
  9. webpack打包之后在浏览器能看到源文件
  10. 成都市武侯区计算机实验小学校长,成都市武侯区群文阅读研究活动在棕北小学召开...