链表之“无头单向非循环链表”

目录

​编辑

1.顺序表的问题及思考

2.链表

2.1链表的概念及结构

2.2无头单向非循环链表的实现

1.创建结构体

2.单链表打印

3.动态申请一个节点

3.单链表尾插

4.单链表头插

5.单链表尾删

6.单链表头删

7.单链表查找

8.单链表在pos位置之前插入x

9.单链表删除pos位置的值

10.单链表在pos位置之后插入x

11.单链表删除pos位置之后的值

12.单链表销毁

3.源码


1.顺序表的问题及思考

🌻问题:

  • 顺序表在尾部插入删除效率还不错,但是在头部或者中间位置插入删除,就需要挪动数据,时间复杂度为O(N),效率低下。
  • 空间满了以后只能增容,增容需要申请新的空间,拷贝数据,释放旧空间,会有一定的消耗。
  • 增容一般是呈2倍的增长,势必会有一定的空间浪费。假设当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。也就是说,一次增的越多,可能浪费越多;一次增的少了,那么就会频繁增容。

🌻思考:

  • 如何解决以上问题呢?下面让我们一起来看看链表的结构:

2.链表

2.1链表的概念及结构

🌴概念: 

  • 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
  • 简单来说,就是链表中的每一个数据元素都是单独存储的,我们把这个数据元素又叫做节点。当需要存储下一个数据元素的时候就申请一块新的内存用来存储数据,每一个节点又通过地址链接起来,当前节点存放的是下一个节点的地址。所以,一个节点里边就包含数据元素和下一个节点的地址这两部分。

🌴结构:

注意:

  1. 从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。
  2. 现实中的节点一般都是从堆上申请出来的。
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

假设在32位系统上,节点中值域为int类型,则一个节点的大小为8个字节,则也可能有下述链表:

2.2无头单向非循环链表的实现

1.创建结构体

typedef int SLTDataType;typedef struct SListNode
{SLTDataType val;        //数据域struct SListNode* next; //指针域
}SLNode;

2.单链表打印

  • phead是一个指针变量,占4个字节,它指向第一个节点,即存的是第一个节点的地址。而在其它节点中,每个节点里边分别又有两个数据域,其中一个保存val,另一个保存下一个节点的地址,在32位环境下,一个节点占8个字节。
  • 遍历链表时,担心phead会发生意外,所以我们先将phead的值赋给cur,看cur等不等于NULL,如果不等于NULL,就进入循环,打印第一个节点的值,然后将第二个节点的地址再赋给cur,看它等不等于NULL,如果不等于NULL,再打印第二个节点的值,再将第三个节点的地址赋给cur,以此循环,直到cur等于NULL,就跳出循环。

