2.线性表——数据结构学习

news2025/1/13 13:21:34

零个或多个数据元素的有限序列。

  • 有序 (每个元素有且只有一个前驱与后继) -> 一对一的关系
    • 前驱 (第一个元素无前驱)
    • 后继(最后一个元素无后继)
  • 有限

线性表元素个数:n (n ≥ \geq 0),当n=0时,称为空表。
非空表中的每个数据元素都有一个确定的位置:位序

在比较复杂的线性表中,一个数据元素可以由若干个数据项组成。


线性表的ADT

请添加图片描述
当传递参数给函数时,它在函数内是否会被改动决定了使用什么参数形式。

  • 需要被改动,则需要传递指向这个参数的指针
  • 不用被改动,可以直接传递这个参数

顺序存储结构

线性表的每个元素类型都相同!

数组长度与线性表长度区别:

  • 数组的长度是存放线性表的存储空间的长度
  • 线性表的长度指数据元素的个数
    任意时刻,线性表的长度都是小于等于数组的长度!

一般高级语言可以实现动态分配数组。

数组的下标是从0至n-1,用数组存储顺序表意味着要分配固定长度的数组空间。

获取元素

// 获取线性表中第i个位置的元素,并用e返回  
int GetElem(SqList L, int i, ElemType *e){  
    bool ret = true;  
    // 若线性表长度为0 或 位置值小于1 或 线性表长度小于位置i则返回错误  
    if (L.length == 0 || i < 1 || L.length < i){  
        ret = false;  
    } else{  
        *e = L.data[i - 1];  
    }  
  
    return ret;  
}

插入操作

  1. 如果插入位置不合理,抛出异常
  2. 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
  3. 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
  4. 将要插入元素填入位置i,表长加 1
// 在位置i处插入元素e  
int ListInsert(SqList *L, int i, ElemType e){  
    bool ret = true;  
    // 线性表满时  
    // 当i插入位置低于第一位 或 当前插入位置超出线性表长度
    if ((L->length == MAXSIZE) || (i < 1 || L->length + 1 < i)){  
        ret = false;  
    } else{  
        // 将要插入位置的所有元素向后移1位  
        for (int j = L->length - 1; i - 1 <= j; j--) {  
            L->data[j + 1] = L->data[j];  
        }  
  
        L->data[i - 1] = e;  
        L->length++;  
    }  
  
    return ret;  
}

删除操作

// 删除位置i上的元素,并用e返回  
int ListDelete(SqList *L, int i, ElemType *e){  
    bool ret = true;  
    // 若删除位置不合理  
    if ((L->length == 0) || (i < 1 || L->length < i)){  
        ret = false;  
    } else{  
        *e = L->data[i - 1];  
        // length: 5 0,1,2,3,4  
        for (int j = i; j < L->length; j++) {  
            // 删除i位置的元素,将其余后面的元素向前移   
L->data[j] = L->data[j + 1];  
        }  
        L->length--;  
    }  
  
    return ret;  
}

链式存储结构

单链表

线性表的链式存储结构:n个节点链结成一个链表 -> 单链表
特点:是用一组任意的存储单元存储线性表的数据元素,可以连续或不连续。

顺序存储结构中,每个数据元素只需要存储数据元素信息
链式存储结构中,除了存储数据元素信息,还需要存储后继元素的存储地址

  • 数据域:存储数据元素信息的域
  • 指针域:把存储直接后继位置的域
    指针域中存储的信息称作指针,这两部分信息组成数据元素 a i a_{i} ai的存储映像,称结点

头指针:链表中第一个节点存储位置
在这里插入图片描述

头节点:单链表的第一个节点前附设一个节点
请添加图片描述

头指针与头节点的异同:

  • 头指针
    • 指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
    • 头指针具有标志作用,所以常用头指针冠以链表的名字
    • 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
  • 头节点
    • 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)
    • 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了
    • 头结点不一定是链表必需要素

代码描述

若线性表为空,则头节点的指针域为"NULL"
表示的线性表中的数据元素及数据元素之间的逻辑关系在这里插入图片描述在这里插入图片描述

typedef int ElemType;  
  
typedef struct Node{  
    ElemType data;  
    struct Node *next;  
}Node;  
  
typedef Node *LinkList;

