单向链表

什么是单向链表

链表是一种物理储存单元上非连续、非顺序的储存结构。它由一系列结点(链表中每一个元素称为结点)组成,结点可动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

其实可以形象的认为,单向链表就好像一列火车。

链表的节点就好像火车的每一节车厢,而链表节点的数据域就对应车厢中存放的货物,链表节点的指针域充当连接各节车厢的锁链。

为什么需要单向链表

我们知道如何用数组去存储数据。但是在实际应用的过程中,我们有时会出现一些不可避免的问题:

问题1

假如定义了并初始化一个数组 a[5] = {1,2,3,4,5}, 此时要在加入新的元素,只能开辟一个更大的数组。

问题2

假如定义一个数组 a[100000], 但是只存储了几个元素,会导致浪费空间。

而单向链表这样链式的动态存储的数据结构,恰恰解决了使用数组时若数组已满无法插入新的数据的问题,以及使用数组时可能浪费大量存储空间的问题。


单向链表的创建

单个链表节点的构成

首先看一下单向链表节点的结构体,我们把单个节点看作是一节车厢,数据域就好像车厢存储的货物,指针域就好像锁链。

typedef int Elemtype;        //数据类型

typedef struct Node {

    Elemtype data;           //结构体数据域
struct Node *next; //结构体指针域 } Linklist;

链表的初始化创建

链表一般需要一个头节点,这个头结点一般是不带数据的。可以看作是驱动火车前进的火车头。

//链表的初始化
Linklist* Initial_linklist(){
//向系统申请内存
Linklist *head = (Linklist *)malloc(sizeof(Linklist));
head->next = NULL;
return head;
}

链表初始数据插入

单向链表的初始数据可以是任意个,此处采用的是尾插法,后面会具体解释这个方法。

//创建初始链表  采用尾插法
void Create_linklist(Linklist *head, int n) {
Linklist *node, *end; //普通节点 尾节点
end = head; //当链表为空时 头尾指向同一个节点
printf("创建链表输入 %d 个元素:", n);
for (int i = 0; i < n; i++) { //n为插入普通节点的个数
node = (Linklist *)malloc(sizeof(Linklist));
scanf("%d", &node->data);
end->next = node; //当前end的next指向了新节点node
end = node; //end往后移,此时新的节点变成尾节点
}
end->next = NULL; //end最后置NULL
}

单向链表的基本操作

头插法 插入单个数据

头插法图解

头插法代码

//头插法 插入单个数据
void Insert_Front(Linklist *head, int data) {
Linklist *node = (Linklist *)malloc(sizeof(Linklist));
node->next = NULL;
node->data = data; node->next = head->next; //新节点node的next指向当前head的next
head->next = node; //head的next重新指向新节点node
}

尾插法 插入单个数据

尾插法图解

尾插法代码

//尾插法 插入单个数据
void Insert_Back(Linklist *head, int data) {
Linklist *node = (Linklist *)malloc(sizeof(Linklist));
node->next = NULL;
node->data = data; Linklist *end = head; //起初end指向头节点
while (end->next != NULL)
end = end->next; //end指针往后移,直到最后一个节点
end->next = node; //当前end的next指向了新节点node
}

指定位置插入节点

分析及解释

指定位置插入单个数据,这里一般设定为在第 k 个带数据的普通节点(除去头节点)位置插入一个新的节点。其实指定位置插入新节点的原理和头插法是类似的,只不过是将第 k 个带数据节点的前一个节点看成头结点一样。因此我们需要找到第 k-1 个带数据节点,假如遍历指针 t 从头节点开始,那么算上头节点,需要经历 k-1 次循环找到第 k-1 个带数据节点。值得注意的是,如果k = 1的话,也就是在第一个带数据节点的位置插入新节点,不存在第0个带数据节点,此时其实这个第 k-1 个节点就变成了头节点。那么此时明显和头插法是完全一样的。

图解的话可以参考头插法的图解,是差不多的。

指定位置插入单个数据 代码

//指定位置插入单个节点
void Insert_position(Linklist *head, int k) {
//k表示在第k个普通节点的位置插入新节点
Linklist *t = head, *in; //t为遍历指针
//in是要插入的新节点
for (int i = 0; i < k - 1; i++)
t = t->next; if (t != NULL) {
in = (Linklist *)malloc(sizeof(Linklist));
in->next = NULL;
printf("在第 %d 个节点处插入新节点的数据: ", k);
scanf("%d", &in->data);
in->next = t->next; //插入节点in的next指向当前第k-1个普通节点的next指向的节点
t->next = in; //第k-1个普通节点的next重新指向插入的节点in //原理和头插法类似 就好像把第k-1个普通节点t看做是头节点
} else {
puts("节点不存在");
}
}

遍历输出链表数据

分析及解释

我们需要用指针去访问链表中的数据。定义了一个指针t,来对链表的每一个存有数据的节点进行访问并读取数据,直到当前节点为NULL,停止遍历。

通俗地来说,就好比一个卸货员工,他挨个从头到尾取下每一节火车车厢的货物,直到最后到达尾部车厢的时候,他便不再取下货物。

打印链表代码

//打印链表
void Show_linklist(Linklist *head) {
Linklist *t = head->next; //t为遍历指针 访问每个节点数据 if (t == NULL)
puts("链表为空"); while (t != NULL) {
printf("%d ", t->data);
t = t->next;
}
printf("\n\n");
}

指定位置删除节点

分析及解释

删除单个节点很容易,只需要找到要删除的节点的前一个节点,然后直接跨过中间这个删除的节点指向删除节点的下一个节点就可以了。

指定位置删除节点 图解

指定位置删除节点 代码

void Delete_position(Linklist *head, int k) { //k表示要删除第k个节点
Linklist *t = head, *del = NULL; //t为遍历指针
int i = 0;
while (i < k - 1 && t != NULL) {
t = t->next; //t指向删除的第k个的前一个节点
i++;
}
if (t != NULL) {
del = t->next;
t->next = del->next;
free(del);
} else {
puts("节点不存在");
}
}

其他基本操作

其他基本操作代码

//查找元素返回节点位置
void Find_Element(Linklist *head, int x) {
Linklist *t = head->next;
while (t != NULL) {
int sub = 1;
if (t->data == x)
printf("元素 %d 的位置为: %d \n", x, sub); t = t->next;
sub++;
}
if (t == NULL)
puts("元素不存在");
} //读取指定节点位置元素
void Read_position(Linklist *head, int k) {
Linklist *t = head->next;
for (int i = 0; i < k; i++)
t = t->next;
printf("第 %d 个节点位置的数据为: %d \n", k, t->data);
} //计算链表的长度
void List_length(Linklist *head){
Linklist *t = head->next;
int len = 0;
while(t){
len++;
t = t->next;
}
printf("链表的长度为: %d \n", len);
} //清空链表
void Clear_linklist(Linklist *head) {
Linklist *t;
while (head->next != NULL) {
t = head->next;
head->next = t->next;
free(t);
}
} //判断是否为空
bool IsEmpty(Linklist *head){
return head->next == NULL;
}

完整程序

完整程序源代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h> typedef int Elemtype; //数据类型 typedef struct Node { Elemtype data; //结构体数据域
struct Node *next; //结构体指针域 } Linklist; //链表的初始化
Linklist* Initial_linklist(){
//向系统申请内存
Linklist *head = (Linklist *)malloc(sizeof(Linklist));
head->next = NULL;
return head;
} //创建初始链表 采用尾插法
void Create_linklist(Linklist *head, int n) { //头节点(不带数据)
Linklist *node, *end; //普通节点 尾节点
end = head; //当链表为空时 头尾指向同一个节点
printf("创建链表输入 %d 个元素:", n);
for (int i = 0; i < n; i++) { //n为插入普通节点的个数
node = (Linklist *)malloc(sizeof(Linklist));
scanf("%d", &node->data);
end->next = node; //当前end的next指向了新节点node
end = node; //end往后移,此时新的节点变成尾节点
}
end->next = NULL; //end最后置NULL
} //打印链表
void Show_linklist(Linklist *head) {
Linklist *t = head->next; //t为遍历指针 访问每个节点数据
if (t == NULL)
puts("链表为空"); while (t != NULL) {
printf("%d ", t->data);
t = t->next;
}
printf("\n\n");
} //头插法 插入单个数据
void Insert_Front(Linklist *head, int data) {
Linklist *node = (Linklist *)malloc(sizeof(Linklist));
node->next = NULL;
node->data = data; node->next = head->next; //新节点node的next指向当前head的next
head->next = node; //head的next重新指向新节点node
} //尾插法 插入单个数据
void Insert_Back(Linklist *head, int data) {
Linklist *node = (Linklist *)malloc(sizeof(Linklist));
node->next = NULL;
node->data = data; Linklist *end = head; //起初end指向头节点
while (end->next != NULL)
end = end->next; //end指针往后移,直到最后一个节点
end->next = node; //当前end的next指向了新节点node
} //指定位置插入单个数据
void Insert_position(Linklist *head, int k) { //k表示在第k个普通节点的位置插入新节点
Linklist *t = head, *in; //t为遍历指针
//in是要插入的新节点
for (int i = 0; i < k - 1; i++)
t = t->next; if (t != NULL) {
in = (Linklist *)malloc(sizeof(Linklist));
in->next = NULL;
printf("在第 %d 个节点处插入新节点的数据: ", k);
scanf("%d", &in->data);
in->next = t->next; //插入节点in的next指向当前第k-1个普通节点的next指向的节点
t->next = in; //第k-1个普通节点的next重新指向插入的节点in //原理和头插法类似 就好像把第k-1个普通节点t看做是头节点
} else {
puts("节点不存在");
}
} //指定位置改变节点的数据
void Change_position(Linklist *head, int n) { //n表示要改变的是第n个普通节点
Linklist *t = head; //t为遍历指针
for (int i = 0; i < n; i++)
t = t->next; //t指向要改变的节点 if (t != NULL) {
printf("修改第 %d 个节点的数据: ", n);
scanf("%d", &t->data);
} else {
puts("节点不存在");
}
} //指定位置删除节点
void Delete_position(Linklist *head, int k) { //k表示要删除第k个节点
Linklist *t = head, *del = NULL; //t为遍历指针
int i = 0;
while (i < k - 1 && t != NULL) {
t = t->next; //t指向删除的第k个的前一个节点
i++;
}
if (t != NULL) {
del = t->next;
t->next = del->next;
free(del);
} else {
puts("节点不存在");
}
} //查找元素返回节点位置
void Find_Element(Linklist *head, int x) {
Linklist *t = head->next;
while (t != NULL) {
int sub = 1;
if (t->data == x)
printf("元素 %d 的位置为: %d \n", x, sub); t = t->next;
sub++;
}
if (t == NULL)
puts("元素不存在");
} //读取指定节点位置元素
void Read_position(Linklist *head, int k) {
Linklist *t = head->next;
for (int i = 0; i < k; i++)
t = t->next;
printf("第 %d 个节点位置的数据为: %d \n", k, t->data);
} //计算链表的长度
void List_length(Linklist *head){
Linklist *t = head->next;
int len = 0;
while(t){
len++;
t = t->next;
}
printf("链表的长度为: %d \n", len);
} //清空链表
void Clear_linklist(Linklist *head) {
Linklist *t;
while (head->next != NULL) {
t = head->next;
head->next = t->next;
free(t);
}
} //判断是否为空
bool IsEmpty(Linklist *head){
return head->next == NULL;
} int main() {
//头指针初始化
Linklist *mylist;
mylist = Initial_linklist(); Create_linklist(mylist, 10);
printf("初始状态链表:\n");
Show_linklist(mylist); Insert_Front(mylist, 30);
Insert_Back(mylist, 30);
printf("链表进行首尾插入数字30后:\n");
Show_linklist(mylist); Insert_position(mylist, 5);
printf("链表进行在第5个节点后插入新节点后:\n");
Show_linklist(mylist); Change_position(mylist, 4);
printf("链表进行改变第4个数据后:\n");
Show_linklist(mylist); Delete_position(mylist, 1);
printf("链表进行删除第1个数据后:\n");
Show_linklist(mylist); Clear_linklist(mylist);
printf("链表进行清空后:\n");
if(IsEmpty(mylist))
puts("链表为空"); return 0;
}

