数据结构学习

100 阅读5分钟

一、数据结构简介

数据结构是什么

  • 就是数据与数据之间关系和结构

如何表示两个数据

如果顺序有意义

  • [x,y]表示第一个是x,第二个是y

  • 比如坐标就是这样表示的数据

  • 要提供firstlast操作

如果顺序没有意义

  • (x,y)(y,x)一样

  • 比如血压值(120,80)和(80,120)表达的意思没区别

  • 不需要提供firstlast操作

如何表示N个数据

如果顺序有意义

  • 数组表示[a1,a2,...,aN]

  • 要提供索引操作get(i)

  • 要提供add/indexOf/delete操作

如果顺序没有意义

  • 集合表示{a1,a2,...,aN}

  • 要提供add/delete/has操作

如何表示N对N数据

比如学号

  • 用哈希表可以表示

  • hash = {1001 => '小明', 1002 => '小红'}

  • key可以是任何数据类型(和js的对象不一样)

数据结构的作用

数据结构 = 数据形式(逻辑形式)+ 操作

  • 数据结构在编程世界里或生活中都很常见,可以让人快速理清解决问题的思路

  • 面试特别特别爱问

锻炼抽象能力

  • 一种数据结构往往可以解决很多类似的问题

  • 如果选错了数据结构,那就很难想到思路

  • 大佬级别的程序员更重视数据结构,而不是算法

二、队列Queue(先进先出的 FIFO 的数组)

实现一个餐厅叫号网页

  • 点击取号,生成一个号码

  • 点击叫号,显示xxx号请您就餐

代码思路

  • 先选择队列Queue作为数据结构

  • queue.push为入队,queue.shift为出队

代码实现

const 显示屏
const 拿号按钮
const 叫号按钮
const 当前号码显示
const 当前队列显示

let n = 0; // 初始化号码
let 当前队列数组 = [];

拿号按钮.onclick = () => {
    n += 1;
    // 练习一下call的用法
    当前队列数组.push.call(queue, n);
    当前号码显示.innerText = n;
    当前队列显示.innerText = JSON.stringify(当前队列数组);
};

叫号按钮.onclick = () => {
    if (当前队列数组.length === 0) {
        alert('没号了叫个屁啊')
        return
    }
    const m = 当前队列数组.shift.call(queue);
    显示屏.innerText = `请 ${m} 号就餐`;
    当前队列显示.innerText = JSON.stringify(当前队列数组);
};

三、栈 Stack (后进先出 LIFO 的数组)

  • JS函数的调用栈call stack 就是一个栈
function f1() {
    let a = 1; return a + f2()
}

function f2() {
    let b = 2; return b + f3()
}

function f3() {
    let c = 3; return c
}
  • 假如函数f1调用函数f2,函数f2再调用函数f3,那f3调用完了应该回到f2的位置,f2调用完了回到f1的位置(压栈,弹栈)

四、链表 Linked List (一个链一个)

image.png

实际使用

  • let array = [1,2,3]

  • array.__proto__ === Array.prototype

  • Array.__proto__ === Object.prototype

  • 这个角度看对象,就是链表

代码实现

list = create(value) 创建链表

const createList = (value) => {
    return createNode(value)
}

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

node = get(index) 获取链表

const getList = (list, index) => {
    let x = list
    for (let i = 1; i <= index; i++) {
        x = x.next
        return x
    }
}

append(node,value) 插入一个链表

const appendList = (list, value) => {
    const node = createNode(value)
    while (list.next) { //判断list是否为最后一个节点,如果不是就继续往下找 直到确定是最后一个节点
        list = list.next
    }
    list.next = node
    return node
}

trevel(list, fn) 遍历链表

const travelList = (list, fn) => {
    let x = list
    while (x !== null) { // 判断list是否为null是因为要遍历所有节点 包括最后一个 直到list是最后一个节点的next以后再退出
        fn(x)
        x = x.next
    }
}

remove(node) 删除一个链表

const removeFromList1 = (list, node) => {
    let x = list;
    let p = node; // 这里不能为null 
    while (x !== node && x !== null) { // 如果 node 不在 list 中,x 就可能为 null
        p = x;
        x = x.next;
    }
    if (x === null) { // 若 x 为 null,则不需要删除,直接 return, false 表示无法删除不在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 即可
    }
};

五、哈希表

哈希表是干嘛的

  • 声明哈希表是很简单的,存储数据也是很简单的,最难的是如何快速读取数据

场景

  • 假设哈希表hash里有一万对key-value

  • 比如name:'jack',age:18,pi:'property',...

使哈希表读取变快的解决方法

  • 不做任何优化,hash['xxx'] 需要遍历hash表里的所有key,复杂度为:O(n)

  • 对key排序,使用二分法查找,复杂度为:O(log2n)

  • 用字符串对应的ASCII数字做索引,复杂度为:O(1) 需要的空间太大

  • 对索引做除法取余数,复杂度为:O(1),如果冲突了就顺延

有一篇文章比较通俗的介绍了这个数据结构「哈希表」是什么?有哪些常用的解决冲突的方法?

六、树 Tree (一个链多个)

image.png

实际使用

  • 中国的行政区域划分,省-市-区(县)可以看成一棵树

  • 公司的层级结构可以看成一棵树

  • 网页中的节点可以看成一棵树

代码实现

let tree = createTree(value) 新增树

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

let node = addChild(tree,node) 新增节点

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

travel(tree) 遍历节点

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

find(tree,node) 找到节点

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

removeChild(node1,node2) 删除节点

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