单链表是什么?
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
重点:链式存取(即可存可取,逻辑形式为链),物理地址不连续
链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
重点:元素,指针
这就解释了,物理地址不连续的情况下怎么做到以链的逻辑形式存在,即通过指针指向下一个元素的地址
结构示意图如下:
特别注意:头指针和尾结点,
单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。链表由头指针唯一确定,单链表可以用头指针的名字来命名。
终端结点无后继,故终端结点的指针域为空,即NULL
翻译过来就是,头结点没有元素,但是头结点的next指向第一个元素的地址,尾结点通常有元素(链表非空),但是尾结点的next没有指向目标,所以为null
特殊情况,当链表为空时,头结点和尾结点是同一个,所以空链表只有一个结点,即头结点,且头结点的next为空
使用单链表的优缺点
优点:
1.无需给出链表的初始大小。因为链表本身是一个动态的数据结构,它可以在运行时通过分配和取消分配内存来增长和收缩
2.插入/删除操作很便捷。得益于链表的物理地址非连续,在进行插入或者删除时只要更改指向下一个元素地址的指针即可完成插入删除操作。不同于数组,数组中的物理地址时连续分配的,所以每当插入或删除元素时需要移动大量的元素
3.节省物理空间,不会造成浪费。因为链表时通过指针来连接元素的,所以有多少元素就分配多少空间,插入就动态新增空间分配,删除就动态回收空间分配,可以粗略理解成,元素有多少,链表就有多长。而数组通常会出现大量的空间浪费,例如一个数组中只有4个元素,但是初始化时为了考虑极端情况而分配了100个元素的物理空间,那么大多数时候,大量的物理空间都是闲置的,就是资源浪费。
4.实用性强。使用链表可以简易的实现栈和队列的结构
缺点:
1.使用更多的内存(元素数量相同的情况下相较数组而言)。这与上面的资源节约的优点并不矛盾,只不过使用背景不同。当元素数量固定时。使用数组会比链表更节约内存,因为数组的物理地址时连续的,只需要为元素分配存储空间即可,而链表除了需要分配元素存储空间,还需要为指向下一个元素的指针分配存储空间。所以,这个缺点实在特定的情况下才会显现。
2.查询操作繁琐。因为链表中所有的元素都是通过上一个元素的指针连接在一起的,无法直接通过索引找到元素本身,只能从头结点开始一个个往下找,直到找到目标元素。因此,每一次查询操作都几乎是一次遍历操作。相比之下数组的优点就是查询快捷,因为数组空间是连续的,所以只要有索引就可以直接找到对应的元素在哪。
3.逆向遍历。这个缺点和第二点有相似的地方。例如,当一个数字,我们知道它在链表的倒数第二个结点上,我们要取到它必须从头遍历链表直到这个结点。但是理论上,聪明的做法肯定是从尾部找起,只需要找2个结点就能取到,但是单链表做不到逆序遍历。双向链表虽然可以逆序遍历,但是需要在每个节点都添加趋前指针来指向上一个结点的地址,对内存的消耗更大。
单链表的基本操作
1.初始化
2.打印
3.根据索引查找元素
4.头部插入
5.头部删除
6.尾部插入
7.尾部删除
8.查找元素位置
9.删除指定位置元素
10.删除第一个出现的指定元素
11.删除出现的所有指定元素
12.输出单链表长度
13.链表排序
14.销毁
单链表的实现(只实现部分简单操作)
function Node(element) {
this.element = element;
this.next = null;
}
class Linkedlist {
constructor() {
this.head = null;
this.length = 0;
}
getElement(index) {
// 获取索引对应节点的元素
if (index < 0 || index > this.length) {
return null;
}
let current = this.head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current.element;
}
append(element) {
// 向尾部添加新节点
let node = new Node(element);
if (this.head === null) {
this.head = node;
} else {
// 直接找到尾结点,把其next指向新节点
let current = this.getElement(this.length - 1);
current.next = node;
}
this.length++;
}
insert(index, element) {
// 在指定位置插入新节点
if (index < 0 || index > this.length) { return false; }
let node = new Node(element);
if (index === 0) {
node.next = this.head;
this.head = node;
} else {
let prev = this.getElement(this.length - 1);
node.next = prev.next;
prev.next = node;
}
this.length++;
return true;
}
remove(index) {
// 根据索引移除节点
if (index < 0 || index > this.length) { return null; }
let current = this.head;
if (index === 0) {
this.head = current.next;
} else {
let prev = this.getElement(index - 1);
let current = prev.next;
prev.next = current.next;
}
this.length--;
return current.element;
}
indexOf(element) {
// 根据值查找索引(如果链表有多个相同的值就不适用)
for (let i = 0; i < this.length; i++) {
if (current.element === element) {
return i;
}
current = current.next;
}
}
}