学习目录
- 数组的缺点
- 定义链表
- 设计一个基于对象的链表
- Node 类
- LinkList 类
- 插入新节点
- 从链表中删除一个节点
- 双向链表
why
因为在 React 中就是通过链表的形式实现遍历组件树的。所以数据结构的重要性可想而知。
介绍
数组作为底层的数据结构存储的时候是有缺点的,这一章我们将解释为什么有时候链表作为数据结构的时候会比数组好。还会基于对象实现一个链表。
数组的缺点
- 数组在 JavaScript 中长度虽然不是固定的,但是在很多其他的语言中长度是固定的,造成了添加新的元素非常的困难
- 数组操作,如增删改查是比较困难的,一般是通过将
数组元素前后移动位置的方式,反应数组的增删改查操作。但是 JavaScript 并不存在这样的问题,splice 方法实现了不需要访问数组中的其他元素的方式了。 - 问题: JavaScript 中的数组被实现成
对象, 这样与其他语言相比,效率就会低很多。 - 使用: 当我们使用数组访问很慢的时候,就应该考虑使用链表结构,React 中遍历组件树使用链表的原因可能就是这个。
- 链表的一个缺点就是对随机访问没有优势,对其他对于一维数组的操作都可使用链表的形式。
定义链表
链表
本质:是一个集合。由很多节点组成的集合。节点使用一个对象指向他的后继。
链
本质:指向后继的这个对象的引用,就是链。
遍历链表
遍历就是跟着链表的指向,走一遍所有的节点。
注意: 链表的遍历是不包含 头节点, 头节点 作为节点的接入用,不参与遍历的行为。链表有头就有尾, 尾部一般指向 JavaScript 的 null 节点。
头结点
对于链表结构我们需要自己实现链表的头节点。
节点尾
节点尾部,一般是使用 null 作为节点,表示后面没有新的节点了。
节点插入链表
链表能有效的找打要插入节点的位置,然后高效的进行节点查询
节点从链表中删除
同节点插入,在链表数据结构中,我们能快速的查找到需要删除的节点的位置,进行删除。
设计一个基于对象的链表
- 包含两个类:
- Node 类表示节点
- LinkedList 类提供插入节点,删除节点、显示列表元素方法,以及一些其他的方法
Node 类
Node 类包含两个属性
/**
*
* @param {Any} element 保存节点上的数据
*/
function Node(element) {
this.element = element;
this.next = null
}
- element 是保存在节点的数据
- next 指向下一个节点连接
LinkedList 类
LList 类是提供了对链表进行操作的方法。
- 插入
- 删除
- 查找
LList 也是一个构造函数,链表只要一个属性 --- 使用 Node 对象保存的链表头,即 头节点。
function LList() {
this.head = new Node('head');
this.find = find;
this.insert = insert;
this.remove = remove;
this.display = display;
}
值得注意的是: 头结点 的 next 被指定为 null, 当有新元素插入的时候,next 会向新的元素。
插入新的节点
在插入新的节点的时候,需要找到后面的节点。我们需要一个 find 方法,也就是上面的find方法。这个find 方法主要是遍历链表,查找我们的目标元素,找到位置。如果找到了,就将该节点的数据保存起来。
所以在插入一个新的节点的时候,我们需要干的第一件事 --- 定义一个 find 函数:
/**
*
* @param {Any} item 我们需要查找的链表数据项
*/
function find(item) {
var currNode = this.head;
while(currNode.element != item) {
currNode = currNode.next
}
return currNode;
}
实现一个 insert 方法
/**
*
* @param {Any} newElement 新的链表节点的数据
* @param {Any} item 找到插入的位置
*/
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode
}
展示列表中的元素,我们可以定义一个display 方法
function display() {
var currNode = this.head;
while(!(currNode.next == null)) {
print(currNode.next.element);
currNode = currNode.next;
}
}
function print(value) {
console.log(value)
}
diplay 函数的作用:获取到头部保存为当前的节点,然后遍历链表,将所有的链表数据打印出来。
从链表中删除一个节点
删除一个节点,首先要知道从哪里删除。所有可以定义个类似于 find 的方法叫 findPrevious 函数辅助实现删除一个节点。
function findPrevious(item) {
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item)) {
currNode = currNode.next
}
return currNode
}
function remove() {
var preNode = this.findPrevious(item)
if(!(preNode.next == null)) {
prevNode.next= prevNode.next.next
}
}
整合
function Node(element) {
this.element = element;
this.next = null
}
function LList() {
this.head = new Node("head");
this.find = find
this.insert = insert
this.display = display
this.findPrevious = findPrevious
this.remove = remove
}
function find(item) {
var currNode = this.head;
while (currNode.element != item ) {
currNode = currNode.next;
}
return currNode
}
function insert(newElement, item) {
var newNode = new Node(newElement)
var current = this.find(item)
newNode.next = current.next;
current.next = newNode;
}
function display() {
var currNode = this.head;
while(!(currNode.next == null)) {
print(currNode.next.element)
currNode = currNode.next
}
}
function findPrevious(item) {
var currNode = this.head;
while (!(currNode.next == null) && (currNode.next.element != item)) {
currNode = currNode.next
}
return currNode
}
function remove(item) {
var preNode = this.findPrevious(item)
if (!(preNode.next == null)) {
preNode.next = preNode.next.next
}
}
function print(value) {
console.log(value)
}
var cities = new LList();
cities.insert('Conway', 'head')
cities.insert('Russellville', 'Conway')
cities.insert('Carlisle', 'Russellville')
cities.insert('Alma', 'Carlisle')
cities.display()
cities.remove('Carlisle')
cities.display()
// Conway
// Russellville
// Carlisle
// Alma
// Conway
// Russellville
// Alma
参考
- [数据结构与算法](Data Structures & Algorithums with JavaScript) 是 MichAel McMillan 著作,是 王群, 杜欢 翻译;主要参考书籍。
- React 使用链表遍历组件树