数据结构与算法 | 第二章:线性表

news2024/11/17 15:37:58

本文参考网课为 数据结构与算法 1 第二章线性表,主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。

本文使用IDE为 Clion,开发环境 C++14

更新:2023 / 10 / 22


数据结构与算法 | 第二章:线性表

  • 线性表总览
    • 线性结构
      • 概念
      • 特点
      • 分类
        • 复杂程度
        • 访问方式
        • 操作方式
          • 队列
    • 概念
      • 逻辑结构
      • 存储结构
        • 顺序表
        • 链表
      • 运算
  • 线性表
    • 顺序表
      • 概念
      • 运算
        • 插入元素
        • 删除元素
    • 链表
      • 概念
      • 分类
        • 单链表
          • 结点
          • 查找
          • 插入
          • 删除
          • 运算分析
        • 双链表
          • 结点
          • 插入
          • 删除
        • 循环链表
      • 链表的边界条件
    • 顺序表与链表的比较
    • 示例
  • 参考链接


线性表总览

线性结构

概念

线性表 可以用二元组 B = (K,R),K = {a0,a1,…,an-1},R = {r} 来表示:

  • 有一个唯一的开始节点。开始节点是没有前驱的,有一个唯一的直接后继;
  • 有一个唯一的终止节点。终止节点是没有后继的,有一个唯一的直接前驱;
  • 除了开始节点、终止节点以外的节点皆称为内部节点。每一个内部节点都有且仅有一个唯一的直接前驱和直接后继。
    <ai,ai+1>,ai 是 ai+1 的前驱,ai+1 是 ai 的后继。

前驱、后继关系是具有反对称性和传递性的。

反对称性是指<ai,ai+1> 成立,但是,<ai+1,ai> 不成立

传递性是指<ai,aj>,<aj,ak> 则 <ai,ak>


特点

线性结构 的特点:

  • 均匀性
    虽然不同 线性表 的数据元素可以是各种各样的,但对于同一线性表的各数据元素必定具有相同的数据类型和长度
  • 有序性
    各数据元素在 线性表 中都有自己的位置,且数据元素之间的相对位置是线性的。

分类

在这里插入图片描述

复杂程度

按复杂程度划分:

  • 简单
    线性表队列散列表
  • 高级
    广义表多维数组文件

访问方式

