数据结构(二)——线性表(单链表)

 2.3线性表的链式表示

顺序表的优缺点:
优点:可随机存储,存储密度高
缺点:要求大片连续空间,且改变容量不方便

2.3.1 单链表的基本概念

单链表:用链式存储实现了线性结构。一个结点存储一个数据元素,各结点间的前后关系用一个指针表示。
优点:不要求大片连续空间,改变容量方便。
缺点:不可随机存取,要耗费一定空间存放指针。

//用代码定义一个单链表
struct LNode{    //LNode是结点ElemType data;    //data为数据域 定义单链表结点类型struct LNode *next;  //*next是指针域 指针指向下一个结点  
}struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
//增加一个新的结点:在内存中申请一个结点所需空间,并用指针n指向这个结点

typedef关键字——数据类型重命名
typedef <数据类型> <别名>
typedef struct LNode LNode;        //这样使用就可以直接哟弄个LNode 不需要前面加一长串
typedef struct LNode *LinkList;

要表示一个单链表时,只需要表明一个头指针L,指向单链表的第一个结点
LNode *L;        //声明一个指向单链表第一个结点的指针 

LinkList L;        //声明一个指向单链表第一个结点的指针  代码可读性更强

不带头结点的单链表

typedef struct LNode{    //定义单链表结点类型ElemType data;       //每个节点存放一个数据元素struct LNode *next;    //指针指向下一个节点
}LNode, *LinkList;//初始化一个空的单链表
bool InitList(LinkList &L){L = NULL; //空表,暂时还没有任何结点 防止脏数据return true;
}void test(){LinkList L;  //声明一个指向单链表的头指针//初始化一个空表InitList(L);...
}//判断单链表是否为空
bool Empty(LinkList L){return (L==NULL)
}

带头结点的单链表

typedef struct LNode{          ElemType data;      struct LNode *next;    //指针指向下一结点
}LNode, *LinkList;// 初始化一个单链表(带头结点)
bool InitList(LinkList &L){      L = (LNode *)malloc(sizeof(LNode));  //分配一个头结点 if (L == NULL)        //内存不足,分配失败    return false;    L->next = NULL;       //头结点之后暂时还没有结点   return true;
}void test(){     LinkList L;  //声明一个指向单链表的头指针 //初始化一个空表    InitList(L);     ...
}//判断单链表是否为空
bool Empty(LinkList L){  if (L->next == NULL) return true;     else             return false;
}

不带头结点,写代码更麻烦 对第一个数据结点和后续数据结点的 处理需要用不同的代码逻辑 对空表和非空表的处理需要用不同的 代码逻辑

带头结点,写代码更方便

2.3.2_1单链表的插入删除

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e

按位序插入(带头结点):

typedef struct LNode{ElemType data;struct LNode *next;
}LNode,*LinkList;//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i ElemType e){    //i是要插入的位序if(i<1)    //i表示的是位序 位序最小是1 return false;LNode *p;    //指针p指向当前扫描到的结点int j=0;    //当前p指向的是第几个结点p=L;        //L指向头结点,头结点是第0个结点,不存数据while(p!=NULL && j<i-1){    //循环找到第i-1个结点 如果i>length p最后会等于NULLp=p->next;j++;}if(p==NULL)       //i值不合法return false;LNode *s=(LNode*)malloc(sizeof(LNode));    //malloc开辟一块空间ss->data=e;    //把要插入的元素存到新结点中s->next=p->next;    //让s指向的next的指针等于p结点的next指针指向的位置p->next=s;    //让p的结点的next指针指向新的结点s 即将结点s连到p之后return true;    //插入成功
}

时间复杂度:这里问题规模n指的是表的长度
        最好时间复杂度:O(1)
        最坏时间复杂度:O(n)
        平均时间复杂度:O(n)

按位序插入(不带头结点):
由于不存在“第0个”结点,因此i=1时需要特殊处理

