数据结构

103 阅读11分钟

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效

栈(Stack)

  • 栈( stack )又称堆栈,是一种后进先出的有序集合,其中一端为栈顶,另一端为栈底,添加元素(称为压栈/入栈或进栈)时,将新元素压入栈顶,删除元素(称为出栈或退栈)时,将栈底元素删除并返回被删除元素。

  • 特点:先进后出,后进先出(LIFO)

  • 例子:一叠书、一叠盘子。

image.png

手动实现一个栈,包含以下方法

注意数组的末尾是栈顶

  • push(element(s)): 此方法将新添加的元素添加至堆栈的顶部

  • pop():此方法删除栈顶的元素,同时返回已删除的元素

  • peek(): 返回堆栈的顶部元素

  • isEmpty(): 判断堆栈是否为空,如果为空,返回True, 否则返回False。

  • clear(): 清空堆栈所有的元素。

  • size(): 此方法返回堆栈元素的数量,类似数组的长度。

  • toArray(): 以数组的形式返回堆栈的元素。

  • toString():以字符串的形式输出堆栈内容。

class Stack {
    constructor() {
      this.items = [];
    }

    push(element) {
      this.items.push(element);
    }

    pop() {
      return this.items.pop();
    }

    peek() {
      return this.items[this.items.length - 1];
    }

    isEmpty() {
      return this.items.length === 0;
    }

    clear() {
      this.items = [];
    }

    size() {
      return this.items.length;
    }

    toArray() {
      return this.items.slice();
    }

    toString() {
      return this.items.toString();
    }
  }

队列(Queue)

队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作

队列是遵循FIFO先进先出原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。

image.png

手动实现一个队列,包含以下方法

  • enqueue(element):向队列尾部添加一个新的项。

  • dequeue():移除队列的第一项,并返回被移除的元素。

  • front():返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与 Stack 类的 peek 方法类似)。

  • tail():返回队列中的最后一个元素,队列不做任何变动。

  • isEmpty():如果栈没有任何元素就返回 true,否则返回 false

  • size():返回队列包含的的元素个数,与数组的 length 属性类似。

  • print():打印队列中的元素。

 /**
  * 2. 实现一个队列
  */
class Queue {
    constructor (){
        this.items = []
    }
    // enqueue(element):向队列尾部添加一个新的项。
    enqueue( element ){
        this.items.push(element)
    }
    // dequeue():移除队列的第一项,并返回被移除的元素。
    dequeue (){
        return this.items.shift()
    }
    // front():返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与 Stack 类的 peek 方法类似)。
    front (){
        return this.items[0]
    }
    // tail():返回队列中的最后一个元素,队列不做任何变动。
    tail (){
        return this.items[this.items.length]
    }
    // isEmpty():如果栈没有任何元素就返回 true,否则返回 false。
    isEmpty (){
        return this.items.length === 0
    }
    // size():返回队列包含的的元素个数,与数组的 length 属性类似。
    size (){
        return this.items.length
    }
    // print():打印队列中的元素。
    print (){
        console.log(this.items.toString())
    }
}

字典(Dictionary)

字典是用来存储唯一值的一种数据结构,通常以[键, 值]对的形式来存储数据,也称为映射。 es6中Map类的实现,就是我们所说的字典。

手动实现一个字典

  • set(key, value): 向字典中添加新元素。

  • remove(key): 使用键值从字典中移除键值对应的数据值。

  • has(key): 如果某个键值存在于这个字典中,则返回true,否则返回false。

  • get(key): 通过键值查找特定的值并返回。

  • clear(): 将字典中的所有元素都移除。

  • size(): 返回字典中所包含的元素的数量。与数组的length属性类似。

  • keys(): 将字典中所包含的所有的键名以数组的形式返回。

  • values(): 将字典中所包含的所有值以数组的形式返回。

class Dictionary {
    constructor() {
      this.items = Object.create(null);
    }

    has(key) {
      return this.items.hasOwnProperty(key);
    }

    set(key, value) {
      this.items[key] = value;
    }

    get(key) {
      return this.items[key];
    }

    remove(key) {
      return Reflect.deleteProperty(this.items, key);
    }

    clear() {
      this.items = {};
    }