节点由存放数据元素的数据域和存放后继节点的指针域组成在这里插入图片描述

读取

在单链表中,由于第 i 个元素到底在哪没办法一开始就知道,必须得从头开始找。

思路:

  • 声明一个指针p只想链表第一个节点,初始化j从1开始
  • 当 j < i 时,就遍历链表,让p的指针向后移动,不断指向下一个节点,j累加1
  • 若链表末尾为空,则说明第i个节点不存在
  • 否则查找成功,返回节点p的数据
// 查找第i个元素,e返回数据值  
int GetElem(LinkList L, int i, ElemType *e){  
    int j = 1;  
    bool ret = true;  
    // 声明p节点  
    LinkList p;  
    p = L->next;  
      
    // 指针p不为空,或j不等于i时  
    while (p && j < i){  
        p = p->next;  
        ++j;  
    }  
      
    if (!p || i < j){  
        ret = false;  
    } else{  
        *e = p->data;  
    }  
      
    return ret;  
}

插入

只需要让s->next和p->next的指针做一点改变即可。

s-next = p->next;
p->next = s;

表头与表尾的特殊情况是相同的。在这里插入图片描述

思路:

  1. 声明另一指针(如p)指向链表头节点,初始化j从1开始
  2. 当j<i时就遍历链表,让p的指针向后移动,不断移向下一节点,j累加1
  3. 若到链表末尾p为空,则说明第i个节点不存在
  4. 否则查找成功,在系统中生成一个空节点(如s)
  5. 将数据元素e赋值给s->data
  6. 单链表的插入标准语句:s->next = p->next; p->next = s;
// 插入  
int Insert(LinkList *L, int i, ElemType e){  
    LinkList p, s;  
    p = *L;  
    int ret = true, j = 1;  
  
    // 当j<i时就遍历链表,让p的指针向后移动,不断移向下一节点,j累加1  
    while (p && j < i){  
        p = p->next;  
        ++j;  
    }  
  
    if (!p || i < j) {  
        ret = false;  
    } else{  
        s = (LinkList) malloc(sizeof(Node));  
        s->data = e;  
        s->next = p->next;  
        p->next = s;  
    }  
    return ret;  
}

删除

其实就是将前继节点的指针绕过,指向它的后继节点即可。

思路:

  1. 声明一指针 p 指向链表头结点,初始化从 1 开始
  2. 当j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j累加 1
  3. 若到链表末尾 p 为空,则说明第结点不存在
  4. 否则查找成功,将欲删除的结点 p 一> next 赋值给 q
  5. 单链表的删除标准语句 p->next=q->next
  6. 将 q 结点中的数据赋值给,作为返回
  7. 释放q节点
// 删除  
int Delete(LinkList *L, int i, ElemType *e){  
    LinkList p, q;  
    p = *L;  
  
    int ret = true, j = 1;  
    while (p->next && j < i){  
        p = p->next;  
        ++j;  
    }  
    // 末尾为空的情况,表示当前删除元素为  
    if (!(p->next) || i < j){  
        ret = false;  
    } else{  
        q = (LinkList) malloc(sizeof(Node));  
        q = p->next;  
        p->next = q->next;  
        *e = q->data;  
    }  
  
    return ret;  
}

整表创建

数组的初始化,声明一个类型和大小的数组并赋值的过程。
创建一个单链表的过程就是一个动态生成链表的过程,从“空表”的初始状态起,依次建立元素节点,并逐个插入链表。

思路

  • 声明一指针和计数器
  • 初始化空链表L
  • 让L的头节点的指针指向NULL,建立一个带头节点的单链表
  • 循环

头插法

// 整表创建(头插法)  随机生成n个元素的值  
void CreateListHead(LinkList *L, int n){  
    LinkList p;  
    // 初始化随机种子  
    srand(time(0));  
    *L = (LinkList) malloc(sizeof(Node));  
    (*L)->next = NULL;  
  
    for (int i = 0; i < n; i++) {  
        p = (LinkList) malloc(sizeof(Node));  
        p->data = rand() % 100 + 1; // 随机生成100以内的数字  
        p->next = (*L)->next;  
        // 插入到表头  
        (*L)->next = p;  
    }  
}

尾插法

