数据结构与算法-线性结构之顺序表[二]

tag: 线性表


数据结构:

先明确一点,数据指的是什么,比如我们有一堆数字,1,2,3,4,5 这是5个数字,这一组数据5个数字是孤立的,它们是数据,但是我们给他赋予一种关系,这个关系可以是各种逻辑关系,然后就可以将这个整体【性质相同的数据集合 + 数据之间的联系】叫做数据结构。当然这个数据只要是同性质的即可,可以是整型,字符串,同一类型对象…。

逻辑结构

数据之间的关系可能是多种的,逻辑结构上来讲,分几种:

  - 线性结构
  - 非线性结构 【包括三种: 集合结构,树形结构,图状结构】

存储结构

数据之间按照存储在内存中的结构,存储结构来区分,也分为几种:

  - 顺序结构
  - 链式结构
  - 索引结构
  - 散列结构


线性结构

  今天的topic是线性结构,线性结构具体又怎么定义呢,指的是数据之间的逻辑关系是一对一的关系,一个数据元素只和另外一个元素关联。

  线性结构有多种:

  - 线性表
  - 栈(特殊的线性表) 【先进后出】
  - 队列(特殊的线性表)【先进先出】
  - 字符串,数组,广义表

###线性表:

线性表

  线性表指的是具有**相同特性的数据元素的一个有限序列

(A1, A2…Ai, Ai+1…An)

  线性表的逻辑特征:

  1. 只有一个起点A1,没有直接前驱,仅有一个直接后继A2。
  2. 只有一个重点An,没有直接后继,仅有一个直接前驱An-1。
  3. 其他的内部节点Ai,其中 1<i<n,有且仅有一个直接前驱Ai-1和一个直接后继Ai+1

当然这个线性表里面的数据元素可以为简单类型,也可以为复杂类型。

线性表的抽象数据类型

ADT 数据抽象:
  数据对象:序列里面的数据元素
  数据关系:一对一、前驱、后继
  基本操作:对数据的各种基本操作。

我们来看线性表的基本操作:

initialList(&L) // &L 存储线性表头部地址的指针
destroyList(&L) // 销毁线性表
clearList(&L)   // 清空线性表
listEmpty(&L)     // 判断线性表是否为空 
listLength(&L)
getEle(L, i, &e) // i为1到n,n为线性表的长度,返回到指针变量e当中
locateEle(L, e, compare()) // e为需要找到的元素与e对比,如何对比,compare函数定义
priorEle(L, curE, &preE) // 求一个元素的前驱
nextEle(L, curE, &nextE) // 求一个元素的后继
listInsert(&L, i, e)     // 在第i个位置插入一个元素
listDelete(&L, i, &e)    // 删除第i个元素,返回这个元素的引用 i为1到n,n为线性表长度
listTraverse(&L, visited()) // 遍历每一个元素,然后对它做什么操作呢,在visited函数里面做处理。

  除了初始化,基本上所有操作的前提都是线性表存在。上面这些操作,是在线性表上的逻辑上的操作,我们需要达到什么目的,需要什么结果。但是实际上我们具体该怎么实现需要根据线性表的存储结构来定。


线性表有两种基本的存储结构:

顺序存储结构:

  逻辑上相邻的元素存储在物理上相邻的存储单元中的存储结构 【在内存中是连续的存储单元】 依次存储,地址连续。逻辑上相邻,物理上也相邻。 一般采用数组方式实现

链式存储结构:

  逻辑上相邻的元素相邻的的存储单元的存储结构 并不是连续,只会存储的指针引用。


顺序表的查找 插入 删除操作

以顺序存储结构存储的线性表,我们叫他顺序表,它有如下的特点:地址连续/依次存放/随机存取/类型相同。 我们就想到用一纬数组来表示顺序表。

  顺序表用一纬数组来实现,我们来实现特定的几个基本操作,查找/插入/删除一个元素。

  javascript里面数组是可变的,但是在golang里面数组都是不可变的,改变长度或者元素的值,都是new一个,然后从新赋值。

  定义 var orderList = [1,4,5,2,8,9] 顺序表。

