数据结构(上)

288 阅读5分钟

Queue队列

定义

队列是一种First-in,First-out的数据结构,这是事件循环(Event Loop)的基础结构

题目

请实现一个餐厅叫号网页,点击取号按钮,生成一个号码,点击叫号按钮,显示“请x号就餐”

思路

  • 首先选择队列作为数据结构
  • 使用push, shift来加入队列和退出队列
  • 练习使用call写法

代码

import "./styles.css";

const btnCreateNumber = document.querySelector("#createNumber");
const btnCallNumber = document.querySelector("#callNumber");
const spanNewNumber = document.querySelector("#newNumber");
const spanQueue = document.querySelector("#queue");
const divScreen = document.querySelector("#screen");

const queue = [];
let number = 0;

btnCreateNumber.onclick = () => {
  number += 1;
  queue.push.call(queue, number); // 等价于 queue.push(number)
  spanNewNumber.innerText = number;
  spanQueue.innerText = JSON.stringify(queue);
};

btnCallNumber.onclick = () => {
  const n = queue.shift.call(queue); // 等价于 queue.shift()
  // if n === undefined 没处理
  divScreen.innerText = `请 ${n} 号就餐`;
  spanQueue.innerText = JSON.stringify(queue);
};

Stack栈

定义

结构就是后进先出 Last-in,Fist-out,每个数据按顺序存放。例如,乒乓盒子中最顶层的球5,最后一个放置进去,却第一个被拿出来使用。如果想拿到最底部的乒乓球1,需要把上面的球全部取出来,让乒乓球1处于盒子顶层

题目

JS函数的调用栈就是一个栈,假设f1调用了f2, f2存在就调用f3, f3调用结束了就调用f2f2调用结束了就回到f1

思路

自己用笔画一下,压栈pusu和出栈pop的过程,再来写代码

代码

function f1(){let a = 1; return a + f2()}
function f2(){let b = 2; return b + f3()}
function f3(){let c = 3; return c}
f1()

LinkedList链表

定义

链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构-节点,按特定的顺序链接在一起的抽象数据类型。

分类

Linked List单链表

单链表 由各个内存结构通过一个Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和Next指针域组成。

Double Linked List双向链表

双向链表:由各个内存结构通过指针Next和指针Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域,Prev指针域和Next指针域组成。

Circular Linked List循环链表

单向循环链表 : 由各个内存结构通过一个指针Next 链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域和Next指针域组成。

Double Circular Linked List双向循环链表: 由各个内存结构通过指针Next和指针Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域,Prev指针域和Next 指针域组成。

链表的运用

JS中的原型链就是链表的运用

let array = [1,2,3]
array._proto_ === Array.prototype
Array.prototype._prototype === Object.prototype

代码

const createList = value => {
  return createNode(value);
};
const appendList = (list, value) => {
  const node = createNode(value);
  let x = list;
  while (x.next) {
    x = x.next;
  }
  // x.next === null //x 是最后一个节点
  x.next = node;
  return node;
};
const removeFromList = (list, node) => {
  let x = list;
  let p = node; // 将 p 初始化为 null,这里改为 node
  while (x !== node && x !== null) { // 对 null 进行处理,如果 node 不在 list 中,x 就可能为 null
    p = x;
    x = x.next;
  }
  if(x === null){ // 若 x 为 null,则不需要删除,直接 returnfalse 表示无法删除不在list里的节点
    return false
  }else if(x === p){ // 这说明要删除的节点是第一个节点
    p = x.next
    return p // 如果删除的是第一个节点,那么就要把新 list 的头节点 p 返回给外面,即 newList = removeFromList(list, list)
  }else{
    p.next = x.next;
    return list// 返回list
  }
};

const createNode = value => {
  return {
    data: value,
    next: null
  };
};

const travelList = (list, fn) => {
  let x = list;
  while (x !== null) {
    fn(x);
    x = x.next;
  }
};

const list = createList(10);
const node2 = appendList(list, 20);
const node3 = appendList(list, 30);
const node4 = appendList(list, 40);
travelList(list, node => {
  console.log(node.data);
});

Key-valuePairs哈希表

定义

散列(hashing)是电脑科学中一种对资料的处理方法,通过某种特定的函数/算法(称为散列函数/算法)将要检索的项与用来检索的索引(称为散列,或者散列值)关联起来,生成一种便于搜索的数据结构(称为散列表)。也译为散列。旧译哈希(误以为是人名而采用了音译)。它也常用作一种资讯安全的实作方法,由一串资料中经过散列算法(Hashing algorithms)计算出来的资料指纹(data fingerprint),经常用来识别档案与资料是否有被窜改,以保证档案与资料确实是由原创者所提供

场景

假设哈希表里有一万对key-value, 比如name:'frank', age: '18... 如何读取hash[xxxx]速度最快

解决办法

  1. 不做任何算法的优化,hash[xxxx]需要遍历所有的key,时间为O(n)
  2. 如果对key排序,使用二分法查找,时间为O(log2 n)
  3. 用字符串对应的ASCII数字作为索引,时间为O(1)
  4. 对索引做除法,取其余数,时间为O(1)
  5. 如果冲突了,就扩充容积或者顺延

Tree树

定义

树是一种递归数据结构,包含一个或多个数据节点的集合,其中一个节点被指定为树的根,而其余节点被称为根的子节点

特点

  • 除根节点之外的节点被划分为非空集,其中每个节点将被称为子树。
  • 树的节点要么保持它们之间的父子关系,要么它们是姐妹节点。
  • 在通用树中,一个节点可以具有任意数量的子节点,但它只能有一个父节点。下图显示了一棵树,其中节点A是树的根节点,而其他节点可以看作是A的子节点。

实际运用

  • 中国的省市区,可以看成一棵树
  • 公司的层级结构,可以看成一颗树
  • 网页的节点,可以看出一棵树

代码

const createTree = value => {
    return {
        data: value,
        children: null,
        parent: null,
    };
};

const addChild = (node, value) => {
    const newNode = {
        data: value,
        children: null,
        parent: node
    }
    node.children = node.children || []
    node.children.push(newNode)
    return newNode
}

const travel = (tree, fn) =>{
    fn(tree)
    if(!tree.children){return ;}
    for(let i = 0; i<tree.child.length; i++){
        travel(tree.children[i], fn)
    }
};

const find = (tree, node) =>{
    if(tree === node){
        return tree
    }else if(tree.children){
        for(let i = 0; i<tree.length;i++){
            const result = find(tree.children[i], node)
            if(result){return result}
        }
        return undefined
    }else{
        return undefined
    }
}
const removeNode = (tree, node)=>{
    const siblings = node.parent.children
    let index = 0
    for(let i =1; i < siblings.length; i++){
        if(siblings[i] === node){
            index = i
        }
    }
    siblings.splice(index, 1)
};

const tree = createTree(10);
const node2 = addChild(tree, 20);
const node3 = addChild(tree, 30);

const fn = node = {
    console.log(node.data);
}
travel(tree, fn);

removeNode(tree, node3)

更多信息

数据结构:链表

树 易百教程

「哈希表」是什么?有哪些常用的解决冲突的方法?