顺序存储结构(数组)
一段地址连续的存储的单元依次存储线性表的数据元素
数组:
数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般不变
线性表的长度是线性表中存储元素的个数,这个数小于等于数组长度
线性表中元素地址值:
后一个元素的地址值 = 前一个位置 + 前一个元素占据的存储单元
存取时间复杂度:
由上面的公式可知,数组的存取时间性能为o(1)
插入与删除:
插入:
1. 插入位置不合理,抛出异常(插入位置 < 1 个位置或大于数组长度+1)
2. 线性表长度 >= 数组长度,抛出异常或动态增加容量
3. 从最后一个位置开始遍历到第 i 个位置,全部向后移动一个位置,将元素插到第 i 个位
置
4. 线性表长度加一
删除:
1. 删除位置不合理,抛出异常(删除位置 < 1 个位置或大于数组长度+1)
2. 线性表为空,抛出异常
3. 取出要删除的元素
4. 从删除位置遍历到最后一个位置,将他们向前移动一个位置
5. 线性表长度减一
数组的优缺点
优点:
● 无需为元素之间的逻辑关系增加额外的存储空间
● 可以快速存取表中任意位置的位置的元素
缺点:
● 插入删除需要移动大量元素
● 线性表长度变化较大时难以确定存储空间
● 造成存储空间的碎片
链式存储结构
链表除了存储数据信息外,还存有后继元素的储存地址
每一个元素称为结点,节点分为两部分,前一部分存储数据,称为数据域,后一部分存储后继元素 的地址值,称为指针域
每个节点中只包含一个指针域,叫做单链表
链表第一个节点的存储位置较头指针,最后一个节点指针为空
链表的第一个节点前可以设一个头结点,数据域可以不存东西也可以存链表的长度,指针域指向第一个结点
若链表有头结点,头指针指向头结点,无论链表是否为空,头指针都不为空。此时头结点的指针域为空
单链表的读取
● 获取第 i 个元素
● 声明一个指针指向第一个结点,初始化一个 j 从 1 开始向 p 向后移动, j 累加 1
● 若到了链表尾P为空,说明 i 不存在
● 反之存在,返回 p
时间复杂度:
最坏的情况为 o(n)
单链表的插入和删除
插入(在位置 i 插入 s)
● 声明指针p , s。p指向链头,初始化 j 从 1开始
● 当 j < i 时,让 p向后移动,不断指向下一个结点
● 若到链表尾p为空,说明第 i 个结点不存在
● 若查找成功,在系统中用指针 s 生成一个空节点 s
● 将元素e赋值给s的数据域
● 单链表插入语句 将p的指针域赋值给s的指针域,将 s 的位置赋值给p的指针域
删除(删除位置 i 的结点)
● 声明指针p,q。p指向链头,初始化 j 从 1开始
● 当 j < i 时,让 p向后移动,不断指向下一个结点
● 若到链表尾p为空,说明第 i 个结点不存在
● 否则将欲删除的元素的位置 p -> next 赋值给q
● 将q个位置 赋值给p
● 返回q的数据域的值,并释放q
单链表查找的时间复杂度为o(n),插入和删除的时间复杂度为o(n),但是在这个位置之后的 插入和删除只是简单的通过赋值移动指针,时间复杂度为o(1)。对于插入和删除越频繁的操作,单 链表的效率高
单链表的整表创建:
头插法:
● 声明一个指针 p
● 声明一个空链表 L,生成一个头结点,指针指向null
● 循环
● 生成新结点赋值给p
● 给p的数据域赋值
● 将头结点的指针域赋值给p的指针域
● 将p的位置赋值给头结点的指针域
尾插法:
● 声明两个指针p , r
● 声明一个空链表 L,生成一个头结点,将r赋值为指向尾部的结点
● 循环
● 生成新节点p
● 给新结点的数据域赋值
● 将p的位置赋值给r的指针域
● 循环完毕,将r的指针域指向null
单链表的整表删除
● 声明两个指针 p, q
● p指向链表的第一个结点
● 循环,当p不为空时
● 将p的指针域赋值给q
● 释放p
● 将q赋值给p
● 循环完毕,链表头指针指向null
单链表结构和顺序存储结构(数组)的优缺点
存储分配方式:
顺序结构用一段连续存储单元依次存储线性表的数据元素
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
查找:
顺序结构 : o(1)
单链表 : o(n)
插入删除:
顺序结构 : o(n)
单链表 : 查出某个位置的指针后,插入和删除时间为o(1)
空间性能:
顺序结构需要预先分配空间,分大了浪费,分小了溢出
单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
总结:
线性表需要频繁进行插入和删除操作时,采用链表;需要大量查找时,宜采用顺序存储结 构
一开始不知道元素个数时,宜采用链表;事先知道,采用顺序存储结构
循环链表
将单链表的尾指针指向头结点,使整个单链表形成一个环
与单链表的差异:
单链表: p -> next = null ? 到了尾部 : 没到尾部
循环链表: p -> next = 头结点 ? 循环结束 : 循环未结束
循环链表查找第一个结点花费o(1),找最后一个花费o(n),对循环链表进行改造,不用头指针,改用指向尾部的尾指针rear,这样查找尾部结点的时间为o(1),查找头结点为rear -> next ->next(头结点的存在),时间复杂度也为o(1)
对于将两个循环链表合成一个循环链表的处理方法(a链表尾结点为rearA,b链表尾结点为rearB)
● 声明指针p, q
● p = rearA -> next
● q = rearB -> next
● rearA -> next = rearB -> next -> next
● rearB -> next = p
● free(q)
双向链表
单链表查找下一个的时间复杂度为o(1),但是查找上一个的复杂度为o(n),为了解决这一问题,引入双向链表
双向链表是在单链表的每个结点中,再设置一个前驱结点指针
在双向链表中插入元素:
将 s 插入 p 和 p -> next之间
● s -> prior = p
● s - > next= p - > next
● p - > next-> prior = s
● p - > next = s
在双向链表中删除元素:
删除p:
将p - > next 赋值给 p -> prior 的后继
将p - > prior赋值给 p - >next 的前驱
free(p)