    size() {
      return Object.keys().length;
    }

    keys() {
      return Object.keys();
    }

    values() {
      return Object.values();
    }
  }

集合(Set)

集合是由一组无序且唯一(即不能重复)的项组成的。也可以将集合看成一个既没有重复元素,也没有顺序概念的数组。通常以[值, 值]对的形式来存储数据

手动实现一个集合

  • add(value): 向集合添加一个新的项。

  • remove(value): 从集合移除一个值。

  • has(value): 如果值在集合中,返回true,否则返回false。

  • clear(): 移除集合中的所有项。

  • size(): 返回集合所包含元素的数量。与数组的length属性相似。

  • values(): 返回一个包含集合中所有值的数组。 以es6的Set类的实现为基础:

class Set {
    constructor() {
      this.items = Object.create(null);
    }

    has(value) {
      return this.items.hasOwnProperty(value);
    }

    set(value) {
      this.items[value] = value;
    }

    get(value) {
      return this.items[value];
    }

    remove(value) {
      return Reflect.deleteProperty(this.items, value);
    }

    clear() {
      this.items = {};
    }

    size() {
      return Object.keys(this.items).length;
    }

    values() {
      return Object.values(this.items);
    }
  }

散列表(HashTable)

HashTable类也叫HashMap类,是Dictionary类的一种散列表实现方式。

散列表:顾名思义也就是离散的或者零散,即不连贯的列表,也可以类比于离散数组。

散列算法的作用是尽可能的在数据结构中找到一个值。如果使用散列函数就知道值的具体位置,因此能快速检索到该值。散列函数的作用是给定一个键值,然后返回值在表中的地址。

class HashTable {
    constructor(){
        this.table = []
    }
    /**
     * 散列函数
     * @param {*} key 键名
     */
    hashCode(key) {
        let hash = 0;
        for (let i = 0; i < key.length; i++) {
          hash += key.charCodeAt(i);
        }
        return hash % 37;
    }
    /**
     * 向散列表增加/更新一个新的项
     * @param {*} key 添加的键名
     * @param {*} value 添加的值
     */
    put (key, value) {
        let position = this.hashCode(key)
        this.table[position] = value
    }

    /**
     * 根据键值从散列表中移除值
     * @param {*} key 移除的键名
     * @return {Boolean} 是否成功移除
     */
    remove (key) {
        if ( !key ) return false
        let position = this.hashCode(key)
        this.table[position] = undefined
        return true
    }

    /**
     * 根据键值检索到特定的值
     * @param {*} key 查找的键名
     * @return {*} 查找的值
     */
    get (key) {
        let position = this.hashCode(key)
        return this.table[position]
    }

    /**
     * 打印散列表中已保存的值
     * @return {*} 散列表的值
     */
    print () {
        return this.table
    }
}

数组(Array)

就是我们js里面的数组,没什么可说的

  • 元素的值按顺序放置,并通过从 0 到数组长度的索引访问;
  • 数组是连续的内存块;
  • 它们通常由相同类型的元素组成(这取决于编程语言);
  • 元素的访问和添加速度很快;搜索和删除是在 O(1) 中完成的。

链表(Linked Lists)

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。

image.png

  • push(element):向链表尾部添加一个新元素。

  • insert(element, position):向链表的特定位置插入一个新元素。

  • getVal(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回undefined。

  • remove(position):从链表的特定位置移除一个元素。

  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。

  • size():返回链表包含的元素个数,与数组的length属性类似。

  • toString():返回表示整个链表的字符串。

// 结构
{val: xxx, next: {val: xxx, next: {val: xxx, next: {...}}}}
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}

class LinkNodeList {
  constructor() {
    this.head = null;
    this.count = 0;
  }
  
  isEmpty() { return this.size() === 0; }
  
  // 从尾部添加节点
  // 如果想链表的尾部添加一个元素,那么首先需要通过上面实现的Node类来创建一个节点。如果当前的head还不存在,则说明当前的链表还是一个空,所以直接将head节点赋值即可。否则就找到链表的最后一个节点,将它的next指向新push的节点即可。并且将count增加

