顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表是一种在存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现。
链表是由一系列的结点组成,结点可以在运行时动态生成。每个结点包含两部分:数据域与指针域。数据域存储数据元素,指针域存储下一结点的指针。
链表与顺序表的对比
链表失去了序列的随机读取优点,同时链表增加了指针域,空间开销也较大,但它对存储空间的使用要相对灵活。
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(1) | O(1) |
在中间插入/删除 | O(1) | O(n) |
注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作:
- 链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是 O(1)。
- 顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
单向链表
单向链表也叫单链表,是链表中最简单的形式。它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
head 保存首地址,item 存储数据,next 指向下一结点地址。
列如:有一堆数据[1,2,3,5,6,7,8,9,11,13],要在11和13之间插入12, 如果用数组,需要将13之后的数据都往后退一位,然后再插入12,这样非常麻烦,但是如果用链表,我就直接在11和13之间插入12就行。
自定义单链表的操作:
- isEmpty():链表是否为空。
- length():链表长度。
- travel():遍历整个链表。
- add(item):链表头部添加元素。
- append(item):链表尾部添加元素。
- insert(index, item):指定位置添加元素。
- remove(item):删除元素。
- find(item):元素是否存在。
定义节点
节点的数据结构为数据元素(item)与 指针(next)
kotlin
class Node {
/// 单链表的结点
var item;
Node? next;
Node({this.item, this.next});
}
定义链表
链表需要具有首地址指针head。
ini
class SingleLinkList {
/// 单链表
/// 指向头节点
Node? _head;
/// 链表是否为空
bool isEmpty() {
return _head == null;
}
/// 链表长度
int length() {
/// 初始指针指向_head
Node? cur = _head;
int count = 0;
while (cur != null) {
count += 1;
/// 指针下移
cur = cur.next;
}
return count;
}
/// 遍历链表
void travel() {
Node? cur = _head;
while (cur != null) {
print('cur.item: ${cur.item}');
cur = cur.next;
}
}
/// 链表头部添加元素
void add(item) {
final Node node = Node(item: item);
/// 新节点指针指向头节点
node.next = _head;
/// 头节点重新指向该节点
_head = node;
}
/// 链表尾部添加元素
void append(item) {
final Node node = Node(item: item);
/// 先判断链表是否为空, 若是空链表, 则将head指向新节点
if (isEmpty()) {
_head = node;
} else {
/// 若不为空, 则将尾部节点的next节点指向新节点
Node? cur = _head;
while (cur?.next != null) {
cur = cur?.next;
}
cur?.next = node;
}
}
/// 指定位置插入元素
insert(int index, item) {
/// index <=0, 插入头部
if (index <= 0) {
add(item);
} else if (index > (length() - 1)) {
/// 指定位置超过尾部, 尾部插入
append(item);
} else {
/// 指定位置插入
final Node node = Node(item: item);
Node? cur = _head;
/// 循环到需要插入的位置
for (var _ in List.generate(index - 1, (i) => i)) {
cur = cur?.next;
}
node.next = cur?.next;
cur?.next = node;
}
}
/// 移除节点
remove(item) {
/// 删除节点
Node? cur = _head;
Node? pre;
while (cur != null) {
if (cur.item == item) {
/// 头结点就是被删除的节点
if (pre == null) {
/// 将头指针指向头结点的后一个节点
_head = cur.next;
} else {
/// 将删除位置前一个节点的next指向删除位置的后一个节点
pre.next = cur.next;
}
} else {
/// 继续按链表后移节点
pre = cur;
cur = cur.next;
}
}
}
/// 查看节点是否存在,返回布尔值
bool find(item) {
Node? cur = _head;
while (cur != null) {
if (cur.item == item) {
return true;
}
cur = cur.next;
}
return false;
}
}