1
0
mirror of https://github.com/142vip/408CSFamily.git synced 2026-02-08 13:03:49 +08:00
Files
408CSFamily/docs/ds/linear_table.md
2023-02-03 09:34:39 +08:00

10 KiB
Raw Blame History

title
title
线性表

基础概念和基本操作

强调线性表是一种逻辑结构,不是存储结构

定义

线性表是具有相同数据类型的nn≥0个数据元素的有限序列。一般表示

L=(a1,a2,a3......an) 其中n可以理解为表长线性表的长度n=0时候即表空

  • 表头元素线性表中唯一的“第一个”数据元素例如a1
  • 表尾元素线性表中唯一的“最后一个”数据元素例如an

重要逻辑特性:

  • 除表头元素外,线性表中每个元素有且仅有一个直接前驱
  • 除表尾元素外,线性表中每个元素有且仅有一个直接后继

基于此,这种线性有序的逻辑结构,使得线性表的特点如下:

  • 元素的个数有限(强调有限序列)
  • 元素在逻辑上具有顺序性,在序列中每个元素都是都有先后次序的
  • 元素都数据元素,每个元素都是单个元素
  • 元素的数据类型都相同(强调相同数据类型),每个数据元素占用相同大小的存储空间
  • 元素具有抽象性,仅仅讨论元素之间的逻辑关系,不需要去考虑元素究竟表示的什么内容

Tips: 线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表则指的是存储结构

基本操作

  • InitList(&L) 初始化表。构造空的线性表
  • Length(L)获取表的长度。返回线性表L的长度即表中的数据元素个数
  • LocateElem(L,e)按值查找操作。在表L中国查找具有给定关键字的元素
  • GetElem(L,i)按位查找操作。获取表中第i个位置的元素的值
  • ListInsert(&L,i,e)插入操作。在表的第i个位置上插入指定元素e
  • ListDelete(&L,i,&e)删除操作。删除表中第i个位置的元素并用e返回删除元素的值
  • PrintList(L)输出操作。按照前后顺序1、2....n输出线性表的所有元素值
  • Empty(L)判空操作。当表L为空则返回true否则返回false
  • DestoryList(&L)销毁操作。将线性表销毁释放线性表L所占用的内存空间类似释放内存

线性表是具有相同的数据类型的有限个数据元素组成的,数据元素是由数据项组成的

线性表的顺序表示

定义

顺序表:顺序存储的线性表,是用一组地址连续的存储单元,依次存储线性表中的数据元素,使得在逻辑上相邻的两个元素在物理位置上也相邻。

顺序表中的元素的逻辑顺序与实际的物理位置相同

注意:

  • 线性表中的元素的位序是从1开始的例如1、2、3...
  • 数组中的元素的下标是从0开始的例如0、1、2...
# define MaxSize 20         // 定义常量MaxSize 用来声明顺序表的最大长度

// 线性表结构体定义【ElemType用来代指顺序表中元素的类型例如高级语言中的int、string....】
typedef struct{
    ElemType data[MaxSize];     // 顺序表的元素
    int length;                 // 顺序表的长度
}SqList

存储分配

静态分配:数组的大小和空间都是实现确定好的,一旦存储空间占满就会产生溢出,直接导致程序崩溃。(有点内存不够,宕机重启的意思....

动态分配:存储数据的空间在程序执行过程中通过动态存储分配语句分配的,即便是数据空间占满,也可以另外开辟一块更大的空间,来替换原来的存储空间,满足扩充数据空间的目的。(有点动态规划的意思....)最重要的是:不需要像静态分配那样,一次性地固定线性表的空间和大小

#define InitSize 100    // 表长度初始化


// 动态分配数组顺序表的结构体定义
typedef struct{
    ElemType *data;             // 动态分配数组的指针
    int MaxSize,length;         // 数组的最大容量和当前元素个数
}SqList;    

动态分配语句

// C语言中

L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);


// C++ 中

L.data=new ElemType[InitSize];

malloc()函数 指针型函数返回的指针指向该分配域的开头的位置。作用是在内存的动态存储区中分配一个长度为size的连续空间。百度百科

动态分配不是链式存储,而是属于顺序存储结构,动态分配的物理结构没有改变,依然是随机存取的方式。只是分配的空间大小可以在运行时决定;

顺序表的特点

  • 随机访问【这是最主要的特点】通过存储起始地址和元素序号O(1)时间内访问指定元素。
  • 存储密度高,没有结点只存储数据元素,不像索引存储那样,还需要索引表什么的..
  • 逻辑上相邻的元素物理上也相邻,插入和删除需要移动大量元素

基本操作

插入

在顺序表L的第i1≤i≤L.length+1个位置插入新的元素e

  • 第一步如果i非法则直接返回false插入失败结束插入过程
  • 第二步i正常将表的第i个元素以及后面的所有元素都像有移动一个位置在腾出来的空位置插入元素e
  • 第三步顺序表插入成功返回true