  push(element) {
    const node = new Node(element);
    
    // 第一次没有节点 直接赋值
    if (this.head === null) {
        this.head = node;
    } else {
        // 当前节点
        let current = this.head;
        // 循环节点 如果current.next没有值就代表是最后一个节点,有值就替换current,依次往下找节点
        while (current.next) {
            current = current.next;
        }
        // 找到最后一个节点,把它的current.next赋值为新节点
        current.next = node;
    }
    this.count++;
  }
  
  // 返回特定位置的元素
  getPosition(index) {
    if (index > 0 && index <= this.count) {
        let node = this.head;
        for (let i = 0; i < index; i++) {
                node = node.next;
        }
        return node;
    }
    return undefined;
   }
   
  // 返回特定值的元素
  getValue(val) {
    if (!this.head) {
        return undefined
    }
    
    let node = this.head
    while (node) {
        if (node.val === val) {
            return node
        } else {
            node = node.next
        }
    }
   }
   
  // 返回头元素
  getHead() {
    return this.head
  }
  
  // 返回尾元素
  getTail() {
    if (count < 2) {
        return this.head
    }
    
    let node = this.head
    while (node.next) {
        node = node.next
    }
    
    return node
   }
   
   // 向指定位置插入指定元素
   insert(ele, index) {
    if (index >= 0 && index <= this.count) {
        const node = new Node(ele);
        if (index === 0) {
            const current = this.head;
            node.next = current;
            this.head = node;
        } else {
            const prev = this.getVal(index - 1);
            node.next = prev.next;
            prev.next = node;
        }
        this.count++;
        return true;
    }
    return false;
   }
   
   // 移除指定位置的节点
   remove(index) {
    if (index >= 0 && index < this.count) {
            let current = this.head;
            // 移除第一项
            if (index === 0) {
                    this.head = current.next;
            } else {
                    const prev = this.getVal(index - 1);
                    current = prev.next;
                    prev.next = current.next;
            }
            this.count--;
            return current.val;
    }

    return undefined;
  }
}

树(Tree)

二叉树:是每个结点最多有两个子树的树结构。一个是左侧子节点,一个是右侧子节点。

二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大(或者等于)的值

image.png

  • insert(key): 向树中插入一个新的键。

  • search(key): 在树中查找一个键,如果节点存在返回true,如果不存在则返回false。

  • inOrderTraverse: 通过中序遍历方式遍历所有节点。

  • preOrderTraverse: 通过先序遍历方式遍历所有节点。

  • postOrderTraverse: 通过后序遍历方式遍历所有节点。

  • min: 返回树中最小的值/键。

  • max: 返回树中最大的值/键。

  • remove(key): 从树中移除某个键。

image.png

// 结构
{key: xxx, left: {key, left: {}, right: {}}, right: {key, {key, left: {}, right: {}}}}
// 创建一棵树
function BinarySearchTree () {
    // 初始化根节点为null
    let root = null
    // 声明节点Node类,用于创建多个独立的节点
    let Node = function (key) {
        this.key = key
        this.left = null
        this.right = null
    }
    // 下面是methods
    this.insert = function (key) {
        // 首先根据传入的键生成节点node
        let newNode = new Node(key)
        // 插入之前判断当前树中是否有根节点
        if (!root) {
            root = node
        } else {
            // 根节点存在则判断插入逻辑,需要辅助函数 insertNode
            insertNode(root, newNode)
        }
    }
    // 插入逻辑实际上就是判断要插入的子节点和父节点之间的大小,从而决定是该放在左边还是右边
    let insertNode = functicon (node, newNode) {
        // 左侧节点只能存放比父节点小的值
        if(newNode.key < node.key) {
            // 当前节点的左侧节点为空则可以插入,否则递归调用 insertNode
            !node.left ? node.left = newNode : insertNode(node.left, newNode)
        } else {
            // 当前节点的右侧节点只能存放大于等于父节点的值
            !node.right ? node.right = newNode : insertNode(node.right, newNode)
        }
    }
}

中序遍历:左节点根右节点 前序遍历:根左节点右节点 后序遍历:左节点右节点根 一切都是以根为基础,根输出的顺序

