什么是链表
链表:是一种非连续,非顺序的存储数据的一种数据结构,由一系列的节点组成。
- 不同于数组,链表中的内存不必是连续的空间。
- 链表的每一个元素都是由存储元素本身的节点和指向下一个元素的引用所组成。
链表的优点:
- 存储的内存空间不必连续,可以充分的利用计算机的内存空间,可以实现灵活的内存空间动态管理。
- 链表不必在创建是确定大小,它可以无限的添加数据。
- 链表在添加和删除数据时,时间复杂度可以达到 O(1),相对数组效率要高很多。
链表的缺点:
- 访问任何位置的数据时都要从头访问。无法跳过头部元素访问。
- 无法通过下标值直接访问元素,需要遍历查找。
- 回到上一个节点比较难。
链表如下图:
什么是单向链表
单向链表(LinkedList):单向链表类似于火车,有一个火车头,火车头会连接一个节点,节点上有乘客,并且这个节点会连接下一个节点,以此类推。
单向链表如下图:
链表常见的操作
- append(data) 向链表尾部添加数据
- insert(position,data) 在指定的位置插入节点
- getData(position) 获取指定位置的链表节点
- indexOf(data) 查找数据对应的 index
- update(position, data) 更新指定位置的节点数据
- removeAt(position) 删除指定的节点
- remove(data) 删除指定 data 的节点
- isEmpty() 判断链表是否为空
- size() 获取链表的长度
- toString() 以字符串输出链表的所有元素
ES6 实现单向链表结构
- 链表内部节点类的封装
// 内部数据类: 由data和next组成。 链表的节点
class Node {
data;
next = null;
constructor(data) {
this.data = data;
}
}
- 单向链表类的封装
class LinkedList {
length = 0;
head = null;
}
- append(data) 方法的实现
// append(data) 向链表尾部添加数据
append(data) {
// 1. 首先创建新的链表节点
const newNode = new Node(data);
// 2. 向链表中添加节点
if (this.length === 0) {
// 如果是空链表,则head 指向 这个新节点
this.head = newNode;
} else {
// 主要思路: 否则从头结点开始查找到尾结点,添加到链表尾部
// 获取 head 节点
let currentNode = this.head;
// 循环找到尾部节点
while (currentNode.next !== null) {
currentNode = currentNode.next;
}
// 给尾部节点添加新的节点
currentNode.next = newNode;
}
// 链表长度增加
this.length++;
}
- insert(position,data) 方法的实现
// insert(position,data) 在指定的位置插入节点
insert(position = 0, data) {
// position 代表插入的位置
// position = 0 代表插入第一个位置
// 首先判断边界问题
if (position < 0 || position > this.length) return false;
// 1. 创建新的链表节点
const newNode = new Node(data);
// 主要思想:如果position 为 0,插入到链表头部,否则,找到对应的位置添加节点
// 2. 根据 position 添加 节点
if (position === 0) {
// position 为 0 的情况
// 首先将头的引用交给新节点的next
newNode.next = this.head;
// 再将这个新的节点的引用交给head
this.head = newNode;
} else {
// 0 < position < this.length 的情况
// 主要思想: 查找到对应的位置添加节点
// 准备变量
let currentNode = this.head; // 当前节点
let previousNode = null; // 上一个节点
let index = 0; // head 的index 为 0, 代表了当前是第几个节点
// 查找想要插入的位置
while (index++ < position) {
previousNode = currentNode;
currentNode = currentNode.next;
}
// 添加链表节点
// 首先,新节点的next 连接 当前的 currentNode 节点
// 然后,上一个节点的next 连接 新节点 newNode
newNode.next = currentNode;
previousNode.next = newNode;
}
// 跟新链表的长度
this.length++;
return newNode;
}
- getData(position) 方法的实现
// getData(position) 获取指定位置的链表节点
getData(position = 0) {
// 1. 判断边界
if (position < 0 || position >= this.length) return false;
// 查找指定位置
// 2. 准备变量
let currentNode = this.head; // 当前的链表节点
let index = 0; // 当前链表节点的位置
// 查找
while (index++ < position) {
currentNode = currentNode.next;
}
//返回
return currentNode.data;
}
- indexOf(data) 方法的实现
// indexOf(data) 查找数据对应的index
indexOf(data) {
// 主要思想: 查找所有的链表数据,返回对应数据的index
let currentNode = this.head;
let index = 0; // 代表当前节点的索引
// 循环查找
while (currentNode) {
if (currentNode.data === data) {
return index;
}
currentNode = currentNode.next;
index++;
}
// 否则返回 -1
return -1;
}
- update(position, data) 方法的实现
// update(position, data) 更新指定位置的节点数据
update(position, data) {
// 判断边界
if (position < 0 || position >= this.length) return false;
let currentNode = this.head;
let index = 0;
// 循环找到指定的节点
while (index++ < position) {
currentNode = currentNode.next;
}
// 更新数据
currentNode.data = data;
return currentNode;
}
- removeAt(position) 方法的实现
// removeAt(position) 删除指定的节点
removeAt(position) {
// 边界
if (position < 0 || position >= this.head) return false;
let currentNode = this.head;
// position 情况
if (position === 0) {
this.head = this.head.next;
} else {
// 上一个节点
let previousNode = null;
// 当前的节点索引
let index = 0;
// 查找链表节点
while (index++ < position) {
previousNode = currentNode;
currentNode = currentNode.next;
}
// 这样写的巧妙之处在于: 上一个节点的next 直接 和 当前节点的next 相连
// 相当于直接删除了这个指定的节点
previousNode.next = currentNode.next;
}
// 节点长度 -1
this.length--;
return currentNode;
}
- remove(data) 方法的实现
// remove(data) 删除指定data的节点
remove(data) {
return this.removeAt(this.indexOf(data));
}
- isEmpty() 方法的实现
// isEmpty() 判断链表是否为空
isEmpty() {
return this.length === 0;
}
- size() 方法的实现
// size() 获取链表的长度
size() {
return this.length;
}
- toString() 方法的实现
toString() {
let currentNode = this.head;
let result = "";
// 遍历所有的节点,拼接为字符串,直到节点为 null
while (currentNode) {
result += currentNode.data + " ";
currentNode = currentNode.next;
}
return result;
}
单向链表的总体代码
// 内部数据类: 由data和next组成。 链表的节点
class Node {
data;
next = null;
constructor(data) {
this.data = data;
}
}
class LinkedList {
length = 0;
head = null;
// append(data) 向链表尾部添加数据
append(data) {
// 1. 首先创建新的链表节点
const newNode = new Node(data);
// 2. 向链表中添加节点
if (this.length === 0) {
// 如果是空链表,则head 指向 这个新节点
this.head = newNode;
} else {
// 主要思路: 否则从头结点开始查找到尾结点,添加到链表尾部
// 获取 head 节点
let currentNode = this.head;
// 循环找到尾部节点
while (currentNode.next !== null) {
currentNode = currentNode.next;
}
// 给尾部节点添加新的节点
currentNode.next = newNode;
}
// 链表长度增加
this.length++;
}
// insert(position,data) 在指定的位置插入节点
insert(position = 0, data) {
// position 代表插入的位置
// position = 0 代表插入第一个位置
// 首先判断边界问题
if (position < 0 || position > this.length) return false;
// 1. 创建新的链表节点
const newNode = new Node(data);
// 主要思想:如果position 为 0,插入到链表头部,否则,找到对应的位置添加节点
// 2. 根据 position 添加 节点
if (position === 0) {
// position 为 0 的情况
// 首先将头的引用交给新节点的next
newNode.next = this.head;
// 再将这个新的节点的引用交给head
this.head = newNode;
} else {
// 0 < position < this.length 的情况
// 主要思想: 查找到对应的位置添加节点
// 准备变量
let currentNode = this.head; // 当前节点
let previousNode = null; // 上一个节点
let index = 0; // head 的index 为 0, 代表了当前是第几个节点
// 查找想要插入的位置
while (index++ < position) {
previousNode = currentNode;
currentNode = currentNode.next;
}
// 添加链表节点
// 首先,新节点的next 连接 当前的 currentNode 节点
// 然后,上一个节点的next 连接 新节点 newNode
newNode.next = currentNode;
previousNode.next = newNode;
}
// 跟新链表的长度
this.length++;
return newNode;
}
// getData(position) 获取指定位置的链表节点
getData(position = 0) {
// 1. 判断边界
if (position < 0 || position >= this.length) return false;
// 查找指定位置
// 2. 准备变量
let currentNode = this.head; // 当前的链表节点
let index = 0; // 当前链表节点的位置
// 查找
while (index++ < position) {
currentNode = currentNode.next;
}
//返回
return currentNode.data;
}
// indexOf(data) 查找数据对应的index
indexOf(data) {
// 主要思想: 查找所有的链表数据,返回对应数据的index
let currentNode = this.head;
let index = 0; // 代表当前节点的索引
// 循环查找
while (currentNode) {
if (currentNode.data === data) {
return index;
}
currentNode = currentNode.next;
index++;
}
// 否则返回 -1
return -1;
}
// update(position, data) 更新指定位置的节点数据
update(position, data) {
// 判断边界
if (position < 0 || position >= this.length) return false;
let currentNode = this.head;
let index = 0;
// 循环找到指定的节点
while (index++ < position) {
currentNode = currentNode.next;
}
// 更新数据
currentNode.data = data;
return currentNode;
}
// removeAt(position) 删除指定的节点
removeAt(position) {
// 边界
if (position < 0 || position >= this.head) return false;
let currentNode = this.head;
// position 情况
if (position === 0) {
this.head = this.head.next;
} else {
// 上一个节点
let previousNode = null;
// 当前的节点索引
let index = 0;
// 查找链表节点
while (index++ < position) {
previousNode = currentNode;
currentNode = currentNode.next;
}
// 这样写的巧妙之处在于: 上一个节点的next 直接 和 当前节点的next 相连
// 相当于直接删除了这个指定的节点
previousNode.next = currentNode.next;
}
// 节点长度 -1
this.length--;
return currentNode;
}
// remove(data) 删除指定data的节点
remove(data) {
return this.removeAt(this.indexOf(data));
}
// isEmpty() 判断链表是否为空
isEmpty() {
return this.length === 0;
}
// size() 获取链表的长度
size() {
return this.length;
}
toString() {
let currentNode = this.head;
let result = "";
// 遍历所有的节点,拼接为字符串,直到节点为 null
while (currentNode) {
result += currentNode.data + " ";
currentNode = currentNode.next;
}
return result;
}
}