程序运行结果图

[数据结构]单向链表及其基本操作(C语言)的更多相关文章

  1. 数据结构-单向链表 C和C++的实现

    数据结构,一堆数据的存放方式. 今天我们学习数据结构中的 链表: 链表的结构: 链表是一种特殊的数组,它的每个元素称为节点,每个节点包括两个部分: 数据域:存放数据,此部分与数组相同 指针域:存放了下 ...

  2. python数据结构——单向链表

    链表 ( Linked List ) 定义:由许多相同数据类型的数据项按照特定顺序排列而成的线性表. 特点:各个数据在计算机中是随机存放且不连续. 优点:数据的增删改查都很方便,当有新的数据加入的时候 ...

  3. Linux C 数据结构 ->单向链表<-(~千金散尽还复来~)

    之前看到一篇单向链表的博文,代码也看着很舒服,于是乎记录下来,留给自己~,循序渐进,慢慢 延伸到真正的内核链表~(敢问路在何方?路在脚下~) 1. 简介 链表是Linux 内核中最简单,最普通的数据结 ...

  4. Linux C 数据结构 ->单向链表

    之前看到一篇单向链表的博文,代码也看着很舒服,于是乎记录下来,留给自己~,循序渐进,慢慢 延伸到真正的内核链表~(敢问路在何方?路在脚下~) 1. 简介 链表是Linux 内核中最简单,最普通的数据结 ...

  5. 数据结构—单链表(类C语言描写叙述)

    单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List). 链表的详细存储表示为: ① 用一组随意的存储单元来存放线性表的结点(这组存储单元既能够是连续的.也能够是不连续的) ...

  6. C语言数据结构-单链表的实现-初始化、销毁、长度、查找、前驱、后继、插入、删除、显示操作

    1.数据结构-单链表的实现-C语言 typedef struct LNode { int data; struct LNode* next; } LNode,*LinkList; //这两者等价.Li ...

  7. Python3玩转单链表——逆转单向链表pythonic版

    [本文出自天外归云的博客园] 链表是由节点构成的,一个指针代表一个方向,如果一个构成链表的节点都只包含一个指针,那么这个链表就是单向链表. 单向链表中的节点不光有代表方向的指针变量,也有值变量.所以我 ...

  8. Python链表的实现与使用(单向链表与双向链表)

    参考[易百教程]用Python实现链表及其功能 """ python链表的基本操作:节点.链表.增删改查 """ import sys cl ...

  9. 数据结构算法C语言实现(五)---2.3重新定义线性链表及其基本操作

    一.简述 ...由于链表在空间的合理利用上和插入.删除时不需要移动等的优点,因此在很多场合下,它是线性表的首选存储结构.然而,它也存在着实现某些基本操作,如求线性表的长度时不如顺序存储结构的缺点:另一 ...

  10. C语言 - 基础数据结构和算法 - 单向链表

    听黑马程序员教程<基础数据结构和算法 (C版本)>,照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友可 ...