// 声明节点Node类,用于创建多个独立的节点
  const Node = function (key) {
    this.key = key;
    this.left = null;
    this.right = null;
  };

  class BinarySearchTree {
    constructor() {
      // 初始化根节点为null
      this.root = null;
    }

    // 下面是methods
    insert(key) {
      // 首先根据传入的键生成节点node
      const newNode = new Node(key);
      // 插入之前判断当前树中是否有根节点
      if (!this.root) {
        this.root = newNode;
      } else {
        // 根节点存在则判断插入逻辑,需要辅助函数 insertNode
        this.insertNode(this.root, newNode);
      }
    }

    // 插入逻辑实际上就是判断要插入的子节点和父节点之间的大小,从而决定是该放在左边还是右边
    insertNode = function (node, newNode) {
      // 左侧节点只能存放比父节点小的值
      if (newNode.key < node.key) {
        // 当前节点的左侧节点为空则可以插入,否则递归调用 insertNode
        !node.left ? (node.left = newNode) : this.insertNode(node.left, newNode);
      } else {
        // 当前节点的右侧节点只能存放大于等于父节点的值
        !node.right ? (node.right = newNode) : this.insertNode(node.right, newNode);
      }
    };

    // 中序遍历
    inOrderTraverse(callback) {
      this.inOrderTraverseNode(this.root, callback);
    }

    // 中序遍历
    inOrderTraverseNode(node, callback) {
      if (node) {
        this.inOrderTraverseNode(node.left, callback);
        callback(node.key);
        this.inOrderTraverseNode(node.right, callback);
      }
    }

    // 先序遍历
    preOrderTraverse(callback) {
      this.preOrderTraverseNode(this.root, callback);
    }

    // 先序遍历
    preOrderTraverseNode(node, callback) {
      if (node) {
        callback(node.key);
        this.preOrderTraverseNode(node.left, callback);
        this.preOrderTraverseNode(node.right, callback);
      }
    }

    // 后序遍历
    postOrderTraverse(callback) {
      this.postOrderTraverseNode(this.root, callback);
    }

    // 后序遍历
    postOrderTraverseNode(node, callback) {
      if (node) {
        this.postOrderTraverseNode(node.left, callback);
        this.postOrderTraverseNode(node.right, callback);
        callback(node.key);
      }
    }

    // 最小值
    min() {
      let node = this.root;
      if (node) {
        while (node && node.left) {
          node = node.left;
        }

        return node.key;
      }

      return null;
    }

    // 最大值
    max() {
      let node = this.root;
      if (node) {
        while (node && node.right) {
          node = node.right;
        }

        return node.key;
      }

      return null;
    }

    // 搜索
    search(key) {
      return this.searchNode(this.root, key);
    }

    searchNode(node, key) {
      if (node) {
        if (key < node.key) {
          return this.searchNode(node.left, key);
        } else if (key > node.key) {
          return this.searchNode(node.right, key);
        }
        return node;
      }

      return null;
    }

    remove(key) {
      this.root = this.removeNode(this.root, key);
    }

    // 删除指定值辅助类
    removeNode(node, key) {
      if (node === null) {
        return null;
      }
      if (key < node.key) {
        node.left = this.removeNode(node.left, key);
        return node;
      } else if (key > node.key) {
        node.right = this.removeNode(node.right, key);
        return node;
      }
      // 第一种情况,删除节点没有子节点
      if (node.left === null && node.right === null) {
        node = null;
        return node;
      }
      // 第二章情况,删除节点有一个子节点
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }
      // 第三种情况,删除节点有很多子节点
      // 我们需要找到删除节点,右子节点的最小值,用最小节点去更新删除节点
      const aux = this.findMinNode(node.right);
      node.key = aux.key;
      node.right = this.removeNode(node.right, aux.key);
      return node;
    }
    // 查找节点的最小值节点
    findMinNode(node) {
      while (node && node.left) {
        node = node.left;
      }
      return node;
    }
  }

  const tree = new BinarySearchTree();
  // 插入节点
  tree.insert(11);
  tree.insert(7);
  tree.insert(15);
  tree.insert(5);
  tree.insert(3);
  tree.insert(9);
  tree.insert(8);
  tree.insert(10);
  tree.insert(13);
  tree.insert(12);
  tree.insert(14);
  tree.insert(20);
  tree.insert(18);
  tree.insert(25);