我正在参加「掘金·启航计划」
数据结构学习
我感觉这个东西,大家就字面意思理解就好。
计算机存储数据的结构。
像我们前端,最常提到的堆栈,就是数据结构。
前端相对后端、大数据这种岗位来说,对数据结构的敏感度并不高。在写前端的时候,大部分时候,数组、字典基本上就能满足大多数的场景。js中也没有链表这种数据结构。但是我们js中的的原型链就是一种链表的形式。
随着职位的提升。格局和视野也要跟着一起提升。不要给自己设线。前端后端不一定要明确划线。对自己感兴趣的东西,可以多做深入研究。多种技能傍身。也就不怕裁员了。
接下来,我们把每一种数据结构详细讲解,并附上js代码
栈
栈,英文名stack。
栈是一个先进后出的数据结构。也有人说后进先出。
js中没有栈。但是我们可以用array来模拟。
比较简单,我们直接上代码。大家看代码理解。
class Stack{
constructor(){
this._stack = []
}
push(val){
this._stack.push(val)
return this._stack
}
pop(){
if(!this.isEmpty()){
return this._stack.pop()
}
}
isEmpty(){
return this._stack.length===0
}
size(){
return this._stack.length
}
}
const stack = new Stack()
stack.push(1)
stack.push(2)
stack.size()
stack.pop()
stack.pop()
stack.pop()
stack.size()
stack.isEmpty()
什么场景适合使用栈数据结构呢?我们来leetcode,按照栈的筛选一下。
让我们用栈的数据结构来且试一下20题。
20. 有效的括号
var isValid = function(s) {
if(s.length%2) return false
const stack = []
function isSame(left,right){
if(left==='('&&right===')') return true
if(left==='['&&right===']') return true
if(left==='{'&&right==='}') return true
return false
}
for(let i=0;i<s.length;i++){
if(['(','{','['].includes(s[i])){
stack.push(s[i])
}else{
const prev = stack.pop()
if(!isSame(prev,s[i])){
return false
}
}
}
return stack.length===0
};
用栈来解决此题并非最优解,后面学习到其他数据结构,会重新写这道题。大家留意一下。
队列
队列刚好和栈相反。队列是先进先出的一种数据结构。说完栈,刚好趁热打铁,学习队列。
既然他两是相反的,那我们尝试用两个stack可以实现一个队列。 用两个stack实现一个queue,queue可以add、delete、size。
class TwoStackOneQueue{
constructor(){
this._stack1 = []
this._stack2 = []
}
size(){
return this._stack1.length
}
add(val){
this._stack1.push(val)
return this._stack1
}
delete(){
let res
const stack1 = this._stack1
const stack2 = this._stack2
while(stack1.length){
const n = stack1.pop()
stack2.push(n)
}
res = stack2.pop()
while(stack2.length){
const n = stack2.pop()
stack1.push(n)
}
return res
}
}
const myQueue = new TwoStackOneQueue()
console.log(myQueue.add(1))
console.log(myQueue.add(2))
console.log(myQueue.size())
console.log(myQueue.delete())
console.log(myQueue.size())
链表
链表是一种基础的数据结构
用next指针来连接元素。在本地试一下下面的代码,输出a。
const a = {val:1}
const b = {val:2}
const c = {val:3}
a.next = b
b.next = c
console.log(a)
查看结果之后相信你已经初步了解了什么是链表。
链表特点
- 查找节点的时间复杂度为O(n),插入、删除节点的时间复杂度为O(1)。
- 链表适用于写操作多,读操作少的场景。
链表可以分为
- 单向链表
- 双向链表
- 循环链表
通过几道leetcode题目,深入掌握链表的基础操作
- 删除链表中的某个节点
237. 删除链表中的节点
const deleteNode = function (node) {
node.val = node.next.val
node.next = node.next.next
}
- 反转链表
206. 反转链表
const reverseList = function (head) {
let p1 = head
let p2 = null
while (p1) {
const tmp = p1.next
p1.next = p2
p2 = p1
p1 = tmp
}
return p2
}
2. 两数相加
在刷leetcodel链表相关的题目中可以看到,相关的链表定义。在后面题目解答中,我们都可以直接来使用ListNode
// Definition for singly-linked list.
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
const addTwoNumbers = function (l1, l2) {
let lastCarry = 0
const list = new ListNode(null, {})
let cursor = list
while (l1 || l2) {
const val1 = l1 && l1.val ? l1.val : 0
const val2 = l2 && l2.val ? l2.val : 0
cursor.next = new ListNode((val1 + val2 + lastCarry) % 10, null)
lastCarry = Math.floor((val1 + val2 + lastCarry) / 10)
cursor = cursor.next
l1 = l1.next || ''
l2 = l2.next || ''
}
if (lastCarry) {
cursor.next = new ListNode(lastCarry, null)
}
return list.next
}
集合
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
set 的具体使用,大家可以参照阮一峰老师的 ECMAScript 6 入门
349. 两个数组的交集
var intersection = function (nums1, nums2) {
if (!nums1 || !nums1.length || !nums2 || !nums2.length) return [];
const res = new Set();
for (var i = 0; i < nums1.length; i++) {
if (nums2.includes(nums1[i])) {
res.add(nums1[i]);
}
}
return Array.from(res);
};
字典
Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
用字典再来实现一遍
20. 有效的括号
var isValid = function (s) {
if (s.length % 2 === 1) return false;
const map = new Map();
map.set("(", ")");
map.set("{", "}");
map.set("[", "]");
const stack = [];
for (var i = 0; i < s.length; i++) {
if (map.has(s[i])) {
stack.push(s[i]);
} else {
const n = stack.pop();
if (map.get(n) !== s[i]) return false;
}
}
return !stack.length;
};
树
树是一种分层数据的抽象模型。像js中的DOM树、级联选择组件等。
树的遍历——深度优先和广度优先
深度优先和广度优先算法一定要背下来。在算法题中出现的次数非常多
现在让我们来构建一颗树,在后续的遍历中使用
const tree = {
val: '1',
children: [
{
val: '2',
children: [
{
val: '4'
},
{
val: '5'
}
]
},
{
val: '3',
children: [
{
val: '6'
},
{
val: '7'
}
]
}
]
}
深度优先
深度优先就是在遍历的过程中,只要有子元素就先遍历子元素
递归
function dfs(root) {
console.log(root.val)
if (!root.children) return
root.children.forEach(dfs)
}
dfs(tree)
广度优先
广度优先就是在遍历的过程中,只要有兄弟元素就先遍历兄弟元素
function bfs(root) {
let stack = [root]
while (stack && stack.length) {
const n = stack.shift()
console.log(n.val)
n.children && n.children.forEach(v => stack.push(v))
}
}
bfs(tree)
二叉树
二叉树中每个树的节点最多只能两个
二叉树有三种遍历方式:先序遍历、中序遍历、后序遍历。
构建一颗二叉树,方便后续遍历
const tree = {
val: 1,
left: {
val: 2,
left: {
val: 3
},
right: {
val: 4
}
},
right: {
val: 5,
left: {
val: 6
},
right: {
val: 7
}
}
}
先序遍历
- 访问根节点
- 访问左子树
- 访问右子树
function preorderRraversal(tree) {
if (!tree) return
console.log(tree.val)
preorderRraversal(tree.left)
preorderRraversal(tree.right)
}
中序遍历
- 访问左子树
- 访问根节点
- 访问右子树
function inorderTraversal(tree) {
if (!tree) return
inorderTraversal(tree.left)
console.log(tree.val)
inorderTraversal(tree.right)
}
后序遍历
- 访问左子树
- 访问右子树
- 访问根节点
function subsequentTraversal(tree) {
if (!tree) return
subsequentTraversal(tree.left)
subsequentTraversal(tree.right)
console.log(tree.val)
}