typedef struct LNode{     ElemType data;      struct LNode *next;
}LNode, *LinkList;//在第i个位置插入元素e
bool ListInsert(LinkList &L, int i, ElemType e){   //判断i的合法性     if(i<1)      return false; //需要判断插入的位置是否是第1个 对第一个结点单独写操作进行处理    if(i==1){              LNode *s = (LNode *)malloc(size of(LNode)); //malloc申请一个结点s->data =e;          //把要插入的e放到新申请的结点中s->next =L;       //让新结点的指针指向LL=s;          //头指针指向新结点   return true;      }       //i>1的情况与带头结点一样,唯一区别是j的初始值为1   LNode *p;       //指针p指向当前扫描到的结点     int j=1;        //当前p指向的是第几个结点   p = L;          //p指向的是第1个结点,注意不是头结点//循环找到第i-1个结点    while(p!=NULL && j<i-1){     //如果i>lengh,p最后会等于NULL    p = p->next;             j++;      }       //p值为NULL说明i值不合法   if (p==NULL)           return false;      //在第i-1个结点后插入新结点   LNode *s = (LNode *)malloc(sizeof(LNode));  s->data = e;      s->next = p->next;  p->next = s;        return true;
}

结论:不带头结点写代码更不方便,推荐用带头结点注意

指定结点的后插操作:

typedef struct LNode{        ElemType data;        struct LNode *next;
}LNode, *LinkList;// 在结点p后插入元素e
bool InsertNextNode(LNode *p, ElemType e){      if(p==NULL){         return false;   }    LNode *s = (LNode *)malloc(sizeof(LNode));     if(s==NULL)     //内存分配失败return false;    s->data = e;     //用结点s保存数据元素es->next = p->next;  p->next = s;     //将结点s连接到p后return true;
}// 按位序插入的函数中可以直接调用后插操作
bool ListInsert(LinkList &L, int i, ElemType e){  if(i<1)            return False;LNode *p;     //指针p指向当前扫描到的结点 int j=0;        //当前p指向的是第几个结点 p = L;       //循环找到第i-1个结点   while(p!=NULL && j<i-1){ //如果i>lengh, p最后会等于NULL   p = p->next;        j++;       }       return InsertNextNode(p, e)
}

时间复杂度:O(1)

指定结点的前插操作:

typedef struct LNode{     ElemType data;      struct LNode *next;
}LNode, *LinkList;//前插操作: 在结点p前插入元素e
bool InsertPriorNode(LNode *p, ElemType e){  if(p==NULL)      return false;  LNode *s = (LNode *)malloc(sizeof(LNode));  if(s==NULL)           // 内存分配失败       return false;    s->next = p->next;        p->next = s;       // 将新结点s连到结点p之后   s->data = p->data;       //将p中元素复制到s中p->data = e;       //p中元素覆盖为ereturn true;
}

按位序删除(带头结点)

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值
找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点,头结点可看做“第0个”结点

typedef struct LNode{       ElemType data;    struct LNode *next;}LNode, *LinkList;// 删除第i个结点并将其所保存的数据存入e
bool ListDelete(LinkList &L, int i, ElemType &e){      if(i<1)             return false;     LNode *p;       //指针p指向当前扫描到的结点     int j=0;        //当前p指向的是第几个结点    p = L;         //L指向头结点,头结点是第0个结点,不存数据while(p!=NULL && j<i-1){       //循环找到第i-1个结点     p = p->next;                    //如果i>lengh,p和p的后继结点会等于NULL        j++;      }       if(p==NULL)       //i值不合法return false;    if(p->next == NULL)        //第i-1个结点之后已无其他结点return false;    	   LNode *q = p->next;        //令q指向被删除的结点   e = q->data;             //把删除的结点的值存到变量ep->next = q->next;      //p的next指向q的next 就是null 相当于将*q结点从链中“断开”free(q);                //释放结点的存储空间     return true;        //删除成功
}

指定结点的删除 
删除结点p,需要修改其前驱 结点的 next 指针
方法1:传入头指针,循环寻找 p 的前驱结点
方法2:类似于结点前插的实现

