线性表有头有尾穿成一串。
顺序存储的线性表称为顺序表(Sequential List)。顺序存储指的是在线性表在存储空间内连续存放,这种存储方式就是顺序存储结构,逻辑上相邻的元素在物理存储上也相邻。
数组就是一种顺序表,定义一个顺序表结构:
/* 最大长度 */
#define MAXSIZE 100
/* 元素数据类型 */
typedef int element_t;
/* 顺序表类型 */
struct _sq_list{
element_t *base; /* 顺序表基地址 */
int length; /* 表长 */
};
typedef struct _sq_list *sq_list_t;
/* 定义一个顺序表 */
sq_list_t sq_list;
这时候就有一个类型为sq_list_t的顺序表sq_list了,这是一个指针,在使用的时候还需要为其分配内存空间。可以用sq_list.base[i*sizeof(element_t)]来访问第i个元素。可以看出,求表长、判断表是否为空、等操作的时间复杂度都是 O(1) ,下面讨论顺序表主要操作实现。
注意:元素计数按照C语言习惯从0开始,但是长度length就是元素个数。
顺序表的初始化:
代码里malloc()函数用于申请内存空间,大小需要我们进行计算,其返回类型为(void*),因此需要强制转换为目标类型。使用完毕要及时释放空间。
从顺序表里取出第i个数据,从0开始计数。算法步骤:
在第i个位置插入新元素e,并让长度加1,这时候就要考虑搬动元素了。
算法步骤:
这个算法没有处理表的动态扩充,表长到达预设最大长度时,就无法插入新元素了。这个算法主要的时间耗费在移动元素上,假设插入n个元素具有的n+1个位置是等概率的,概率为 pi=1/(n+1) ,那么移动次数的期望值为:
A=∑i=0n−1pi(n−i)=2n
平均时间复杂度为 O(n) 。或者简单一点考虑,最差情况下,n个数据需要移动n次,那么最坏时间复杂度为 O(n) 。
删除是插入的逆操作,直接后面覆盖前面就行,在操作上没有明显的删除这个行为,算法步骤:
同样的这个算法的主要时间也是耗费在搬动元素上,最坏情况下n个元素需要搬动n-1次,那么时间复杂度为 O(n) 。
顺序表读取很快,但是插入删除不快,并且是静态的。有时候我们想要更好的插入删除性能,并且长度可以动态增加,这时候就要设计一种新的数据结构了。
线性表的链式存储结构,这种东西叫链表。一个元素除了存储信息,还要存储前后信息,这两部分合起来称为节结点(node)。结点包含数据域和指针域,根据一个结点所含的指针数、指针连接方式可以分为单链表、循环链表、双向链表、十字链表、邻接表、邻接多重表等。
一个单链表的结构如下图:
单链表结点的定义:
结点的数据类型为node_t,这里使用了一个数据类型别名为link_list_t,本质上是指向结点类型的一个指针,可以用来定义头指针,头指针需要初始化,或者叫作链表初始化,算法步骤为:
1.头指针指向一个新的node_t类型的内存空间(新结点)
取出第i个结点的数据,假设从0开始计数
这个算法的基本操作是比较ij并移动指针,最坏时间复杂度为 O(n) ,等概率读取下平均时间复杂度为
A=∑i=0n−1n1⋅i=2n−1
则平均时间复杂度为 O(n) 。
查找给定元素是否在链表上,如果在则返回地址,否则最后返回了NULL
平均时间复杂度计算核上面差不多都为 O(n)。
在第i个位置插入一个新节点(从0开始计数)