//单链表打印
void SLTPrint(SLNode* phead)
{//不要动phead,否则会找不到头SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}

3.动态申请一个节点

如果直接在其它函数内部申请节点的话,它的作用域就只能在那个函数内部,出了函数作用域就会销毁,所以得单独写一个函数来申请节点,让其它函数来调用它。

//动态申请一个节点
SLNode* CreateNode(SLTDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}

3.单链表尾插

单链表尾插分为有节点无节点两种情况:

  • 假设现在链表里边有节点,那我们要尾插一个数据,第一步肯定是要先找到尾。先定义一个tail,让它指向头节点,然后看tail->next等不等于NULL,如果不为NULL,让tail等于tail->next,依次循环,直到tail->next等于NULL的时候,tail刚好就是尾节点。找到尾节点后,再动态申请一个节点,让新申请的节点指向NULL,再让尾节点指向新申请的这个节点。

  • 如果链表里边没有节点,即链表为空,那我们直接让*pphead指向新节点的地址就行了。因为在主函数里边我们定义的是*plist的指针,所以要想改变外面结构体指针SLNode*,就要用二级指针SLNode**。
//单链表尾插
void SLTPushBack(SLNode** pphead, SLTDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

4.单链表头插

头插时比较简单,我们让新申请出来的节点newnode指向第一个节点的地址,而第一个节点的地址保存在plist里边,我们又把plist的地址赋给了pphead,所以让newnode->next指向*pphead,再将*pphead指向newnode就可以了。

//单链表头插
void SLTPushFront(SLNode** pphead, SLTDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);newnode->next = *pphead;*pphead = newnode;
}

5.单链表尾删

单链表的尾删也分为两种情况:

  • 第一种是当链表里边只有一个节点的时候,我们只需要free掉这个节点,然后让plist指向空就行了。
  • 第二种是当链表里边有多个节点的时候,我们可以定义两个指针,让第一个指针prev指向NULL,第二个指针保存头节点的地址,然后通过循环找尾,当找到尾的时候,跳出循环,此时prev刚好指向了尾的前一个节点,我们再free掉尾,让前一个节点指向空就可以了。
//单链表尾删
void SLTPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//1.一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//2.多个节点else{/*SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/SLNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

6.单链表头删

因为空链表无法删除,所以需要先断言。头删只需要将第一个节点free掉就可以了,但是free之前得先保存下一个节点的地址。

//单链表头删
void SLTPopFront(SLNode** pphead)
{assert(pphead);//空assert(*pphead);//一个或多个以上节点都可以处理SLNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

7.单链表查找

单链表的查找只需要遍历一遍就可以了,定义一个cur让它指向第一个节点的地址,通过循环如果cur不为空,就进入循环,进入循环后,如果cur->val等于x,则返回cur,否则,让cur指向下一个节点的地址。遍历完一遍如果没有找到,则返回NULL。可以说查找函数是为了配合修改单链表中的值而写的。

//单链表查找
SLNode* SLTFind(SLNode* phead, SLTDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}

8.单链表在pos位置之前插入x

如果此时链表为空,那么就相当于头插;如果不为空,则只需找到pos位置的前一个节点,让前一个节点的地址指向新节点,新节点指向pos位置即可。

//单链表在pos位置之前插入x
void SLTInsert(SLNode** pphead, SLNode* pos, SLTDataType x)
{assert(pphead);//要么都为空,要么都不为空assert((!pos && !(*pphead)) || (pos && *pphead));if (*pphead == pos){//头插SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}

9.单链表删除pos位置的值

如果链表只有一个节点,则相当于头删;如果有多个节点,则需要找到pos位置的前一个节点,然后让前一个节点指向pos位置的后一个节点,接着free掉pos即可。

//单链表删除pos位置的值
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos){//头删SLTPopFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}

10.单链表在pos位置之后插入x

创建一个新节点,先让新节点的指针域存pos后一个节点的地址,然后让pos的指针域存新节点的地址。

//单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLTDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}


11.单链表删除pos位置之后的值

这个操作必须保证链表中有两个以上的数据,如果只有一个数据的话,那它后面为空,没法执行删除操作,所以得先断言一下。

//单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}

12.单链表销毁

//单链表销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

3.源码

🌻SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType val;struct SListNode* next;
}SLNode;//单链表打印
void SLTPrint(SLNode* phead);//单链表尾插
void SLTPushBack(SLNode** pphead, SLTDataType x);
//单链表头插
void SLTPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删
void SLTPopBack(SLNode** pphead);
//单链表头删
void SLTPopFront(SLNode** pphead);//单链表查找
SLNode* SLTFind(SLNode* phead, SLTDataType x);//单链表在pos位置之前插入x
void SLTInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
//单链表删除pos位置的值
void SLTErase(SLNode** pphead, SLNode* pos);//单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLTDataType x);
//单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos);//单链表销毁
void SLTDestroy(SLNode** pphead);

🌻SList.c

#include "SList.h"//单链表打印
void SLTPrint(SLNode* phead)
{//不要动phead,否则会找不到头SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}//动态申请一个节点
SLNode* CreateNode(SLTDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}//单链表尾插
void SLTPushBack(SLNode** pphead, SLTDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}//单链表头插
void SLTPushFront(SLNode** pphead, SLTDataType x)
{assert(pphead);SLNode* newnode = CreateNode(x);newnode->next = *pphead;*pphead = newnode;
}//单链表尾删
void SLTPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//1.一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//2.多个节点else{/*SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/SLNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}//单链表头删
void SLTPopFront(SLNode** pphead)
{assert(pphead);//空assert(*pphead);//一个或多个以上节点都可以处理SLNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//单链表查找
SLNode* SLTFind(SLNode* phead, SLTDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}//单链表在pos位置之前插入x
void SLTInsert(SLNode** pphead, SLNode* pos, SLTDataType x)
{assert(pphead);//要么都为空,要么都不为空assert((!pos && !(*pphead)) || (pos && *pphead));if (*pphead == pos){//头插SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}//单链表删除pos位置的值
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos){//头删SLTPopFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}//单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLTDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}//单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}//单链表销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/486514.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

入门Web自动化测试之Selenium+Python基础方法封装

之前我们讲过元素定位的配置管理&#xff0c;这一篇我们来学习封装内容。 封装 既然选择了Python这门语言来实现Web自动化&#xff0c;那我们就不得不讲到一个重要的概念&#xff0c;那就是面向对象编程理念中的封装。 就字面意思来理解的话&#xff0c;就像是把各种物品放入…

素数筛法详解:埃氏筛和欧拉筛

素数筛法详解&#xff1a;埃氏筛和欧拉筛-CSDN博客https://blog.csdn.net/GD_ONE/article/details/104660294/?ops_request_misc&request_id&biz_id102&utm_term%E5%9F%83%E5%BC%8F%E7%AD%9B%E6%B3%95&utm_mediumdistribute.pc_search_result.none-task-blog-…

【知识整理】Git Commit Message 规范

一. 概述 前面咱们整理过 Code Review 一文&#xff0c;提到了 Review 的重要性&#xff0c;已经同过gitlab进行CodeReview 的方式&#xff0c;那么本文详细说明一下对CodeReivew非常重要的Git Commit Message 规范。 我们在每次提交代码时&#xff0c;都需要编写 Commit Mes…

Docker容器故障排查与解决方案

Docker是一种相对使用较简单的容器&#xff0c;我们可以通过以下几种方式获取信息&#xff1a; 1、通过docker run执行命令&#xff0c;或许返回信息 2、通过docker logs 去获取日志&#xff0c;做有针对性的筛选 3、通过systemctl status docker查看docker服务状态 4、通过…

Redis 事务机制之ACID属性

事务属性 事务是对数据库进行读写的一系列操作。在事务执行时提供ACID属性保证&#xff1a; 包括原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xff08;Durability&#xff09;…

mysql-多表查询-内连接

一、简介 MySQL中的内连接&#xff08;INNER JOIN&#xff09;是一种多表查询的方式&#xff0c;它返回两个表中满足连接条件的记录。这意味着&#xff0c;只有当一个记录在两个表中都存在时&#xff0c;它才会出现在结果集中。 二、内连接查询语法 &#xff08;1&#xff0…

如何使用ChatGPT创建一份优质简历

目录 第一步&#xff1a;明确目标和重点 第二步&#xff1a;与ChatGPT建立对话 第三步&#xff1a;整理生成的内容 第四步&#xff1a;注重行文风格 第五步&#xff1a;强调成就和量化结果 第六步&#xff1a;个性化和定制 第七步&#xff1a;反复修改和完善 总结 在现…

69.x的平方根

目录 一、题目 二、暴力求解 三、二分查找&#xff08;改进&#xff09; 一、题目 https://leetcode.cn/problems/sqrtx/description/ 二、暴力求解 1.溢出问题 2.x为1 class Solution { public:int mySqrt(int x) {if(x 1)return 1;long long i0;for(i;i<x/2;i){if(…

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测 目录 分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测分类效果基本描述程序设计参考资…

华为OD机试真题-来自异国的客人-2023年OD统一考试(C卷)--Python3--开源

题目&#xff1a; 考察内容&#xff1a; 10进制转为任何进制 代码&#xff1a; """ 题目分析&#xff1a;输入&#xff1a; k --物品价值&#xff1b;n 幸运数字&#xff1b;m 进制 输出&#xff1a; 幸运数字的个数 异常&#xff1b;0 eg; 10 2 4思路&…

HTML5技术实现的小钢琴

HTML5技术实现的小钢琴 用HTML5实现的小钢琴&#xff0c;按下钢琴键上的相应字母用或用鼠标点击钢琴键发声&#xff0c;源码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"v…

原型设计工具Axure RP

Axure RP是一款专业的快速原型设计工具。Axure&#xff08;发音&#xff1a;Ack-sure&#xff09;&#xff0c;代表美国Axure公司&#xff1b;RP则是Rapid Prototyping&#xff08;快速原型&#xff09;的缩写。 下载链接&#xff1a;https://www.axure.com/ 下载 可以免费试用…