// 删除指定结点p
bool DeleteNode(LNode *p){   if(p==NULL)           return false;     LNode *q = p->next; // 令q指向p的后继结点  // 如果p是最后一个结点,则q指向NULL,继续执行就会报错  p->data = q->data;  //把后继结点的数据复制到p结点的数据域中p->next = q->next;   //让p结点的next指针指向q结点后的位置 相当于将*q结点从链中“断开”free(q);    //释放q结点的空间return true;
}

 

2.3.2_2单链表的插入查找

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

typedef struct LNode{  ElemType data;    struct LNode *next;
}LNode, *LinkList;// 查找指定位序i的结点并返回
LNode * GetElem(LinkList L, int i){   if(i<0)            return NULL;   LNode *p;     //指针p指向当前扫描到的结点int j=0;     //当前p指向的是第几个结点p = L;        //L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i){   p = p->next;     j++;      }        return p;
}// 在结点p后插入元素e
bool InsertNextNode(LNode *p, ElemType e){      if(p==NULL){         return false;   }    LNode *s = (LNode *)malloc(sizeof(LNode));     if(s==NULL)     //内存分配失败return false;    s->data = e;     //用结点s保存数据元素es->next = p->next;  p->next = s;     //将结点s连接到p后return true;
}// 封装后的插入操作,在第i个位置插入元素e
bool ListInsert(LinkList &L, int i, ElemType e){  if(i<1)             return False;  // 找到第i-1个元素     LNode *p = GetElem(L, i-1);   // 在p结点后插入元素e     return InsertNextNode(p, e);//调用查询操作和后插操作
}

平均时间复杂度:O(n) 

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

// 查找数据域为e的结点指针,否则返回NULL
LNode * LocateElem(LinkList L, ElemType e){           LNode *p = L->next;  //定义一个指针p,让他指向头结点的下一个结点     // 从第一个结点开始查找数据域为e的结点  while(p!=NULL && p->data != e){   //当p不为NULL且数据跟传入的e不相等时循环p = p->next;     //让p指向下一个结点}     return p;    //跳出循环后返回p
}

平均时间复杂度:O(n)  只能依次往后查找

求表的长度

// 计算单链表的长度
int Length(LinkList L){      int len=0;       //统计表长  LNode *p = L;while(p->next != NULL){ p = p->next;      len++;       }      return len;
}

2.3.2_3单链表的插入建立

尾插法

// 使用尾插法建立单链表L
LinkList List_TailInsert(LinkList &L){   int x;			//设ElemType为整型int  L = (LinkList)malloc(sizeof(LNode));     //建立头结点(初始化空表)     LNode *s, *r = L;                        //r为表尾指针    scanf("%d", &x);                         //输入要插入的结点的值   while(x!=9999){                          //输入9999表示结束     s = (LNode *)malloc(sizeof(LNode));    s->data = x;           r->next = s;                         //在r结点之后插入元素xr = s;                               //r指针指向新的表尾结点     scanf("%d", &x);       }    r->next = NULL;                          //尾结点指针置空      return L;
}

头插法

LinkList List_HeadInsert(LinkList &L){       //逆向建立单链表   LNode *s;      int x;     L = (LinkList)malloc(sizeof(LNode));     //建立头结点   L->next = NULL;                          //初始为空链表,先把头指针指向NULL,这步很关键  scanf("%d", &x);                         //输入要插入的结点的值  while(x!=9999){                          //输入9999表结束     s = (LNode *)malloc(sizeof(LNode));  //创建新结点s->data = x;          s->next = L->next;      L->next = s;                  //将新结点插入表中,L为头指针   scanf("%d", &x);       }     return L;
}

 头插法实现链表的逆置:

// 将链表L中的数据逆置并返回
LNode *Inverse(LNode *L){	LNode *p, *q;	  p = L->next;     //p指针指向第一个结点	  L->next = NULL;  //头结点置空       // 依次判断p结点中的数据并采用头插法插到L链表中	while (p != NULL){		   q = p;		  p = p->next;	q->next = L->next;  L->next = q;	}	   return L;
}

 头插法逆置详见【数据结构】单链表逆置:头插法图解

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

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

相关文章

【IEEE列表会议】IEEE第三届信息与通信工程国际会议国际会议(JCICE 2024)

