线性表的顺序存储结构,是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,但是它的缺点就是删除、插入时,需要移动大量元素。
线性表的链式存储结构,由于它不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的弱点,但也失去了顺序存储可随机存取的优点。
链表分为单链表、双链表、循环链表、静态链表。
链表是通过一组任意的存储单元来存储线性表中的数据元素,并且还存放一个指向其后继结点的指针,双链表还会存放指向前驱结点的指针。
单链表中结点类型的描述如下:
class LNode {
data: any; //数据域
next: LNode; //指针域
}
单链表的元素是离散地分布在存储空间中的,所以单链表是非随机存取的存储结构,既不能直接找到表中某个特定的结点。查找某个特定的结点时,需要从表头开始遍历依次查找。
通常,我们会加多一个 头结点 来标识一个链表。头结点的数据域可以不存储任何信息,也可以记录表长等信息。头结点的指针指向线性表的第一个结点,但头指针的指针域为 NULL 时,表示次单链表是空的。
引入头结点后,可以带来两个优点:
- 由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理。
- 无论链表是否为空,空表中头结点的指针域为空,因此空表和非空表的处理就统一了。
单链表的建立可以采用 头插法 或 尾插法,其插入的时间复杂度是 O(1)。
查找结点操作的时间复杂度是 O(n)。
插入结点操作的时间复杂度是 O(1),如果是在某个结点前插入一个新结点,可以转换成在某结点后插入,再交换两个元素。
删除结点操作的时间复杂度是 O(n),因为找到其前驱结点需要 O(n) 的时间。
单链表结点中只有一个指向其后继的指针,这使得单链表只能从头结点依次顺序地向后遍历。 若要访问某个结点的前驱结点(插入、删除操作时),只能从头开始遍历,访问后继结点的时间复杂度是 O(1),访问前驱结点的时间复杂度是 O(n)。
在双链表中,拥有两个指针域 prior 和 next,分别指向其前驱结点和后继结点。
class DNode{
data: any; //数据域
prior: DNode; //前驱结点域
next: DNode; //后继结点域
}
循环单链表与单链表的区别在于,表中最后一个结点的指针不是 NULL,而应该指向头结点,从而整个链形成一个环。
在循环单链表中没有指针域为 NULL 的结点,因此循环单链表的判空条件不是头结点的指针是否为空,而是它是否等于头指针。
循环单链表设置 尾指针 会更加高效,因若设置的是头指针,那么对表尾进行操作需要 O(n) 的时间复杂度。而如果设置的是尾指针 r
,则 r->next
即为头指针了,对表头与表尾的操作都可以在 O(1) 的时间内完成。
在循环双链表中,头结点的 prior 指针指向表尾结点,表尾结点的 next 指针指向头结点。
循环双链表判断是否为空表时,判断头结点的 prior 和 next 指针域是否指向当前头结点。
静态链表是借助数组来描述线性表的链式存储结构,结点有数据域 data 和 指针域 next。这里的指针是结点的相对地址(数组下标),又称为游标。
静态链表是需要预先分配一块连续的内存空间的。在静态链表中可以以 next = -1
作为其结束的标识。
静态链表的插入、删除操作与动态链表相同,只需要修改指针,而不需要移动元素。
static MAX_SIZE = 100;
class StaticLinkList {
data: any;
next: number;
}
C语言定义:
#define MaxSize 50 //静态链表的最大长度
typedef struct { //静态链表结构类型的定义
ElemType data; //存储数据元素
int next; //下一个元素的数组下标
} SLinkList[MaxSize];