文章目录
总的来说,书的质量不太高,如果作为前端工程师算法入门的书的话,不如去读《算法图解》,相比之下更容易理解,讲解也更加准确。这本JavaScript描述只是用了JavaScript去实现一些基础算法,而且实现的也不是很令人满意。
豆瓣评分:6.5 个人评分:☆☆☆
第1章 JavaScript的编程环境与模型
略
第2章 数组
略
第3章 列表
我的疑惑是,在实现列表的remove
方法时,使用了数组的splice
方法,那么为什么列表的其他方法不可以直接使用数组的方法来实现呢?
实际上列表完全可以用元素时对象的数组来代替。
第4章 栈
栈的实现
对于栈,我同样有上一章的疑问,入栈和出栈能够使用数组的
push
和pop
方法来实现,为什么我们不可以直接使用呢?或者说,什么情况下可以使用数组的方法、什么时候不可以使用数组的方法来实现对应的数据结构呢?
栈的元素只能通过列表的一端访问,这一端成为栈顶。一摞盘子就是一个典型的栈,只能从最上面取盘子,盘子洗净后也只能放置在一摞盘子的最上面。
栈是一种先入后出(LIFO,last-in-first-out)的数据结构
栈包含的属性和方法有:
length
,栈内存储了多少元素push
,入栈pop
,出栈peak
,返回栈顶元素clear
,清空站内元素
栈的底层数据结构可以使用数组实现,其中声明了一个内部变量top
,记录栈顶位置,当栈内没有元素是,top
为零
栈的完整实现:
function Stack() {
this.data = [];
this.top = 0;
}
Stack.prototype.push = function (val) {
this.data[this.top++] = val;
};
Stack.prototype.pop = function () {
return this.data[this.top <= 0 ? 0 : --this.top];
};
Stack.prototype.peak = function () {
return this.data[this.top - 1];
};
Stack.prototype.length = function () {
return this.top;
};
Stack.prototype.clear = function () {
this.top = 0;
this.data = [];
};
相比于书中的实现,我进行了三点改动
- 方法由实例方法改为了原型方法
- 在
pop
方法时判断了top
的正负,因为top
不能为负数 - 在
clear
时,同时将数据结构清空了
利用栈实现数制间的相互转换
利用栈将一个数字从一种数制转换为另一种,算法如下:
- 最高位为
n%b
,将此位压入栈中 - 使用
n/b
代替n
- 重复步骤1和2,直到
n
等于0
,且没有余数 - 将栈内元素逐个弹出,直到栈为空
只适用于基数为2-9的情况
function mulBase(num, base) {
let temp = num;
const s = new Stack();
let result = '';
while (temp > 0) {
s.push(temp % base);
temp = Math.floor(temp / base);
}
while (s.length()) {
result += s.pop();
}
return result;
}
console.log(mulBase(32, 2)); // 100000
判断回文字符串
回文指的是从前到后书写与从后到前书写都相同的单词、短语和数字,可以利用栈来判断回文字符串,方法就是将一个字符串按顺序压入栈中,然后再依次弹出,这样得到的字符串时一个与原来的字符串顺序相反的字符串,判断新字符串与旧字符串是否相同即可
function isPalindrome(word) {
let s = new Stack();
for (let i = 0; i < word.length; i++) {
s.push(word[i]);
}
let newWord = '';
while (s.length() > 0) {
newWord += s.pop();
}
return newWord === word;
}
console.log(isPalindrome('hello')); // false
console.log(isPalindrome('racecar')); // true
第5章 队列
队列是一种先进先出(First-In-First-Out,FIFO)的数据结构,用在很多地方例如提交操作系统执行的一系列进程等。
可以使用数组模拟队列。
第6章 链表
数组的缺点
其他编程语言中的数组长度是固定的,当数组被数据填满后,要再加入新的元素会很困难。另外加入、删除元素,都需要移动其他元素,很麻烦。
JavaScript中的数组不存在上述问题,因为JavaScript中的数组实际上被实现成了对象,但是与其他语言的数组相比效率很低。
如果发现在使用时数组很慢,可以考虑使用链表来代替,除了对数据的随机访问外,链表几乎可以用在任何可以使用一维数组的情况。
定义链表
链表是一组节点组成的集合,每个节点都使用一个对象的引用指向它的后继。指向另一个节点的引用叫做链
数组元素靠位置进行引用,链表元素靠相互间的关系进行引用,下图是一个有头节点的链表
设计一个基于对象的链表
包含两个类:
- Node类表示节点
- LinkedList类提供插入节点、删除节点、显示列表元素的方法,以及一些辅助方法
function Node(element) {
this.element = element;
this.next = null;
}
function LList() {
this.head = new Node('head');
}
LList.prototype.insert = function (newElement, item) {
const currNode = this.find(item);
const newNode = new Node(newElement);
newNode.next = currNode.next;
currNode.next = newNode;
};
LList.prototype.find = function (item) {
let currNode = this.head;
while (currNode.element !== item) {
if (currNode.next === null) {
return currNode;
}
currNode = currNode.next;
}
return currNode;
};
LList.prototype.remove = function () {};
LList.prototype.display = function () {
let currNode = this.head;
while (currNode.next) {
console.log(currNode.next.element);
currNode = currNode.next;
}
};
const cities = new LList();
cities.insert('Beijing', 'head');
cities.insert('Tianjin', 'Beijing');
cities.insert('Shanghai', 'Tianjin');
cities.insert('Guangzhou', 'Beijing');
cities.display();
// Beijing
// Guangzhou
// Tianjin
// Shanghai
实现find
时候稍有点不一样,判断了找不到的当前节点的情况,如果找不到的话就插入到最后
实现remove
方法时,需要先实现findPrevious
方法:
LList.prototype.findPrevious = function (item) {
let currNode = this.head;
while (currNode.next !== null && currNode.next.element !== item) {
currNode = currNode.next;
}
return currNode;
};
LList.prototype.remove = function (item) {
const prevNode = this.findPrevious(item);
if (prevNode && prevNode.next) {
prevNode.next = prevNode.next.next;
}
};
const cities = new LList();
cities.insert('Beijing', 'head');
cities.insert('Tianjin', 'Beijing');
cities.insert('Shanghai', 'Tianjin');
cities.insert('Guangzhou', 'Beijing');
cities.remove('Guangzhou');
cities.display();
// Beijing
// Tianjin
// Shanghai
双向链表
普通链表从后向前遍历很麻烦,可以为Node
类添加一个属性,该属性存储指向前驱节点的链接,这样反向遍历就容易多了