随机推荐

  1. SpringBoot+MyBatis Plus对Map中Date格式转换的处理

    在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式 问题 现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需 ...

  2. jsp页面中怎么利用a标签的href进行传递参数以及需要注意的地方

    jsp页面中: <a href="${pageContext.request.contextPath }/infoController/getProductInfo?productId ...

  3. 『现学现忘』Git基础 — 35、Git中删除文件

    目录 1.删除文件说明 2.删除文件操作 (1)仅删除暂存区的文件 (2)完全删除文件 3.本文用到的命令总结 1.删除文件说明 在Git工作目录中要删除某个文件,首先要清楚该文件所处的状态. 若要是 ...

  4. JPA入门学习集合springboot(一)

    1.在pom.xml文件中添加相应依赖 SpringData jpa和数据库MySql <!-- Spring Data JPA 依赖(重要) --> <dependency> ...

  5. JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题

    1.线程和进程 进程:一个程序,微信.qq...程序的集合.(一个进程包含多个线程,至少包含一个线程.java默认有两个线程:主线程(main).垃圾回收线程(GC) 线程:runnable.thre ...

  6. 7.Gitee导入其他远程托管中心仓库

    的码云是开源中国推出的基于Git的代码托管服务中心 网址是https://gitee.com/,使用方式跟github一致,并且是一个中文网站 码云的使用配置方式与github一致,码云支持导入git ...

  7. SpringBoot自定义注解+异步+观察者模式实现业务日志保存

    一.前言 我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和sprin ...

  8. 关于Object.keys()和Object.values()的使用

    关于Object.keys()和Object.values()的使用 1. 关于Object.keys() 1) 处理对象,返回可枚举的所有可枚举属性的字符串数组 let person ={ name ...

  9. 重新整理 .net core 实践篇 ———— linux上排查问题 [外篇]

    前言 简单介绍一下在排查问题.献给初学者. 该文的前置篇: https://www.cnblogs.com/aoximin/p/16838657.html 正文 什么是linux系统 linux 是基 ...

  10. 研发效能|DevOps 已死平台工程永存带来的焦虑

    最近某位大神在推特上发了一个帖子,结果引来了国内众多卖课机构.培训机构的狂欢,开始贩卖焦虑,其实「平台工程」也不是什么特别高深莫测的东西.闲得无聊,把这位大神的几个帖子薅了下来,你看过之后就会觉得没啥 ...