数据结构总结

154 阅读2分钟

我正在参加「掘金·启航计划」

数据结构学习

我感觉这个东西,大家就字面意思理解就好。

计算机存储数据的结构。

像我们前端,最常提到的堆栈,就是数据结构。

前端相对后端、大数据这种岗位来说,对数据结构的敏感度并不高。在写前端的时候,大部分时候,数组、字典基本上就能满足大多数的场景。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,按照栈的筛选一下。

image.png

让我们用栈的数据结构来且试一下20题。

20. 有效的括号

image.png


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. 删除链表中的节点

image.png

const deleteNode = function (node) {
  node.val = node.next.val
  node.next = node.next.next
}
  • 反转链表

206. 反转链表

image.png

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. 两数相加

image.png

在刷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. 两个数组的交集

image.png

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. 有效的括号

image.png

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)
}