// 整表创建(尾插法)  
void CreateLinkTail(LinkList *L, int n){  
    LinkList p, r;  
    srand(time(0));  
    *L = (LinkList) malloc(sizeof(Node));   // 单链表  
    r = *L;    // 指向尾部节点 r: 指向尾节点的变量, 不断变化 L则随着循环增长为一个多节点的链表  
  
    for (int i = 0; i < n; i++) {  
        p = (Node *) malloc(sizeof(Node));  
        p->data = rand() % 100 + 1;  
        r->next = p;    // 将表尾终端节点的指针指向新节点  
        r = p;  // 将当前的新节点定义为表尾终端节点  
    }  
  r->next = NULL;
}

整表删除

思路

  • 声明指针p和q
  • 将第一个节点赋给p
  • 循环
// 整表删除  
int ClearList(LinkList *L){  
    bool ret = true;  
    LinkList p, q;  
    p = (*L)->next;  
      
    while (p){  
        q = p->next;  
        free(p);  
        p = q;  
    }  
  
    (*L)->next = NULL;  
  
    return ret;  
}

在这里插入图片描述

  • 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构
  • 当线性表中的元素个数变化较大或根本不知道多大时,最好用单链表结构

静态链表

用数组表述的链表

数据域data存放数据元素; cur相当于单链表中的next指针,存放该元素的后继在数组中的下标。
第一个与最后一个元素不存数据。
备用链表:未被使用的数据元素
在这里插入图片描述

// 初始化   
void InitList(StaticLinkList space){  
    // 从下标 0 至 MAXSIZE - 1 的游标设置为 i + 1    for (int i = 0; i < MAXSIZE; i++) {  
        space[i].cur = i + 1;  
    }  
      
    // 最后一个下标的游标设置为0,表示数组为空  
    space[MAXSIZE].cur = 0;  
}

在这里插入图片描述

插入

如何用静态模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。

辨明数组中哪些分量未被使用,解决办法是将所有未被使用过的及已被删除的分量用游标链成一个备用链表,每当插入时,可以从备用链表上取得第一个结点作为待插入的新结点。

// 插入  
int ListInsert(StaticLinkList L, int i, ElemType e){  
    int j, k;  
    bool ret = true;  
    // 获得空闲分量的下标  
    j = Malloc_SSL(L);  
    // 获得最后一个元素的下标  
    k = MAXSIZE - 1;  
  
    if ((i < 1 || ListLength(L) + 1 < i) || !(j)){  
        ret = false;  
    }  
  
    switch (ret) {  
        case false:  
            break;  
        case true:  
            L[j].data = e;  
            // 找到第i个元素之前的位置  
            for (int l = 1; l < i; l++) {  
                k = L[k].cur;  
            }  
            // 将第i个元素之间的cur赋值给新元素的cur  
            L[j].cur = L[k].cur;  
            // 将新元素的下标赋值给之前元素的cur  
            L[k].cur = j;  
            break;  
    }  
  
    return ret;  
}

在这里插入图片描述

在这里插入图片描述

删除

同插入类似

// 删除  
int ListDelete(StaticLinkList L, int i) {  
    bool ret = true;  
    int j, k;  
    // 获得最后一个元素的下标  
    k = MAXSIZE - 1;  
  
    // 下标不在范围  
    if (i < 1 || ListLength(L) < i) {  
        ret = false;  
    }  
  
    switch (ret) {  
        case false:  
            break;  
        case true:  
            for (j = 1; j < i; j++) {  
                k = L[k].cur;  
            }  
              
            j = L[k].cur;  
            L[k].cur = L[j].cur;  
            Free_SSL(L, j);  
            break;  
    }  
  
    return ret;  
}

在这里插入图片描述

静态链表优点:

  • 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中插入和删除操作需要移动大量元素的缺点
    缺点:
  • 没有解决连续存储分配带来的表长难以确定的问题
  • 失去了链式存储结构随机存取的特性

静态链表只是为了给没有指针的高级语言设计的一种实现单链表能力的方法! 尽管不一定用上,但这样的思考方式是非常巧妙的,应该理解其思想,以备不时之需。

循环链表

单向

将单链表中终端节点的指针端由空指针改为指向头节点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表——简称循环链表。

为了使空链表与非空链表处理一致,通常设置一个头节点(并非一定要头节点)!