会议简介 Brief Introduction 2024年第三届信息与通信工程国际会议国际会议 (JCICE 2024) 会议时间&#xff1a;2024年5月10日-12日 召开地点&#xff1a;中国福州 大会官网&#xff1a;JCICE 2024-2024 International Joint Conference on Information and Communication Engi…

高电平复位电路工作原理详解

单片机复位电路的作用是&#xff1a;使单片机恢复到起始状态&#xff0c;让单片机的程序从头开始执行&#xff0c;运行时钟处于稳定状态、各种寄存器、端口处于初始化状态等等。目的是让单片机能够稳定、正确的从头开始执行程序。一共分为&#xff1a;高电平复位&#xff0c;低…

程序员失业,被迫开启 PlanB——成为自由职业/独立开发者的第 0 天

程序员失业&#xff0c;被迫开启 PlanB——成为自由职业/独立开发者的第 0 天 今天在逛V2EX的时候看到的一个帖子&#xff0c;程序员中年被裁&#xff0c;被迫开启独立开发这条路。 原贴如下&#xff1a; lastday, 失业啦 公司年前通知我合同到期不续签&#xff0c;今天是我…

docker学习进阶

一、dockerfile解析 官方文档&#xff1a; Dockerfile reference | Docker Docs 1.1、dockfile是什么&#xff1f; dockerfile是用来构建docker镜像的文本文件&#xff0c;由一条条构建镜像所需的指令和参数构成的脚本。 之前我们介绍过通过具体容器反射构建镜像(docker comm…

JavaWeb - 3 - JavaScript(JS)

JavaScript(JS)官方参考文档&#xff1a;JavaScript 教程 JavaScript&#xff08;简称&#xff1a;JS&#xff09;是一门跨平台、面向对象的脚本语言&#xff0c;是用来控制网页行为的&#xff0c;它能使网页可交互&#xff08;脚本语言就不需要编译&#xff0c;直接通过浏览器…

Luajit 2023移动版本编译 v2.1.ROLLING

文章顶部有编好的 2.1.ROLLING 2023/08/21版本源码 Android 64 和 iOS 64 luajit 目前最新的源码tag版本为 v2.1.ROLLING on Aug 21, 2023应该是修正了很多bug, 我是出现下面问题才编的. cocos2dx-lua 游戏 黑屏 并报错: [LUA ERROR] bad light userdata pointer 编…

【校园导航小程序】2.0版本 静态/云开发项目 升级日志

演示视频 【校园导航小程序】2.0版本 静态/云开发项目 演示 首页 重做了首页&#xff0c;界面更加高效和美观 校园指南页 新增了 “校园指南” 功能&#xff0c;可以搜索和浏览校园生活指南 地图页 ①弃用路线规划插件&#xff0c;改用SDK开发包。可以无阻通过审核并发布…

seq2seq翻译实战-Pytorch复现

&#x1f368; 本文为[&#x1f517;365天深度学习训练营学习记录博客 &#x1f366; 参考文章&#xff1a;365天深度学习训练营 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制]\n&#x1f680; 文章来源&#xff1a;[K同学的学习圈子](https://www.yuque.com/…

前端使用Ant Design中的Modal框实现长按顶部拖动弹框需求

需求&#xff1a;需要Ant Design中的一个Modal弹框&#xff0c;并且可以让用户按住顶部实现拖动操作 实现&#xff1a;在Ant Design的Modal框的基础上&#xff0c;在title中添加一个onMouseDown去记录拖拽的坐标&#xff0c;然后将其赋值给Modal的style属性 代码部分&#xff…

【JavaEE进阶】 @Transactional详解

文章目录 &#x1f343;前言&#x1f332;rollbackFor&#xff08;异常回滚属性&#xff09;&#x1f384;事务隔离级别&#x1f6a9;MySQL事务隔离级别&#x1f6a9;Spring事务隔离级别 &#x1f38b;Spring事务传播机制&#x1f6a9;什么是事务传播机制&#x1f6a9;事务有哪…

【SpringMVC】RESTFul风格设计和实战 第三期