按访问方式划分:

  • 直接访问型( direct access
    根据元素的下标即可直接访问到元素
  • 顺序访问型( sequential access
    必须在表内挨个查询
  • 目录索引型( directory access
    通过目录索引查找目标元素

操作方式

按操作划分:

  • 线性表
    所有 表目 都是同一类型结点的 线性表
    不限制操作形式
    根据存储的不同分为 顺序表链表
  • LIFOLast in First Out
    插入删除 操作都限制在表的同一端进行
  • 队列FIFOFirst In First Out
    插入 在表的一端,删除 在另一端

在这里插入图片描述
在这里插入图片描述
k0 最先进入栈,最后出栈;

ki+1 最后进入栈,最先出栈。

先进后出,后进先出。


队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
先进先出,后进后出。


概念

线性表,简称 ,是零个或多个元素的有穷序列,通常可以表示成 k0,…,k1,…,kn-1(n>=1)。

  • 表目
    线性表 的元素(可包含多个数据项)
  • 索引
    i称为 表目 ki线性表 的位置 索引下标
  • 表的长度
    线性表 中所含元素的个数 n
  • 空表
    长度为0的 线性表n=0

线性表 的特点有操作灵活(长度可增长可缩短)、数据规模小(易存储和运算)。

定义1个 线性表 可以从3个方面着手:逻辑结构存储结构运算。如果2个数据结构从这3方面中有任何1个方面存在不同,即不同的数据结构。


逻辑结构

在这里插入图片描述
逻辑结构 的主要属性包括:

  • 线性表长度
    存储结构 和数据规模相关
  • 表头( head )、表尾( tail
  • 当前位置( current position

存储结构

根据 存储结构 的不同,线性表 可分为 顺序表链表


顺序表

顺序表 将元素按索引值从小到大存放在一片相邻的连续区域。结构紧凑,存储密度为1。其在物理结构上的关系也表达了相应的逻辑关系。


链表

链表 通过指针链接的关系来表达各个元素在逻辑上的关系。因为指针的存在,链表 需要额外的存储空间,即指针开销,因此,链表 的存储效率不如 顺序表

链表 分为 单链表双链表循环链表


运算

线性表 的运算包括以下几个方面:

  1. 创建和清除
  • 建立 线性表
  • 清除 线性表
  1. 增删改
  • 插入新元素
  • 删除某元素
  • 修改某元素
  • 排序
  • 检索

线性表

根据 存储结构 的不同,线性表 可分为 顺序表链表


顺序表

概念

顺序表,也称 向量,采用定长的一维数组存储结构实现的。

顺序表 的主要特点有:

  • 元素的类型相同;
  • 元素顺序地存储在连续存储空间中,每一个元素有唯一的索引值;
  • 使用常数作为向量长度
  • 数组存储
  • 通过元素下标可快速访问目标元素,线性表 中的任意元素都可以随机存取

元素地址计算如下所示:
Loc(ki) = Lock(k0) + c * i, c = sizeof(ELEM)

第i个元素地址 = 起始地址 + 元素存储长度 * 第i个元素

在这里插入图片描述


顺序表 的类定义:

class arrList: public List<T>{          // 顺序表,向量
private:                                // 线性表的取值类型和取值空间
    T * aList;                          // 私有变量,存储顺序表的实例
    int maxSize;                        // 私有变量,顺序表实例的当前长度
    int curLen;                         // 私有变量,顺序表实例的当前长度
    int position;                       // 私有变量,当前处理位置
public:
    arrList(const int size){            // 创建新表,设置表实例的最大长度
        maxSize = size; aList = new T[maxSize];
        curLen = position = 0;
    }
    ~arrList(){                         // 析构函数,用于消除该表实例
        delete [] aList;
    }
};
void clear(){                             // 返回当前实际长度
    delete [] aList; curLen = position = 0;
        aList = new T[maxSize];
}
int length();                             // 返回当前实际长度
bool append(cost T value);                // 在表尾添加元素V
bool insert(const int p, const T valule); // 插入元素
bool delete(const int p);                 // 删除位置p上元素
bool setValue(const int p, const T value);// 设元素值
bool getValue(const int p, T&value);      // 返回元素
bool getPos(int &p, const T value);       // 查找元素

运算

顺序表 进行插入、删除运算的算法分析:

  • 表中元素的移动
    • 插入:移动 n-i
    • 删除:移动 n-i-1
  • 表中每个位置被插入和删除的概率不同或相同
    • i 的位置插入和删除的概率分别是 pi 和 pi
      • 插入的平均移动次数

        在这里插入图片描述
      • 删除的平均移动次数

        在这里插入图片描述
    • i 的位置插入和删除的概率相同,即 pi = 1/(n+1),pi’ = 1/n
      时间代价为 O(n)
      • 插入的平均移动次数

        在这里插入图片描述
      • 删除的平均移动次数

        在这里插入图片描述

插入元素

在这里插入图片描述

template <class T> bool arrList<T> :: insert(const int p, const T value){       // 设元素的类型为T,aList是存储顺序表的数组
                                                                                // p是新元素value的插入位置,如果插入成功则返回true,否则则返回false;
    int i;
    if (curLen >= maxSize){                                                     // 检查顺序表是否溢出
        cout << "The list is overflow" << endl; return false;
    }
    if (p<0 || p>curLen){                                                       // 检查插入位置是否合法
        cout << "Insertion point is illegal" << endl; return false;
    }
    for (i = curLen; i>p; i--)
        aList[i] = aList[i-1];                                                  // 从表尾curLen-1起往右移动直到p
    aList[p] = value;                                                           // 位置p处插入新元素
    curLen++;                                                                   // 表的实际长度增1
    return true;
}

删除元素

在这里插入图片描述

template <class T>                              // 设元素的类型为T;
bool arrList<T> :: delete(const int p){         // aList是存储顺序表的数组;
                                                // p为即将删除元素的位置。删除成功则返回true,否则则返回false;
    int i;
    if (curLen <= 0){                           // 检查顺序表是否为空
        count << "No element to delete \n" << endl;
        return false;
    }
    if (p<0 || p>curLen-1){                     // 检查删除位置是否合法
        count << "deletion is illegal\n" << endl;
        return false;
    }
    for (i=p; i<curLen-1;i++)
        aList[i] = aList[i+1];                  // 从位置p开始每个元素左移直到curLen
    curLen--;                                   // 表的实际长度减1
    return true;
}

链表

概念

链表 是通过指针把一串存储结点链接成一个链。存储结点由两部分组成,数据域指针域(后继地址)。

链表 根据链接方式和指针多少可以分为 单链双链循环链

  • 单链
    在这里插入图片描述
  • 双链
    在这里插入图片描述
  • 循环链
    在这里插入图片描述

分类

单链表

一个简单的 单链表

  • 整个 单链表head
  • 第一个结点:head
  • 空表判断:head == NULL
  • 当前结点 a1curr

在这里插入图片描述

一个带头节点的 单链表

  • 整个 单链表head
  • 第一个结点:head -> nexthead != NULL
  • 空表判断:head -> next == NULL
  • 当前结点 a1fence -> nextcurr 隐含 )

在这里插入图片描述

结点

单链表 的结点类型:

template <class T> class Link{
public:
    T data;                             // 用于保存结点元素的内容
    Link<T> * next;                     // *next, 指向后继结点的指针

    Link(const T info, const Link<T>* nextValue = NULL){
        data = info;
        next = nextValue;
    }
    Link(const Link<T>* nextValue) {
        next = nextValue;
    }
};

查找
template <class T>                      // 线性表的元素类型为T
Link<T> *linkList <T>::setPos(int i){
    int count = 0;
    if (i == -1)                        // i为-1则定位到头结点
        return head;
    Link<T> *p = head -> next;          // 循环定位。若i为0则定位到第1个结点
    while (p != NULL && count < i){
        p = p -> next;                  // 指向第i结点,i=0,1,..., 当链表中结点数小于i时返回NULL
        count ++;
    };
    return p;
}

插入

在这里插入图片描述
分为以下几个步骤:

  • 创建新结点
  • 新结点指向右边的结点
  • 左边结点指向新结点
template <class T>                                      // 线性表的元素类型为T
bool linkList<T>::insert(const int i, const T value){   // 将value插入第i个结点
    Link<T> *p, *q;                                     // 假设p和q 2个结点
    if ((p = setPos(i-1)) == NULL){                     // 设p是第i个结点的前驱结点;如果p是空的,则为非法插入点,返回false
        cout << "非法插入点" << endl;
        return false;
    }
    q = new link<T>(value, p -> next);                  // 设新结点q,q的值为value,q是p的后继结点
    p -> next = q;
    if (p == tail)                                      // 如果p是尾结点
        tail = q;                                       // 设尾结点是q
    return true;
}

删除

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分为以下几个步骤:

  • 用 p 指向元素 x 的结点的前驱结点
  • 删除元素为 x 的结点
  • 释放 x 占据的空间
template <class T>                              // 线性表的元素类型为T
bool linkList<T>::delete((const int i)){        // 删除第i个结点
    Link<T> *p, *q;                             // 假设p和q 2个结点
    if ((p = setPos(i-1))==NULL || p==tail){    // 假设p是第i个结点的前驱结点;如果p是NULL或者尾结点,返回false
        count << "非法结点" << endl;
        return false;
    }
    q = p -> next                               // q是p的后继结点
    if (q == tail){                             // 如果q是尾结点,则p的next指向为NULL,因为p的next是第i个结点而第i个结点会被删除
        tail = p;
        p -> next = NULL;
    }
    else                                        // 如果q不是尾结点,则p的next指向为q的next,因为p的next是第i个结点 即q 而第i个结点会被删除
        p -> next = q -> next
    delete q;
    return true;
}

运算分析

单链表 中,对一个结点操作,往往必须先从第一个点开始找到目标结点,即用一个指针指向它:

p = head;
while (没有到达) p = p -> next;

单链表 的时间复杂度 O(n)

  • 定位 O(n)
  • 插入 O(n) + O(1)
  • 删除 O(n) + O(1)

双链表

为弥补 单链表 的不足而产生 双链表。因为 单链表next 字段仅仅指向后继结点,而不能有效地找到前驱结点。反之亦然。因此,双链表 相比于 单链表,增加一个指向前驱的指针。

在这里插入图片描述

结点

双链表 的结点类型:

template <class T> class Link{
public:
    T data;                                                                 // 设T结点,用于保存结点元素的内容
    Link<T> * next;                                                         // 用于指向后继结点的指针
    Link<T> * prev;                                                         // 指向前驱结点的指针
    Link(const T info, Link<T>* preValue=NULL, Link<T>* nextValue=NULL){    // 给定值和前后指针的构造函数
        data = info;
        next = nextValue;
        prev = preValue;
    }
    Link(Link<T>* preValue=NULL, Link<T>* nextValue=NULL){                  // 给定前后指针的构造函数
        next = nextValue;
        prev = preValue;
    }
};

插入

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


删除

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

循环链表

单链表 或者 双链表 的头尾结点链接起来,就是一个 循环链表
相比于单纯的 单链表双链表,从 循环链表 的任一结点出发,都能访问到表中其它结点。不增加额外存储花销,却给不少操作带来了方便。


链表的边界条件

  • 针对特殊结点的处理
    • 针对头指针
    • 针对尾指针
      • 非循环链表尾结点 tail 的指针域保持为 NULL
      • 循环链表尾结点 tail 的指针回指头结点 head
  • 针对链表的处理
    • 空链表的特殊处理
    • 插入或删除结点时指针勾链的顺序
    • 指针移动的正确性
      • 插入
      • 查找或遍历

顺序表与链表的比较

比较项顺序表链表
存储开销1. 不需要使用指针,即不需要额外的存储空间开销来存放指针域。

2. 如果整个数组元素很满,则没有结构性存储开销。
1. 每个元素都存在指针,即需要额外的存储空间开销来存放指针域。

2. 存储利用指针,动态地按照需要为表中新的元素分配存储空间。
时间代价1. 插入、删除元素时间代价为 O(n)

2. 查找元素时间代价为常数时间。
1. 插入、删除元素时间代价为 O(1)

2. 查找元素时间代价为 O(n)
访问1. 对表内元素的读访问十分简洁便利
灵活性1. 需要预先申请固定长度的连续空间1. 不需要预先申请内存空间,表的长度可以动态变化。可以较为方便地插入、删除内部元素。
存储密度n 表示 线性表 中当前元素的数目。
P 表示指针的存储单元大小(通常为 4 bytes) 。
E 表示数据元素的存储单元大小。
D 表示在数据中存储的 线性表 元素的最大数目。

顺序表 的空间需求为 DE
n 越大,顺序表 的空间效率就更高。
n 表示 线性表 中当前元素的数目。
P 表示指针的存储单元大小(通常为 4 bytes) 。
E 表示数据元素的存储单元大小。
D 表示在数据中存储的 线性表 元素的最大数目。

链表 的空间需求为 n(P+E)
应用适合存储静态数据。

适合总结点数目大概可以估计,而不是无法预估需要预先申请多大内存的场景。

适合结点比较稳定(插入、删除少)的场景。
适合存储动态数据。

适合结点数目无法预知。



适合结点动态变化(插入、删除多)的场景。

示例

顺序表链表 来表达一元多项式:

  • 假设一元多项式为 Pn(x) = p0 + p1x + p2x2 + + pnxn
    使用 线性表 表示,即只存系数(第 i 个元素存 xi 的系数)

    在这里插入图片描述
    适合数据密集的情况

  • 假设一元多项式为 p(x) = 1 + 2x10000 + 4x40000
    使用 线性表 表示,即

    在这里插入图片描述
    适合数据稀疏的情况。
    能够较快根据指针域和结点的值恢复该多项式。


参考链接


  1. 数据结构与算法 ↩︎

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

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

相关文章

大数据技术学习笔记(三)—— Hadoop 的运行模式

目录 1 本地模式2 伪分布式模式3 完全分布式模式3.1 准备3台客户机3.2 同步分发内容3.2.1 分发命令3.2.2 执行分发操作 3.3 集群配置3.3.1 集群部署规划3.3.2 配置文件说明3.3.3 修改配置文件3.3.4 分发配置信息 3.4 SSH无密登录配置3.4.1 配置ssh3.4.2 无密钥配置 3.5 单点启动…

人工智能(6):机器学习基础环境安装与使用

1 库的安装 整个机器学习基础阶段会用到Matplotlib、Numpy、Pandas等库&#xff0c;为了统一版本号在环境中使用&#xff0c;将所有的库及其版本放到了文件requirements.txt当中&#xff0c;然后统一安装 新建一个用于人工智能环境的虚拟环境 mkvirtualenv ai matplotlib3.8…

Mybatis应用场景之动态传参、两字段查询、用户存在性的判断

目录 一、动态传参 1、场景描述 2、实现过程 3、代码测试 二、两字段查询 1、场景描述 2、实现过程 3、代码测试 4、注意点 三、用户存在性的判断 1、场景描述 2、实现过程 3、代码测试 一、动态传参 1、场景描述 在进行数据库查询的时候&#xff0c;需要动态传入…

【源码解析】Spring源码解读-bean的加载

Spring的整体流程其实就是通过配置 xml、注解将自定义bean类信息进行配置&#xff0c;然后通过BeanDefinitionReader读取配置信息&#xff0c;由Dom转换成xml解析成Docment。在通过加载的配置信息进行初始化Bean对象&#xff0c;然后在对象的前后进行处理&#xff0c;也就是不同…

2023-10-22

一、总线通信协议简介 总线是计算机系统中负责连接各个硬件的通信线路&#xff0c;它可以传输数据、地址和控制信号。通信协议是指双方实体完成通信所遵循的规则。总线通信协议是一种规定总线设备之间数据通信方式和方法的规则&#xff0c;它包括数据的通信方式、速率、格式、…

python爬虫之js逆向入门:常用加密算法的逆向和实践

一、强大的Chrome DevTools Chrome DevTools是一组内置于Google Chrome浏览器中的开发者工具&#xff0c;用于帮助开发人员调试、分析和优化Web应用程序。它提供了一系列功能强大的工具&#xff0c;用于检查和编辑HTML、CSS和JavaScript代码&#xff0c;监视网络请求、性能分析…

redis怎么设计一个高性能hash表

问题 redis 怎么解决的hash冲突问题 &#xff1f;redis 对于扩容rehash有什么优秀的设计&#xff1f; hash 目标是解决hash冲突&#xff0c;那什么是hash冲突呢&#xff1f; 实际上&#xff0c;一个最简单的 Hash 表就是一个数组&#xff0c;数组里的每个元素是一个哈希桶&…

ida81输入密码验证算法分析以及破解思路

本文分析了ida81对输入密码的验证流程&#xff0c;分别对输入密码到生成解密密钥、密码素材的生成过程以及文件数据的加密过程这三个流程进行分析&#xff0c;并尝试找一些可利用的破绽。很遗憾&#xff0c;由于水平有限&#xff0c;目前也只是有个思路未能完全实现&#xff0c…

查看当前cmake版本支持哪些版本的Visual Studio

不同版本的的cmake对Visual Studio的版本支持不同&#xff0c;以下图示展示了如何查看当前安装的cmake支持哪些版本的Visual Studio。 1.打开cmake-gui 2.查看cmake支持哪些版本的Visual Studio

28. 使用 k8e 玩转 kube-vip with Cilium‘s Egress Gateway 特性

因为在私有云环境下,我们需要保障集群服务 APIServer地址的高可用,所以提供的方案就是使用一个 VIP 让 API Server 的流量可以负载均衡的流入集群。另外,kube-vip 还支持 Service LB,方便SVC 服务的负载均衡,结合 cilium Egress Gateway 特性可以做到集群内的容器对外访问…

canvas绘制刮涂层抽奖效果

实现的效果&#xff1a;主要用到画布设置图层覆盖效果globalCompositeOperation属性 实现的源代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"…

canvas常用的几种重叠绘图设置globalCompositeOperation

globalCompositeOperation描述了2个图形交叉的时候是什么样子&#xff0c;它的值有很多&#xff0c;这里就盗一张很经典的图&#xff1a; 我们来看一个source-in的例子吧&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset&q…

论文-分布式-并发控制-Lamport逻辑时钟

目录 前言 逻辑时钟讲解 算法类比为面包店内取号 Lamport算法的时间戳原理 Lamport算法的5个原则 举例说明 算法实现 参考文献 前言 在并发系统中&#xff0c;同步与互斥是实现资源共享的关键Lamport面包店算法作为一种经典的解决并发问题的算法&#xff0c;它的实现原…

VTM/VVC 编译与测试-- YUV与RGB空间转换

环境配置:ubuntu 18.04 一、VVC测试 软件下载: 官网下载 VVC:http://jvet.hhi.fraunhofer.de/ 这里可以选择版本。 编译工具: 在开始编译前,需要电脑的环境中中有gcc、g++、cmake、make这四个工具。 sudo apt-get install gcc g++ sudo apt-get install cmake sudo …

Android微信逆向--实现发朋友圈动态

Android微信逆向--实现发朋友圈动态 0x0 前言# 最近一直在研究Windows逆向的东西&#xff0c;想着快要把Android给遗忘了。所以就想利用工作之余来研究Android相关的技术&#xff0c;来保持对Android热情。调用微信代码来发送朋友圈动态一直是自己想实现的东西&#xff0c;研…

Internet Download Manager 逆向分析

写在前面 文章仅供学习&#xff0c;切勿用于商业用途&#xff0c;出于版权原因&#xff0c;文章不提供资源下载。 论坛上较早之前已经有前辈对IDM的序列号算法进行过逆向分析 以及最近有师傅尝试对本篇文章对象相同的版本进行了逆向分析&#xff0c;但是比较遗憾的是该文章并…

瑞萨e2studio(26)----SPI驱动TFT-LCD屏

瑞萨e2studio.26--SPI驱动TFT-LCD屏 概述视频教学csdn课程样品申请完整代码下载屏幕接口接线方式新建工程工程模板保存工程路径芯片配置工程模板选择时钟配置开始SPI配置SPI属性配置IO配置头文件定义回调函数lcd_init.clcd.c设置区域颜色显示字符串显示汉字显示图片结果演示 概…

点击查看详情 | 网页版微信客户管理系统如何操作试用?

微信作为我们日常生活中最常用的社交应用之一&#xff0c;早已成为我们与朋友、家人和同事保持联系的重要工具&#xff0c;也是营销引流的重要平台。 通过微信营销&#xff0c;可以比较精准定向亲近用户。而微信的功能并没有很能满足做微信营销的人群&#xff0c;所以我们需要借…

S5PV210裸机(五):定时器

本文主要探讨210定时器相关知识&#xff0c;210定时器主要包含PWN定时器&#xff0c;系统定时器&#xff0c;看门狗&#xff0c;RTC。 PWM定时器 210有5个PWM定时器,timer0、1、2、3通过对应PIO产生PWM波形信号并输出,timer4没有GPIO只产生内部定时器中断 PWM…

Python 安装CSF(布料模拟滤波)的环境配置

一、环境配置 1.1 下载源码: Github下载CSF库源码 1.2 解压文件如下: 二、安装CSF库 2.1在解压文件中找到python文件夹所在目录 2.2 输入cmd并回车,来打开终端窗口 2.3激活虚拟环境 通过: activate +你的虚拟环境名称。来激活安装CSF库的虚拟环境。【不执行此