顺序表的查找算法:

  如果存在,返回在顺序表中的位置。


  function locateEle(L, item) {
    for(let i = 0; i < L.length; i++) {
        if(L[i] == item) return i + 1
    }
    return 0
  }

  时间复杂度分析: 假定线性表长度为n,如果查找第一个元素,那么查找一次即可,最后一个元素,那么需要查找n次。我们求一个平均值,如果所有的值都被查找一遍,那么平均查找的执行次数是多少。

  如果表长度为n,为确定记录在表中的位置,需要与给定值进行比较的关键字个数的期望值叫做查找算法的平均查找长度。Average Search Length。 每个元素的查找概率为1/n, 查找第一个元素需要执行1次,那么第二个元素需要2次,所以这个期望值就是
1 * 1/n + 2 * 1/n +.... n * 1/n = (1+2+..+n)/n = 1/n * n(n+1)/2 = (n+1)/2
  咱们去掉系数和常数项,这个基础查找算法的时间复杂度就是O(n);

顺序表的插入算法


    // i 从1开始,表示在表中的位置,插入的位置只能是1,到n+1,一共就n+1个元素
    function insertElement(L, i, e) {
        if (L == null) throw new Error('空表')
        if (i < 1 || i > L.length + 1) throw new Error('非法的位置')
        //倒序,将 第i到第n的元素往后移一个位置,将e放到i的位置上
        for (let j = L.length - 1; j >= i - 1; j--) {
            L[j+1] = L[j]
        }
        L[i-1] = e
        return e
    }

时间复杂度分析: 假定线性表长度为n,插入的位置影响了执行次数。插入在最后,那么移动0次,插入第一个,那么移动n次。 插入的数字可能有 (n+1)种可能,插入的元素可能在第1个 第二个…直到第n+1个。按照上面的期望值来计算。

1/(n+1) * (0+1+2+...n) = n/2

所以时间复杂度为O(n)

顺序表的删除算法


  // i从1开始,表示在表中的位置,删除的的位置只能是1,到n
  // 倒序
  function removeElement(L, i) {
      if (L == null) throw new Error('空表')
      if (i < 1 || i > L.length) throw new Error('非法的位置')
      //依次将后面的值赋值给前一个,直到到i为止
      for (let j = L.length - 1; j >= i - 1; j--) {
          L[j-1] = L[j]
      }
      L.pop()
      return true
  }

    // 也可以用正序,正序的起点为i即可
    function removeElement(L, i) {
        if (L == null) throw new Error('空表')
        if (i < 1 || i > L.length) throw new Error('非法的位置')
        // 依次将后面的值赋值给前一个,直到到i为止
        // 注意边界
        for (let j = i - 1; j < L.length - 1; j++) {
            L[j] = L[j+1]
        }
        L.pop()
        return true  
    }

时间复杂度分析: 假定线性表长度为n,每个元素都有可能被删除:最坏的是删除第1个元素,需要移动n-1个元素,最好的是删除第n个元素需要移动0次,每个元素被删除的概率为1/n,总的被删除移动的和为 (n-1) + (n-2) + (n-3) + ...+ 0。 所以考虑n个元素的平均移动次数结果为1/n * n(n-1)/2 = (n-2)/2

所以时间复杂度为O(n);


总结

顺序表的逻辑结构和存储结构是一致的,逻辑上相邻的元素在存储的时候物理位置也是相邻的。因为存储地址是连续的,我们知道了第一个元素的地址,那么后面所有的元素都能够快速计算得到,所以粗略的认为访问每一个元素所花费的时间相等。【这种存取元素的方法称为***随机存取法***】

查找、插入、删除算法的时间复杂度都是O(n),空间复杂度都是O(1),没有使用额外的辅助空间。

优点:
   1, 存取效率高,O(1)。
缺点:

   1,特定情况下:插入,删除元素的情况,会移动大量元素,效率低下

   2,一些高级语言里面数组是不可变的,每次改变都会创建一个新的数组。而且不可变数组存在的情况,实现顺序表需要先定义固定长度的数组,这样是非常不灵活的。

所以线性表有另外的方式来实现,链式存储-> 链表,to be continued

致敬:https://www.bilibili.com/video/av31802230

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值