2.1 线性表的定义

190 阅读5分钟

定义:

线性表的基本定义::

  • 线性表(Linear List)是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n = 0时线性表是一个空表。
  • 若用L命名线性表,则其一般表示为L = (a1, a2, … , ai, ai+1, … , an)。
  • a1是表头元素。
  • an是表尾元素。
  • 除第一个元素外,每个元素有且仅有一个直接前驱。
  • 除最后一个元素外,每个元素有且仅有一个直接后继。

线性表的基本操作:

  • InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
  • DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
  • ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
  • ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
  • LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
  • GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
  • Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
  • PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
  • Empty(L):判空操作。若L为空表,则返回true,否则返回false。
  • 什么时候要传入参数的引用“&”: 对参数的修改结果需要“带回来”,是引用类型而不是值类型

顺序表的基本定义:

用顺序存储的方式实现线性表顺序存储,把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

如果“数组”存满了怎么办:

顺序表的表长刚开始确定后就无法更改(存储空间是静态的),同时如果提前初始化太多的空间而不用,又会造成资源的浪费。可以用指针等方法,或者用vector等stl的数据结构。

顺序表和单链表实现线性表的区别

顺序表:

  • 每个结点中只存放数据元素
  • 优点:可随机存取,存储密度高
  • 缺点:要求大片连续空间,改变容量不方便

单链表:

  • 每个结点除了存放数据元素外,还要存储指向下一个结点的指针
  • 优点:不要求大片连续空间,改变容量方便
  • 缺点:不可随机存取,要耗费一定空间存放指针

循环单链表与单链表的区别:

单链表:

  • 表尾结点的next指针指向NULL
  • 从一个结点出发只能找到后续的各个结点

循环单链表:

  • 表尾结点的next指针指向头结点
  • 从一个结点出发可以找到其他任何一个结点

循环双链表与双链表的区别:

双链表:

  • 表头结点的prior指向NULL
  • 表尾结点的next指向NULL

循环双链表:

  • 表头结点的prior指向表尾结点
  • 表尾结点的next指向头结点

顺序表和链表的比较

逻辑结构:

都属于线性表,都是线性结构

存储结构:

顺序表:

  • 优点:支持随机存取、存储密度高
  • 缺点:大片连续空间分配不方便,改变容量不方便

链表:

  • 优点:离散的小空间分配方便,改变容量方便
  • 缺点:不可随机存取,存储密度低

基本操作:

顺序表:

  • 创建

    • 需要预分配大片连续空间。

    • 若分配空间过小,则之后不方便拓展容量;

    • 若分配空间过大,则浪费内存资源

    • 静态分配:静态数组实现,容量不可改变

    • 动态分配:动态数组(malloc、free)实现,容量可以改变但需要移动大量元素,时间代价高

  • 销毁

    • 修改Length = 0

    • 静态分配:静态数组,系统自动回收空间

    • 动态分配:动态数组(malloc、free),需要手动free

  • 增删

    • 插入/删除元素要将后续元素都后移/前移

    • 时间复杂度O(n),时间开销主要来自移动元素

    • 若数据元素很大,则移动的时间代价很高

    • 按位查找:O(1)

    • 按值查找:O(n)若表内元素有序,可在O(log2n)时间内找到

链表:

  • 创建

    • 只需分配一个头结点(也可以不要头结点,只声明一个头指针),之后方便拓展

  • 销毁

    • 依次删除各个结点(free)

  • 增删

    • 插入/删除元素只需修改指针即可

    • 时间复杂度O(n),时间开销主要来自查找目标元素

    • 查找元素的时间代价更低

    • 按位查找:O(n)
    • 按值查找:O(n)

用哪个:

  • 表长难以预估、经常要增加/删除元素——链表
  • 表长可预估、查询(搜索)操作较多——顺序表

常用的c和c++函数

c:

  • printf("%s", s):用 %s 来输出一个字符串(字符数组)。
  • scanf("%s", &s):用 %s 来读入一个字符串(字符数组)。
  • sscanf(const char *__source, const char *__format, ...):从字符串 __source 里读取变量,比如 sscanf(str,"%d",&a)
  • sprintf(char *__stream, const char *__format, ...):将 __format 字符串里的内容输出到 __stream 中,比如 sprintf(str,"%d",i)
  • strlen(const char *str):返回从 str[0] 开始直到 '\0' 的字符数。注意,未开启 O2 优化时,该操作写在循环条件中复杂度是  的。
  • strcmp(const char *str1, const char *str2):按照字典序比较 str1 str2 若 str1 字典序小返回负值,两者一样返回 0str1 字典序更大则返回正值。请注意,不要简单的认为返回值只有 01-1 三种,在不同平台下的返回值都遵循正负,但并非都是 01-1
  • strcpy(char *str, const char *src): 把 src 中的字符复制到 str 中,str src 均为字符数组头指针,返回值为 str 包含空终止符号 '\0'
  • strncpy(char *str, const char *src, int cnt):复制至多 cnt 个字符到 str 中,若 src 终止而数量未达 cnt 则写入空字符到 str 直至写入总共 cnt 个字符。
  • strcat(char *str1, const char *str2): 将 str2 接到 str1 的结尾,用 *str2 替换 str1 末尾的 '\0' 返回 str1
  • strstr(char *str1, const char *str2):若 str2 是 str1 的子串,则返回 str2 在 str1 的首次出现的地址;如果 str2 不是 str1 的子串,则返回 NULL
  • strchr(const char *str, int c):找到在字符串 str 中第一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL
  • strrchr(const char *str, char c):找到在字符串 str 中最后一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL

c++:

  • 访问函数 data()/c_str() 返回一个 const char* 指针,内容与该 string 相同。
  • 容量函数 size() 返回字符串字符个数。
  • find(ch, start = 0) 查找并返回从 start 开始的字符 ch 的位置;rfind(ch) 从末尾开始,查找并返回第一个找到的字符 ch 的位置(皆从 0 开始)(如果查找不到,返回 -1)。
  • substr(start, len) 可以从字符串的 start(从 0 开始)截取一个长度为 len 的字符串(缺省 len 时代码截取到字符串末尾)。
  • append(s) 将 s 添加到字符串末尾。
  • append(s, pos, n) 将字符串 s 中,从 pos 开始的 n 个字符连接到当前字符串结尾。
  • replace(pos, n, s) 删除从 pos 开始的 n 个字符,然后在 pos 处插入串 s
  • erase(pos, n) 删除从 pos 开始的 n 个字符。
  • insert(pos, s) 在 pos 位置插入字符串 s

参考

[1] <<2023王道数据结构>>

[2] cppreference