文章目录 概述一、 RESTFul风格简介二、 RESTFul风格特点三、 RESTFul风格设计规范3.1 HTTP协议请求方式要求3.2 URL路径风格要求 实战一、需求分析二、RESTFul风格接口设计三、后台接口实现总结模糊查询 有范围 返回多数据用户 添加 与 更新 请求参数接收数据显示用户详情/删除…

进腾讯工作一个月,我想辞职了......

前几天&#xff0c;我在网上看到一个微博。 一个应届的校招生&#xff0c;目前入职腾讯&#xff0c;工作了一个月。这一个月给他的感受是大量的写测试用例&#xff0c;自己写测试用例的能力熟练了不少&#xff0c;测试技能倒是没有多大的提高&#xff0c;真正需要技术的工作却…

软考71-上午题-【面向对象技术2-UML】-UML中的图2

一、用例图 上午题&#xff0c;考的少&#xff1b;下午题&#xff0c;考的多。 1-1、用例图的定义 用例图展现了一组用例、参与者以及它们之间的关系。 用例图用于对系统的静态用例图进行建模。 可以用下列两种方式来使用用例图&#xff1a; 1、对系统的语境建模&#xff1b…

ChatGPT 升级出现「我们未能验证您的支付方式/we are unable to authenticate」怎么办?

ChatGPT 升级出现「我们未能验证您的支付方式/we are unable to authenticate」怎么办&#xff1f; 在订阅 ChatGPT Plus 时&#xff0c;有时候会出现以下报错 &#xff1a; We are unable to authenticate your payment method. 我们未能验证您的支付方式。 出现 unable to a…

低密度奇偶校验码LDPC(十)——LDPC码的密度进化

一、密度进化的概念 二、规则LDPC码的密度进化算法(SPA算法) 算法变量表 VN更新的密度进化 CN更新的密度进化 算法总结 程序仿真 参考文献 [1] 白宝明 孙韶辉 王加庆. 5G 移动通信中的信道编码[M]. 北京: 电子工业出版社, 2018. [2] William E. Ryan, Shu Lin. Channel Co…

优牛企讯司法涉诉维度全解析,了解这些小白也可以变专家!

在商业的海洋中&#xff0c;信息的掌握就如同舵手对风向的了解。每一条信息都可能成为引领航船前行的关键&#xff0c;尤其是在法律风险的管理上&#xff0c;准确而及时的信息更是企业稳健航行的保障。 优牛企讯&#xff0c;一款专业的企业司法涉诉监控查询工具&#xff0c;它…

SpringMVC03、HelloSpring

3、HelloSpring 3.1、配置版 新建一个Moudle &#xff0c; springmvc-02-hello &#xff0c; 添加web的支持&#xff01; 确定导入了SpringMVC 的依赖&#xff01; 配置web.xml &#xff0c; 注册DispatcherServlet <?xml version"1.0" encoding"UTF-8…

微调模型(Machine Learning 研习之十二)

现在正处于百模乱战的时期&#xff0c;对于模型微调&#xff0c;想必您是有所了解了&#xff0c;毕竟国外的大语言模型一开源&#xff0c;国内便纷纷基于该模型进行微调&#xff0c;从而开始宣称领先于某某、超越了谁。可到头来&#xff0c;却让人发现他们套壳了国外大语言模型…

Linux Ubuntu 部署SVN

最近需要在ubuntu server上部署一个svn&#xff0c;记录 不需要特定版本 如果不需要特定版本&#xff0c;这样安装就是最简单的 sudo apt update然后开始安装 sudo apt install subversion等到安装完成之后执行查看版本命令&#xff0c;如果正常输出那就没问题了 svnadmin …

C++默认参数详解

目录 缺省参数/默认参数概念&#xff1a; 缺省参数的应用场景&#xff1a; 函数定义定义分离时的默认参数&#xff1a; 简单的编译链接&#xff1a; 缺省参数/默认参数概念&#xff1a; 缺省参数/默认参数&#xff1a;通俗点讲就是形参给的那个某个值 func(int a 0); //例…