数据结构(线性表)

线性表
线性表是具有相同类型的n(n>0)个数据元素的有限序列,其中 n 为表长,当 n = 0 时,线性表是一个空表,若用 L 命名表示线性表,则其一般表示为:
L = ( a 1 , a 2 , a 3 , . . . , a n ) L = (a_1,a_2,a_3,...,a_n) L=(a1,a2,a3,...,an)
a1是线性表中唯一的一个没有前驱的元素,又叫表头元素,an 是唯一的一个没有后缀元素的元素,除了这两个元素外,所有其他的元素都有一个前驱和一个后缀。正是由于这种线性有序的逻辑结构,所以,称其为线性表。

线性表是一中逻辑结构,表示的是元素和元素之间的一对一的相邻关系,但是顺序表和链表表示的是存储关系,需要正确的将这几个概念区分开。

顺序表
顺序表是将线性表的元素,顺序的存储在相邻的位置上,需要访问一个元素的时候,只需要知道顺序表表头的地址和元素相对于表头的偏移量就可以找到需要访问的元素。
定义一个顺序表的时候,顺序表的空间一般是提前就确定好的。当然有时候也可以将一个顺序表设计成可动态添加空间的。

顺序表基本操作的时间复杂度:

插入元素 最坏的情况 , 元素插入顺序表的前端 , 基本操作的执行频度为 f(n) = n,最好的情况,元素插入顺序表的最后端,基本操作频度为 f(n) = 1 ,所以插入元素操作的时间复杂度为 O(n)

删除元素 最坏的情况,删除顺序表表头的元素,此时基本操作的执行频度为 f (n) = n, 最好的情况下,删除顺序表表尾的元素,基本操作的执行频度为 f (n) = 1, 所以删除元素的操作的时间复杂度为O(n)

查找元素 最坏情况,查找到顺序表的表尾的时候,才查找到元素,基本操作的执行频度为 f(n) = n, 最好的情况,需要查找的元素就在表头,此时基本操作的执行频度为 f(n)=1, 所以查找元素操作的时间复杂度为O(n)

单链表:
链表是线性表的一种,链表的元素在逻辑上构成一条线性表。逻辑上相邻的元素在实际的物理空间上并不一定相邻。一个链表,有且仅有一个表头,表头中保存下一元素的地址,第二个元素保存了后一个元素的地址,以此类推,最后一个元素的后继指针为空。
链表的元素的定义为:

typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;

链表基本操作的时间复杂度
头插法创建单链表,在头部插入一个元素,从而初始化一个链表,每次插入,算法的执行频度为f (n) = 1, 假设链表的长度为 n ,那么初始化一个长度为 n 的链表,其时间复杂度为 O(n)

尾插法创建单链表,在尾部插入一个元素,从而初始化一个链表,每次插入,算法的执行频度为f (n) = 1, 假设链表的长度为 n ,那么初始化一个长度为 n 的链表,其时间复杂度为 O(n)

按序号查找元素,需要从链表的表头开始,逐个往后查找,找到序号对应的元素的时候,返回该元素的数据。操作的时间复杂度为 O(n)

按值查找元素的时候,最优的情况下,需要查找的元素就在链表的表头,那么算法的执行频度为f(n) = 1, 最坏的情况下,需要查找的元素在链表的最后一个元素,那么算法的执行频度为f(n)=n, 那么按值查找元素的算法时间复杂度为 O(n)

插入一个元素,算法中花费时间最多的操作是查找插入位置,最优的情况下,插入的位置在链表头,那么插入操作所需的频度为 f(n) = 1, 最坏的情况下,插入的位置在链表的表尾,那么插入所需的操作频度为 f(n) = n, 所以插入操作的时间复杂度为 O(n)

删除节点操作,该操作和插入操作一样,主要的时间是消耗在查找节点上,操作时间复杂度为O(n), 当指定操作的位置的时候,删除节点的时间复杂度为O(1)

求表长的操作,求表长的操作需要从表头开始,逐个遍历链表的所有节点,累计链表节点的长度,并记录。操作的时间复杂度为 O(n)

双链表
单链表结点中只有一个指向其后继的指针, 使得单链表只能从头结点依次顺序地向后遍历,要访问某个结点的前驱结点( 插入、 刪除操作时), 只能从头开始遍历, 访问后结点的时间杂度为 0(1) , 访问前驱结点的吋间杂度为 O(n),为了解决访问前驱节点的问题,引入了双链表。双链表在操作的时候需要修改指向前驱的指针和指向后驱节点的指针。
定义:

typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode, * DLinklist;

循环链表
循环单链灰和笮链表的区别在于, 衣中公后一个结点的指计不足 NULL, 而改为指向头结点,从而整个链表形成一个环,如下图:
循环单链表
在循环单链表中, 表尾结点的 next 域指向 L,故表中没冇指针域为 NULL ,因此,
循环单链表的判空条件不是头结点的指计是否为空, 而是它是否等于头指针。
循环单链表的插入、 刪除算法与单链表的儿乎一样 , 所不同的是若操作是在表尾进行. 则执行的操作不同, 以让单链表继续保持循环的性质。当然,正是因为循环单链表是一个“ 环”,因此在任何一个位置上的插入和删除操作都是等价的, 无须判断是否是表尾。
在单链表屮只能从表头结点幵始往后顺序遍历整个链表, 而循环单链表可以从表中的任意一个结点幵始遍历整个链表,有时对单链表常做的操作是在表头和表尾进行的, 此时对循环单链表不设头指针而仅设尾指针, 从而使得操作效率更高, 若设的是头指针, 对表尾进行操作需要0(n)的时间复杂度而若设的是尾指针 r, r->next 即为头指计, 对于表头与表尾进行操作都只需要 0(1 )的时间tt杂度。
循环双链表
静态链表
静态链表借助数组来描述线性表的链式存储结构, 结点也有数据域 data 和指针域 next 而与前面所讲的链表中的指针不同的是, 这里的指针是结点的相对地址( 数组下标,又称游标 。和顺序表一样,静态链表也需要提前分配一块连续的空间。
静态链表示例:
在这里插入图片描述
静态链表对应的单链表:
在这里插入图片描述

顺序表和线性表的比较:

存取方式
顺序表可以顺序存取, 也可以随机存取, 链表只能从表头顺序存取元素。
逻辑结构与物理结构
采用顺序存储时, 逻辑上相邻的元素, 其对应的物理存储位置也相邻•而采用链式存储时,逻辑上相邻的元索, 其物理存储位置则不一定相邻, 其对应的逻辑关系是通过指针链接來衣示的。
查找、插入和删除操作
对于按值査找, 顺序表无序时, 两者的时间复杂度均为 o(n); 顺序表有序时, 可采用折半查找, 此时的时间复杂度为 O(log2n) 对于按序号査找, 顺序表支持随机访问, 时间杂度仅为 0(1), 而链表的平均时间复杂度为0(n). 顺序表的插入、 删除操作, 平均需要移动半个表长的元素。 链表的插入、 删除操作只需修改相关结点的指针域即可。由于链表的每个结点都带有指针域, 因而在存储空间上比顺序存储付出的代价大, 而存储密度不够大。
空间分配
顺序存储在静态存储分配情形下, 一旦存储空间装满就不能扩充, 若再加入新元素. 则会出现内存溢出, 因此需要预先分配足够大的存储空间, 预先分配过大, 可能会异致顺序表后部太闲置。预先分配过小又会造成溢出动态存储分配虽然存储空间可以扩充, 但需要移动大量元素. 导致操作效率降低而且若内存中没有更大块的连续存储空间, 则会导致分配失败。 链式存储的结点空间只在需要时申请分配, 只要内存有空间就可以分配, 操作灵活、 高效。

顺序存储和链式存储的选择?

**存储因素** 难以估计线性表的长度时, 不宜采用顺序表,链表不用事先估计存储规模,但链表的存储密度较低. 显然链式存储结构的存储密度是小于 1 的。

运算考虑
在顺序存储中按序号访问 A的时间复杂度为 0(1). 而链表中按序号访问的时复杂度为 O(n), 因此若常做的运算是按序号访问数椐元素. 那么顺序表优于链表。
在顺序表中进行插入、 删除操作时, 平均需要移动表中一半的元索, 当数据元索的信息 量较大且表较长时。元素的移动会带来很大的工作量; 在链表中进行插入、 删除操作时, 虽然也要找插入位置, 但操作主要是比较操作。 从这个角度考虑显然链表优于顺序表。

基于环境的考虑
顺序表容易实现, 任何高级语言中都有数组类型: 链表的操作是基于指针的, 相对來讲,顺序表实观较为简单。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值