循环链表与单链表的主要差异就在循环的判断条件上。

  • 单链表
p -> next
  • 循环链表
p -> next不等于头节点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

双向

双向链表是在单链表的每个节点中,再设置一个指向其前驱节点的指针域。

  • 一个指向直接后继
  • 一个指向直接前驱
typedef struct DulNode{  
    ElemType data;  
    struct DulNode *prior;  
    struct DulNode *next;  
}DulNode, *DulLinkList;

既然单链表也可以有循环链表,那么双向链表当然也可以是循环表。

双向链表是单链表中扩展出来的结构,很多操作和单链表都是相同的。

  • 在插入与删除时需要更改两个指针变量
s ->  prior = p;
s -> next = p -> next;
p -> next -> prior = s;
p -> next = s;

在这里插入图片描述

关键在于它们更改的顺序!如何插入理解了,删除也就比较简单了


总结

线性表的两种结构式后面其它数据结构的基础,把它们学明白了对后面的学习有着至关重要的作用!
在这里插入图片描述

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

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

相关文章

D1s芯片启动流程(BROM System)分析

1、D1S芯片介绍 2、BROM介绍 &#xff08;1&#xff09;BROM&#xff08;boot rom&#xff09;&#xff1a;存放启动代码的ROM&#xff0c;该ROM一般在芯片内部集成&#xff0c;是芯片上电执行的最开始代码&#xff1b; &#xff08;2&#xff09;BROM里存放的程序主要功能&…

【Java 进阶篇】JavaScript 介绍及其发展史

JavaScript是一门广泛应用于Web开发的编程语言。它是一种高级的、解释性的脚本语言&#xff0c;主要用于改善用户与Web页面的互动体验。本篇博客将为你详细介绍JavaScript的基础知识、历史背景和它在Web开发中的重要作用。我们还将讨论JavaScript的发展史&#xff0c;从它的起源…

9. 一个SpringBoot项目运行

新手如何运行一个SpringBoot项目 1.SpringBoot项目运行 新创建的SpringBoot项目如何运行 2.启动lombok注解 点击该按钮&#xff0c;启动lombok注解支持 3.展示说明

进阶JAVA篇-Object类与Objects类、包装类的常用API(一)

目录 API 1.0 API概念 2.0 Object类的常用API 2.1 Object 中的 tostring() 方法 表示返回对象的字符串表达形式 2.2 Object 中的 equals(Object o) 方法 &#xff0c;判断两个对象的是否相等 2.2.1深入了解如何重写Object 中的 equals(Object o) 方法 2.2.2 对重写Object 中的…

html css实战之学成在线项目

html css实战之学成在线项目 项目链接&#xff1a;https://download.csdn.net/download/weixin_39451323/88416213 效果图&#xff1a;

4年测试经验,面试却突破不了20K,真是太卷了····

先说一个插曲&#xff1a;上个月我有同学在深圳被裁员了&#xff0c;和我一样都是软件测试&#xff0c;不过他是平安外包&#xff0c;所以整个组都撤了&#xff0c;他工资和我差不多都是14K。 现在IT互联网已经比较寒冬&#xff0c;特别是软件测试&#xff0c;裁员先裁测试&am…

【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Prompt Encoder

论文&#xff1a;Segment Anything   代码&#xff1a;https://github.com/facebookresearch/segment-anything 上一篇&#xff1a;【技术追踪】SAM&#xff08;Segment Anything Model&#xff09;代码解析与结构绘制之Image Encoder 本篇示例依然采用上一篇的狗狗图像运行代…

深度学习笔记之优化算法(八)Adam算法的简单认识

深度学习笔记之优化算法——Adam算法的简单认识 引言回顾&#xff1a;基于Nesterov动量的RMSProp算法Adam算法的简单认识一阶矩、二阶矩修正偏差的功能Adam的算法过程描述Adam示例代码 引言 上一节介绍了基于 Nesterov \text{Nesterov} Nesterov动量与 RMSProp \text{RMSProp}…

文字与视频结合效果

效果展示 CSS 知识点 mix-blend-mode 属性的运用 实现整体页面布局 <section class"sec"><video autoplay muted loop><source src"./video.mp4" type"video/mp4" /></video><h2>Run</h2><!-- 用于切…