注意:先判空和临界值,提高算法健壮性


/*
 * @Description: 顺序表的插入操作
 * @Version: Beta1.0
 * @Author: 【B站&公众号】Rong姐姐好可爱
 * @Date: 2020-02-23 07:48:26
 * @LastEditors: 【B站&公众号】Rong姐姐好可爱
 * @LastEditTime: 2020-02-23 07:48:26
 */
bool ListInsert(SqList &L, int i, ElemType e){

  // i非法 i=1 表头 i=L.length+1 表尾巴
  if(i<1||i>L.length+1){
    return false;
  }

  // 存储空间满,无法插入
  if(L.length >= MaxSize){
    return false;
  }

  // 遍历,将位置元素往后移动,注意从后往前循环,避免值被覆盖
  for(int j=L.length; j>=i;j--){
    L.data[j]=L.data[j-1];
  }

  // 此时表L中的第i个元素和第i+1元素素值一样将新元素存入i位置即可

  // 第i个元素对应的位置角标为i-1
  L.data[i-1]=e;

  // 表长度加1
  L.length++;

  // 返回插入成功
  return true;
}

注意:区别顺序表中的位序和角标;

时间复杂度

  • 最好情况在表尾插入元素向后移动循环没有执行时间复杂度O(1);
  • 最坏情况在表头插入元素后移循环执行n次时间复杂度为O(n);
  • 平均情况随机插入平均次数为n/2对应的平均复杂度为O(n);

线性表插入算法的平均时间复杂度为O(n)

Tips: 需要根据实现代码理解循环为什么是从后往前来实现元素后移通过for循环可以很明显的看出表尾插入快表头插入慢

删除

删除顺序表L中第i1≤i≤L.length+1个位置的元素

  • 成功返回true,将被删除的元素用引用变量返回;
  • 失败返回false

/*
 * @Description: 顺序表的删除操作
 * @Version: Beta1.0
 * @Author: 【B站&公众号】Rong姐姐好可爱
 * @Date: 2020-02-23 07:48:26
 * @LastEditors: 【B站&公众号】Rong姐姐好可爱
 * @LastEditTime: 2020-02-23 07:48:26
 */
bool ListDelete(SqList &L, int i, ElemType &e){

  // i非法 i=1 表头 i=L.length+1 表尾巴
  if(i<1||i>L.length+1){
    return false;
  }

  // 存储空间满,无法插入
  if(L.length >= MaxSize){
    return false;
  }
  
  // 引用变量e赋值
  e=L.data[i-1]

  // 遍历第i个元素后面的往前移动
  for(int j=i; j<=L.length;j++){
    // 从第i个元素开始角标从i-1开始
    L.data[j-1]=L.data[j];
  }

  // 此时表L中的表尾元素和倒数第二个元素值一样将表的长度-1 

  // 表长度减1
  L.length--;

  // 返回删除成功
  return true;
}

从这里来看,删除、插入元素都会涉及到大量的元素的移动(最好情况例外),总结而言:

  • 元素从后往前移,循环从前往后遍历
  • 元素从前往后移,循环从后往前遍历

时间复杂度:

  • 最好情况删除表尾元素不需要移动任何元素时间复杂度为O(1)
  • 最坏情况删除表头元素需要移动除第一个元素外的所有元素时间复杂度为O(n)
  • 平均情况:随机删除,平均需要(n-1)/2对应的时间复杂度为O(n)

线性表删除算法的平均时间复杂度为O(n)

按值查找(顺序查找)

在顺序表L中查找第一个元素值等于e的元素并返回位序

/*
 * @Description: 顺序表的按值查找(顺序查找)
 * @Version: Beta1.0
 * @Author: 【B站&公众号】Rong姐姐好可爱
 * @Date: 2020-02-23 07:48:26
 * @LastEditors: 【B站&公众号】Rong姐姐好可爱
 * @LastEditTime: 2020-02-23 07:48:26
 */
int LocateElem(SqList L,ElemType e){
    int i;
    // 循环判断
    for(i=0;i<L.length;i++){
        if(L.data[i]===e){
            // i是元素的角标i+1是具体元素的位序号
            return i+1;
        }
    }
    
    // 未命中返回0,即:没有
    return 0;
    
}

注意理解位序的含义,即元素在线性表中的位置序号,角标为i角标从0开始,对应的位序为i+1位序从1开始。当返回为0时则直接代表没有命中

时间复杂度:

  • 最好情况查找的元素在表头只需要比较一次循环成本最小时间复杂度为O(1);
  • 最坏情况查找的元素在表尾或者不存在需要完整遍历比较n次时间复杂度为O(n);
  • 平均情况随机查找表上的第i个1≤i≤L.length元素,平均次数为(n+1)/2,对应时间复杂度为O(n)

线性表按值查找顺序查找的平均时间复杂度为O(n)

顺序存取是读写方式,不是存储结构;顺序存储是存储结构,包括有:顺序存储、链式存储、索引存储、散列存储