【Java 进阶篇】JavaScript 与 HTML 的结合方式

JavaScript是一种广泛应用于Web开发中的脚本语言&#xff0c;它与HTML&#xff08;Hypertext Markup Language&#xff09;结合使用&#xff0c;使开发人员能够创建交互式和动态的网页。在这篇博客中&#xff0c;我们将深入探讨JavaScript与HTML的结合方式&#xff0c;包括如何…

Web知识:markupsafe.escape() 函数的作用

1、markupsafe.escape() 函数是 MarkupSafe 库中的一个函数&#xff0c;它的作用是对字符串进行 HTML 转义&#xff0c;以防止在 HTML 文档中引起意外的解析结果或安全漏洞。 2、在 Web 开发中&#xff0c;如果用户提供的数据直接插入到 HTML 页面中&#xff0c;而没有经过转义…

SpringBoot调用存储过程(入参,返参)(亲测有效!!!)

存储过程和函数是有区别的&#xff01;&#xff01;&#xff01; 创建函数&#xff0c;只是演示&#xff0c;以下函数不完整&#xff01;&#xff01;&#xff01;(只是看P_xxx参数) CREATE OR REPLACE PROCEDURE SP_TICKET_CHECKE_ONLINE_TEST (p_transcode IN OUT VA…

Java反射获取抽象类方法属性问题讲解

Java反射获取抽象类方法属性问题讲解 结论一、案例准备二、测试方法&#xff1a;使用反射获取抽象类私有方法和私有属性具体操作&#xff08;获取私有方法&#xff09;具体操作&#xff08;获取私有属性&#xff09; 结论 Java 通过反射可以获得抽象类的任何修饰符&#xff08…

Vue2 Watch的语法

Watch语法 一、监听普通数据类型&#xff08;1&#xff09;把要监听的msg值看作方法名&#xff0c;来进行监听。&#xff08;2&#xff09;把要监听的msg值看作对象&#xff0c;利用hanler方法来进行监听 二、监听对象&#xff1a;&#xff08;1&#xff09;监听对象需要用到深…

LeetCode【300】最长递增子序列

题目&#xff1a; 思路&#xff1a; 通常来说&#xff0c;子序列不要求连续&#xff0c;而子数组或子字符串必须连续&#xff1b;对于子序列问题&#xff0c;第一种动态规划方法是&#xff0c;定义 dp 数组&#xff0c;其中 dp[i] 表示以 i 结尾的子序列的性质。在处理好每个…

1808_ChibiOS基本的架构介绍

全部学习汇总&#xff1a; GreyZhang/g_ChibiOS: I found a new RTOS called ChibiOS and it seems interesting! (github.com) 简单看了一下ChibiOS的架构介绍&#xff0c;感觉这种OS以及组件非常适合快速构建一个应用。这里做一个简单的资料整理。。 1. 不同于其他的OS&#…

TCP/IP(九)TCP的连接管理(六)TIME_WAIT状态探究

一 TIME_WAIT探究 要明确TIME_WAIT状态在tcp四次挥手的阶段 ① 为什么 TIME_WAIT 等待的时间是 2MSL? 背景&#xff1a; 客户端在收到服务端第三次FIN挥手后,就会进入TIME_WAIT 状态,开启时长为2MSL的定时器1、MSL 是 Maximum Segment Lifetime 报文最大生存时间2、2MSL…

4.(vue3.x+vite)style动态绑定的方式

前端技术社区总目录(订阅之前请先查看该博客) 效果浏览 代码如下: <template><div><div :style="{

改造Vue-admin-template登录

这是是将Vue-admin-template改为登录自己的&#xff0c;拿自己的数据&#xff0c;原作者是gitee花裤衩或者github devServer: {proxy: {/dev-api: {target: http://localhost:8035,changeOrigin: true,pathRewrite: {^/dev-api: }}} }, main.js如下 import Vue from vueimpor…

VMware虚拟机安装Linux教程(图文超详细)

1.安装VMware 官方正版VMware下载地址 https://www.vmware.com/ 双击安装 以上就是VMware在安装时的每一步操作&#xff0c;基本上就是点击 "下一步" 一直进行安装。 2.安装Linux VMware虚拟机安装完毕之后&#xff0c;我们就可以打开VMware&#xff0c;并在上面来…