【算法与数据结构】- 实现常用的数据结构

78 阅读19分钟

算法与数据结构

  • javascript
  • typescript

1、数据结构

  • 常用数据结构
    • 字符串
    • 数组
    • 链表
    • 队列
  • 高级数据结构
    • 前缀树
    • 线段树
    • 树状数组
    • 主席树

2、实现常用的数据结构

2.1 如何调试代码

2.1.1 如何调试 js 代码

安装一个可视化插件:Debug Visualizer

ctrl + shift + D 打开运行和调试界面,添加 nodejs 启动程序。

image.png

修改一下 launch.json 中的文件夹路径,匹配到你要调试的 js 文件。

image.png

F5 或者运行调试页面的的开始调试按钮。

F10 逐步调试。

image.png

ctrl + shift + p 打开 Debug Visualizer 界面。在界面中输入 stack 变量,可以看到该变量的可视化结果。

image.png

2.1.2 如何调试 ts 代码

全局安装下 typescript

npm install typescript -g

在项目下新建 tsconfig.json 文件:

{
  "compilerOptions": {
    "target": "es2015", // 编译出 JavaScript 代码的 ECMAScript 版本
    "module": "commonjs", // 编译出 JavaScript 文件的模块格式
    "outDir": "out", // 编译出 JavaScript 文件的存放位置;
    "sourceMap": true // 是否生成 SourceMap 文件;(不生成 SourceMap 不会进端点)
  },
  "include": ["ts/**/*"]
}

在 .vscode/ 中新建 tasks.json

{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "typescript",
      "tsconfig": "tsconfig.json",
      "problemMatcher": ["$tsc"],
      "group": "build",
      "label": "tsc: build - tsconfig.json"
    }
  ]
}

修改 .vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Program Js",
      "program": "${workspaceFolder}/js/index.js",
      "request": "launch",
      "skipFiles": ["<node_internals>/**"],
      "type": "node"
    },
    {
      "name": "Launch Program Ts",
      "program": "${workspaceFolder}/ts/index.ts",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "outFiles": ["${workspaceFolder}/out/**/*.js"],
      "request": "launch",
      "skipFiles": ["<node_internals>/**"],
      "type": "node"
    }
  ]
}

这样就可以调试了

image.png

3、实现栈

  • 受限的线性表,只允许在表的一端进行插入删除操作,这一端被称为栈顶,另一端称为栈底
  • 后进先出
  • 封装
    • push 增加新元素到栈顶
    • pop 移除栈顶的元素
    • peek 返回栈顶的元素,不对栈做任何修改
    • isEmpty 返回是否有元素值的布尔类型
    • size 返回栈里的元素个数
    • clear 清空栈中的元素
    • toString 将栈结构的内容以字符形式返回

3.1 js 版本

/**
 * * 实现一个栈结构
 * @constructor
 */
class Stack {
  constructor() {
    this.STACK_EMPTY_DESC = "栈是空的";
    this.items = [];
  }

  /**
   * 向栈中添加元素
   * @param {*} item
   */
  push(item) {
    this.items.push(item);
  }

  /**
   * 从栈中移除元素
   * @returns {Array<any> | string}
   */
  pop() {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items.pop();
  }

  /**
   * 查看栈顶元素
   * @returns
   */
  peek() {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items[this.items.length - 1];
  }

  /**
   * 检查栈是否空
   * @returns
   */
  isEmpty() {
    return this.items.length === 0 ? true : false;
  }

  /**
   * 获取栈中元素数量
   * @returns
   */
  size() {
    return this.items.length;
  }

  /**
   * 清空栈中所有元素
   */
  clear() {
    this.items = [];
  }
}

// 测试
const stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.peek()); // 输出: 2
console.log(stack.pop()); // 输出: 2
console.log(stack.size()); // 输出: 1
console.log(stack.isEmpty()); // 输出: false
stack.clear();
console.log(stack.isEmpty()); // 输出: true

3.2 ts 版本

ts 可以使用泛型

/**
 * * 实现一个栈结构
 * @constructor
 */
class Stack<T> {
  private items: T[] = [];
  private STACK_EMPTY_DESC = "栈是空的";

  /**
   * 向栈中添加元素
   * @param {T} item
   */
  push(item: T): void {
    this.items.push(item);
  }

  /**
   * 从栈中移除元素
   * @returns
   */
  pop(): T | undefined | string {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items.pop();
  }

  /**
   * 查看栈顶元素
   * @returns
   */
  peek(): T | string {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items[this.items.length - 1];
  }

  /**
   * 检查栈是否空
   * @returns
   */
  isEmpty(): boolean {
    return this.items.length === 0;
  }

  /**
   * 获取栈中元素数量
   * @returns
   */
  size(): number {
    return this.items.length;
  }

  /**
   * 清空栈中所有元素
   */
  clear(): void {
    this.items = [];
  }
}

// 测试
const stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.peek()); // 输出: 2
console.log(stack.pop()); // 输出: 2
console.log(stack.size()); // 输出: 1
console.log(stack.isEmpty()); // 输出: false
stack.clear();
console.log(stack.isEmpty()); // 输出: true

4、实现单向链表

实现单向链表

  • 数组的不足
    • 插入数据时时间复杂度是 O(n)
  • 相较数组链表的优势
    • 插入和删除数据时,时间复杂度可达到 O(1),相对于数组效率会高很多。
  • 链表的缺陷
    • 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素。

链表的结构:

  • 在链表中,每一个元素都包含两个属性:元素的值 item 和 下一个元素 next,next 指向下一个节点。
  • 链表中有一个指针 head 指向链表第一个元素,最后一个元素则指向 null。

链表的常见操作:

  • 向链表尾部添加一个新的项。append(element)
  • 向链表的特定位置插入一个新的项。insert(position,element)
  • 获取对应位置的元素。get(position)
  • 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
  • 修改某个位置的元素。update(position ,element)
  • 从列表的特定位置移除一项。removeAt(position)
  • 从链表中移除一项。remove(element)
  • 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false。isEmpty()
  • 返回链表包含的元素个数。与数组 length 属性类似。size()

4.1 js 版本

/**
 * * 单向链表-节点
 * @constructor
 */
class ListNode {
  constructor(element) {
    // 保存元素
    this.element = element;
    // 指向下一个节点
    this.next = null;
  }
}

/**
 * * 单向链表
 * @constructor
 */
class LinkedList {
  constructor() {
    // 头结点指针
    this.head = null;
    // 统计节点数量
    this.length = 0;
  }
  /**
   * 向链表尾部添加一个新的项
   * @param {*} element 链表节点
   */
  append(element) {
    // 根据 element 创建 Node 对象
    const newNode = new ListNode(element);
    // 追加到最后 判断链表结构 head 是否为空(为空就表示链表中没有任何节点),将新创建的节点赋值给 head,此时表示该链表中已有节点
    if (!this.head) {
      this.head = newNode;
    }
    // 如果 head 不为空(表示链表中已有节点),最开始定义一个变量将头节点(head)赋值给 current(也就是指向第一个节点)
    // 利用 while 循环依次判断是否存在下一个节点,如果不存在,就将这个新创建的节点赋值给最后一个节点的 next 属性
    else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = newNode;
    }
    this.length++;
  }
  /**
   * 向链表的特定位置插入一个新的项
   * @param {*} position 节点位置
   * @param {*} element 链表节点
   */
  insert(position, element) {
    // 判断输入的值是否越界
    if (position < 0 || position > this.length) {
      return false;
    }
    // 创建新的节点
    const newNode = new ListNode(element);
    // 插入元素
    // 判断插入元素的位置是否在头节点(head)
    if (position === 0) {
      // 插入的节点在第一个就将此时指向头节点的节点赋值给插入节点的next属性
      newNode.next = this.head;
      //之后再将新插入的节点赋值给头节点
      this.head = newNode;
    }
    // 在其他位置插入节点 ,需要将插入位置的新节点前一个节点指向该新节点,再将新节点的next属性指向后一个节点
    else {
      let index = 0;
      let current = this.head;
      // 插入节点的前一个位置
      let previous = null;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }

      previous.next = newNode;
      newNode.next = current;
    }
    this.length++;
    // 表示插入成功
    return true;
  }
  /**
   * 获取对应位置的元素
   * @param {*} position 节点位置
   */
  get(position) {
    if (position < 0 || position > this.length - 1) return null;
    // 查找指定位置的元素时,需要一个一个的往后找,直到找到指定位置将此位置的节点 next 属性赋值给变量 current
    let index = 0;
    let current = this.head;
    while (index++ < position) {
      current = current.next;
    }
    return current.element;
  }
  /**
   * 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
   * @param {*} element 链表节点
   */
  indexOf(element) {
    let current = this.head;
    let index = 0;

    while (current) {
      if (current.element === element) {
        return index;
      }
      index++;
      current = current.next;
    }
    return -1;
  }
  /**
   * 修改某个位置的元素
   * @param {*} position 节点位置
   * @param {*} element 链表节点
   * @returns
   */
  update(position, element) {
    let result = this.removeAt(position);
    this.insert(position, element);
    return result;
  }
  /**
   * 从列表的特定位置移除一项
   * @param {*} position 节点位置
   * @returns
   */
  removeAt(position) {
    if (position < 0 || position > this.length - 1) return null;

    let current = this.head;
    let previous = null;
    let index = 0;
    if (position === 0) {
      this.head = current.next;
    } else {
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
    }
    this.length--;
    return current.element;
  }
  /**
   * 从链表中移除一项
   * @param {*} element
   * @returns
   */
  remove(element) {
    const index = this.indexOf(element);
    if (index === -1) return;
    this.removeAt(index);
  }

  /**
   * 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false
   * @returns
   */
  isEmpty() {
    return this.length === 0;
  }

  /**
   * 返回链表包含的元素个数。与数组length属性类似
   * @returns
   */
  size() {
    return this.length;
  }
}

const linkedList = new LinkedList();

// 测试向链表添加节点
linkedList.append("a");
linkedList.append("b");
linkedList.append("c");
linkedList.append("d");
linkedList.append("e");

console.log(linkedList);

// 测试向指定位置插入节点
const res = linkedList.insert("1", "f");

console.log(linkedList, res);

// 测试获取指定位置的元素
console.log(linkedList.get(1));

// 测试通过元素获取下标值{}
console.log(linkedList.indexOf("b"));

// 测试从列表的特定位置移除一项
console.log(linkedList.removeAt(4));
console.log(linkedList);

// 测试修改某个位置的元素
console.log(linkedList.update(1, "www"));
console.log(linkedList);

// 测试从链表中移除一项
console.log(linkedList.remove("a"));
console.log(linkedList);

image.png

4.2 ts 版本

/**
 * * 单向链表-节点
 * @constructor
 */
class ListNode<T> {
  element: T;
  next: ListNode<T> | null;

  constructor(element: T) {
    // 保存元素
    this.element = element;
    // 指向下一个节点
    this.next = null;
  }
}

/**
 * * 单向链表
 * @constructor
 */
class LinkedList<T> {
  private head: ListNode<T> | null;
  private length: number;

  constructor() {
    // 头结点指针
    this.head = null;
    // 统计节点数量
    this.length = 0;
  }
  /**
   * 向链表尾部添加一个新的项
   * @param {*} element 链表节点
   */
  append(element: T) {
    // 根据 element 创建 Node 对象
    const newNode = new ListNode(element);
    // 追加到最后 判断链表结构 head 是否为空(为空就表示链表中没有任何节点),将新创建的节点赋值给 head,此时表示该链表中已有节点
    if (!this.head) {
      this.head = newNode;
    }
    // 如果 head 不为空(表示链表中已有节点),最开始定义一个变量将头节点(head)赋值给 current(也就是指向第一个节点)
    // 利用 while 循环依次判断是否存在下一个节点,如果不存在,就将这个新创建的节点赋值给最后一个节点的 next 属性
    else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = newNode;
    }
    this.length++;
  }
  /**
   * 向链表的特定位置插入一个新的项
   * @param {*} position 节点位置
   * @param {*} element 链表节点
   */
  insert(position: number, element: T) {
    // 判断输入的值是否越界
    if (position < 0 || position > this.length) {
      return false;
    }
    // 创建新的节点
    const newNode = new ListNode(element);
    // 插入元素
    // 判断插入元素的位置是否在头节点(head)
    if (position === 0) {
      // 插入的节点在第一个就将此时指向头节点的节点赋值给插入节点的next属性
      newNode.next = this.head;
      //之后再将新插入的节点赋值给头节点
      this.head = newNode;
    }
    // 在其他位置插入节点 ,需要将插入位置的新节点前一个节点指向该新节点,再将新节点的next属性指向后一个节点
    else {
      let index = 0;
      let current = this.head;
      // 插入节点的前一个位置
      let previous = null;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }

      previous.next = newNode;
      newNode.next = current;
    }
    this.length++;
    // 表示插入成功
    return true;
  }
  /**
   * 获取对应位置的元素
   * @param {*} position 节点位置
   */
  get(position: number) {
    if (position < 0 || position > this.length - 1) return null;
    // 查找指定位置的元素时,需要一个一个的往后找,直到找到指定位置将此位置的节点 next 属性赋值给变量 current
    let index = 0;
    let current = this.head;
    while (index++ < position) {
      current = current.next;
    }
    return current.element;
  }
  /**
   * 返回元素在链表中的索引。如果链表中没有该元素则返回-1。indexOf(element)
   * @param {*} element 链表节点
   */
  indexOf(element: T) {
    let current = this.head;
    let index = 0;

    while (current) {
      if (current.element === element) {
        return index;
      }
      index++;
      current = current.next;
    }
    return -1;
  }
  /**
   * 修改某个位置的元素
   * @param {*} position 节点位置
   * @param {*} element 链表节点
   * @returns
   */
  update(position: number, element: T) {
    let result = this.removeAt(position);
    this.insert(position, element);
    return result;
  }
  /**
   * 从列表的特定位置移除一项
   * @param {*} position 节点位置
   * @returns
   */
  removeAt(position: number) {
    if (position < 0 || position > this.length - 1) return null;

    let current = this.head;
    let previous = null;
    let index = 0;
    if (position === 0) {
      this.head = current.next;
    } else {
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
    }
    this.length--;
    return current.element;
  }
  /**
   * 从链表中移除一项
   * @param {*} element
   * @returns
   */
  remove(element: T) {
    const index = this.indexOf(element);
    if (index === -1) return;
    this.removeAt(index);
  }

  /**
   * 如果链表中不包含任何元素,返回 true,如果链表的长度大于 0 则返回 false
   * @returns
   */
  isEmpty() {
    return this.length === 0;
  }

  /**
   * 返回链表包含的元素个数。与数组length属性类似
   * @returns
   */
  size() {
    return this.length;
  }
}

const linkedList = new LinkedList();

// 测试向链表添加节点
linkedList.append("a");
linkedList.append("b");
linkedList.append("c");
linkedList.append("d");
linkedList.append("e");

console.log(linkedList);

// 测试向指定位置插入节点
const res = linkedList.insert(1, "f");

console.log(linkedList, res);

// 测试获取指定位置的元素
console.log(linkedList.get(1));

// 测试通过元素获取下标值{}
console.log(linkedList.indexOf("b"));

// 测试从列表的特定位置移除一项
console.log(linkedList.removeAt(4));
console.log(linkedList);

// 测试修改某个位置的元素
console.log(linkedList.update(1, "www"));
console.log(linkedList);

// 测试从链表中移除一项
console.log(linkedList.remove("a"));
console.log(linkedList);

image.png

5、实现双向链表

  • 单向链表的缺陷:返回前一个节点(需要从头再开始遍历)

实现的原理

  • 基于单向链表实现
  • 在每个节点中添加一个指向上一个节点的引用(prev),第一个节点的 prev 指向的是 null,最后一个节点的 next 指向的也是 null

缺陷

  • 在插入或者删除某个节点时,需要处理四个引用
  • 占用的内存空间更大

5.1 js 版本

/**
 * * 双向链表-节点
 */
class DoublyNode extends ListNode {
  constructor(element) {
    super(element);
    this.prev = null;
  }
}

/**
 * * 双向链表
 */
class DoublyLinkedList extends LinkedList {
  constructor() {
    super();
    this.tail = null;
  }

  /**
   * 向链表尾部添加一个新的项
   * @param {*} element
   */
  append(element) {
    const newNode = new DoublyNode(element);

    if (this.head === null) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      this.tail.next = newNode;
      newNode.prev = this.tail;
      this.tail = newNode;
    }
    this.length++;
  }

  /**
   * 向链表的特定位置插入一个新的项
   * @param {*} position
   * @param {*} element
   * @returns
   */
  insert(position, element) {
    if (position < 0 || position > this.length) return false;

    const newNode = new DoublyNode(element);

    if (position === 0) {
      if (this.head == null) {
        this.head = newNode;
        this.tail = newNode;
      } else {
        newNode.next = this.head;
        this.head.prev = newNode;
        this.head = newNode;
      }
    } else if (position === this.length) {
      this.tail.next = newNode;
      newNode.prev = this.tail;
      this.tail = newNode;
    } else {
      let index = 0;
      let current = this.head;
      let previous = null;

      while (index++ < position) {
        previous = current;
        current = current.next;
      }

      previous.next = newNode;
      newNode.prev = previous;
      newNode.next = current;
      current.prev = newNode;
    }
  }

  /**
   * 获取对应位置的元素
   * @param {*} position
   * @returns
   */
  removeAt(position) {
    if (position < 0 || position > this.length - 1) return null;

    let current = this.head;

    if (position === 0) {
      if (this.length === 1) {
        this.head = null;
        this.tail = null;
      } else {
        this.head = this.head.next;
        this.head.prev = null;
      }
    } else if (position === this.length - 1) {
      current = this.tail;
      this.tail.prev.next = null;
      this.tail = this.tail.prev;
    } else {
      let index = 0;
      let previous = null;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
      current.next.prev = previous;
    }
    return current.element;
  }
}

const doublyLinkedList = new DoublyLinkedList();

doublyLinkedList.append("a");
doublyLinkedList.append("b");
doublyLinkedList.append("c");
doublyLinkedList.append("d");
doublyLinkedList.append("e");
doublyLinkedList.append("f");

console.log(doublyLinkedList);

doublyLinkedList.insert(1, "g");
console.log(doublyLinkedList);

console.log(doublyLinkedList.get(1));

console.log(doublyLinkedList.indexOf("g"));

console.log(doublyLinkedList.removeAt(1));

console.log(doublyLinkedList.update(0, "nnn"));

console.log(doublyLinkedList.remove("f"));

image.png

5.2 ts 版本

/**
 * * 双向链表-节点
 */
class DoublyNode<T> extends ListNode<any> {
  prev: DoublyNode<T> | null;

  constructor(element: T) {
    super(element);
    this.prev = null;
  }
}

/**
 * * 双向链表
 */
class DoublyLinkedList<T> extends LinkedList<any> {
  tail: DoublyNode<T> | null;

  constructor() {
    super();
    this.tail = null;
  }

  /**
   * 向链表尾部添加一个新的项
   * @param {*} element
   */
  append(element: T) {
    const newNode = new DoublyNode(element);

    if (this.head === null) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      this.tail.next = newNode;
      newNode.prev = this.tail;
      this.tail = newNode;
    }
    this.length++;
  }

  /**
   * 向链表的特定位置插入一个新的项
   * @param {*} position
   * @param {*} element
   * @returns
   */
  insert(position: number, element: T) {
    if (position < 0 || position > this.length) return false;

    const newNode = new DoublyNode(element);

    if (position === 0) {
      if (this.head == null) {
        this.head = newNode;
        this.tail = newNode;
      } else {
        newNode.next = this.head;
        this.head.prev = newNode;
        this.head = newNode;
      }
    } else if (position === this.length) {
      this.tail.next = newNode;
      newNode.prev = this.tail;
      this.tail = newNode;
    } else {
      let index = 0;
      let current = this.head;
      let previous = null;

      while (index++ < position) {
        previous = current;
        current = current.next;
      }

      previous.next = newNode;
      newNode.prev = previous;
      newNode.next = current;
      current.prev = newNode;
    }
  }

  /**
   * 获取对应位置的元素
   * @param {*} position
   * @returns
   */
  removeAt(position: number) {
    if (position < 0 || position > this.length - 1) return null;

    let current = this.head;

    if (position === 0) {
      if (this.length === 1) {
        this.head = null;
        this.tail = null;
      } else {
        this.head = this.head.next;
        this.head.prev = null;
      }
    } else if (position === this.length - 1) {
      current = this.tail;
      this.tail.prev.next = null;
      this.tail = this.tail.prev;
    } else {
      let index = 0;
      let previous = null;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
      current.next.prev = previous;
    }
    return current.element;
  }
}

const doublyLinkedList = new DoublyLinkedList();

doublyLinkedList.append("a");
doublyLinkedList.append("b");
doublyLinkedList.append("c");
doublyLinkedList.append("d");
doublyLinkedList.append("e");
doublyLinkedList.append("f");

console.log(doublyLinkedList);

doublyLinkedList.insert(1, "g");
console.log(doublyLinkedList);

console.log(doublyLinkedList.get(1));

console.log(doublyLinkedList.indexOf("g"));

console.log(doublyLinkedList.removeAt(1));

console.log(doublyLinkedList.update(0, "nnn"));

console.log(doublyLinkedList.remove("f"));

image.png

6、实现队列

6.1 js 版本

/**
 * * 使用数组实现队列
 */
class Queue {
  constructor() {
    this.STACK_EMPTY_DESC = "队列是空的";
    this.items = [];
  }
  /**
   * 入队操作
   * @param {*} element
   */
  enqueue(element) {
    this.items.push(element);
  }
  /**
   * 出队操作
   */
  dequeue() {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items.shift();
  }
  /**
   * 查看队首元素
   * @returns
   */
  front() {
    if (this.isEmpty()) {
      return "队列为空";
    }
    return this.items[0];
  }
  /**
   * 检查队列是否为空
   */
  isEmpty() {
    return this.items.length === 0;
  }
  /**
   * 获取队列的大小
   */
  size() {
    return this.items.length;
  }
  /**
   * 打印队列内容(可选)
   */
  print() {
    console.log(this.items.toString());
  }
  /**
   * 清空队列
   */
  clear() {
    this.items = [];
  }
}

console.log("==queue==");
const queue = new Queue();
console.log(queue.isEmpty()); // 应该输出 true,因为队列为空
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.print(); // 应该输出 1,2,3,表示元素按顺序入队成功
console.log(queue.front()); // 应该输出 1,查看队首元素
console.log(queue.dequeue()); // 应该输出 1,并从队列中移除它
console.log(queue.size()); // 应该输出 2,因为现在队列中有两个元素了
console.log(queue.isEmpty()); // 应该输出 false,因为队列不为空了
queue.print(); // 应该输出 2,3,表示元素按顺序出队成功后的队列状态

image.png

6.2 ts 版本

/**
 * * 使用数组实现队列
 */
class Queue<T> {
  STACK_EMPTY_DESC: string;
  items: Array<T>;

  constructor() {
    this.STACK_EMPTY_DESC = "队列是空的";
    this.items = [];
  }
  /**
   * 入队操作
   * @param {*} element
   */
  enqueue(element: T) {
    this.items.push(element);
  }
  /**
   * 出队操作
   */
  dequeue() {
    if (this.isEmpty()) {
      return this.STACK_EMPTY_DESC;
    }
    return this.items.shift();
  }
  /**
   * 查看队首元素
   * @returns
   */
  front() {
    if (this.isEmpty()) {
      return "队列为空";
    }
    return this.items[0];
  }
  /**
   * 检查队列是否为空
   */
  isEmpty() {
    return this.items.length === 0;
  }
  /**
   * 获取队列的大小
   */
  size() {
    return this.items.length;
  }
  /**
   * 打印队列内容(可选)
   */
  print() {
    console.log(this.items.toString());
  }
  /**
   * 清空队列
   */
  clear() {
    this.items = [];
  }
}

console.log("==queue==");
const queue = new Queue();
console.log(queue.isEmpty()); // 应该输出 true,因为队列为空
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.print(); // 应该输出 1,2,3,表示元素按顺序入队成功
console.log(queue.front()); // 应该输出 1,查看队首元素
console.log(queue.dequeue()); // 应该输出 1,并从队列中移除它
console.log(queue.size()); // 应该输出 2,因为现在队列中有两个元素了
console.log(queue.isEmpty()); // 应该输出 false,因为队列不为空了
queue.print(); // 应该输出 2,3,表示元素按顺序出队成功后的队列状态

image.png

7、二叉树

【前端面试手写必备】树 🌲&实现树结构

7.1 js 版本

/**
 * * 实现二叉树
 */

/** 节点类:保存节点信息和指针 */
class TreeNode {
  left;
  right;
  key;
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

/** 二叉搜索树 */
class BinarySearchTree {
  root;
  constructor(key) {
    this.root = null;
  }

  /**
   * 插入节点
   * @param {*} key
   */
  insert(key) {
    const newNode = new TreeNode(key);
    // 如果根节点为空
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  /**
   * 插入节点的方法
   * @param {*} root
   * @param {*} newNode
   */
  insertNode(root, newNode) {
    // 如果已经有了,则禁止插入
    if (root.key === newNode.key) {
      console.log("======>该节点已经插入过了");
    } else if (root.key > newNode.key) {
      // 左侧查找
      if (root.left === null) {
        root.left = newNode;
      } else {
        this.insertNode(root.left, newNode);
      }
    } else {
      // 右侧查找
      if (root.right === null) {
        root.right = newNode;
      } else {
        this.insertNode(root.right, newNode);
      }
    }
  }

  /**
   * 先序遍历
   * @param {*} root
   * @param {*} handler
   */
  preOrderTraversal(handler) {
    this.preOrderTranversalNode(this.root, handler);
  }

  preOrderTranversalNode(root, handler) {
    // 边界判断
    if (!root) return;
    // 访问根节点
    handler(root.key);
    // 访问左子树
    this.preOrderTranversalNode(root.left, handler);
    // 访问右子树
    this.preOrderTranversalNode(root.right, handler);
  }

  /**
   * 中序遍历
   * @param {*} handler
   */
  inOrderTraversal(handler) {
    this.inOrderTraversalNode(this.root, handler);
  }

  inOrderTraversalNode(root, handler) {
    if (!root) return;
    this.inOrderTraversalNode(root.left, handler);
    handler(root.key);
    this.inOrderTraversalNode(root.right, handler);
  }

  /**
   * 后序遍历
   * @param {*} handler
   */
  postOrderTraversal(handler) {
    this.postOrderTraversalNode(this.root, handler);
  }

  postOrderTraversalNode(root, handler) {
    if (!root) return;
    this.postOrderTraversalNode(root.left, handler);
    this.postOrderTraversalNode(root.right, handler);
    handler(root.key);
  }

  min() {
    let node = this.root;
    while (node.left !== null) {
      node = node.left;
    }
    return node.key;
  }

  max() {
    let node = this.root;
    while (node.right !== null) {
      node = node.right;
    }
    return node.key;
  }

  // 递归的方式
  search(key) {
    return this.searchNode(this.root, key);
  }

  searchNode(root, key) {
    if (!root) return false;
    if (root.key < key) {
      return this.searchNode(root.right, key);
    } else if (root.key > key) {
      return this.searchNode(root.left, key);
    } else {
      return true;
    }
  }

  // 非递归的方式
  searchV2(key) {
    return this.searchNodeV2(this.root, key);
  }

  searchNodeV2(root, key) {
    if (!root) return false;
    while (root !== null) {
      if (root.key < key) {
        root = root.right;
      } else if (root.key > key) {
        root = root.left;
      } else {
        return true;
      }
    }
    return false;
  }

  remove(key) {
    // 当前遍历到的节点
    let current = this.root;
    // 当前节点的父级节点,因为需要更改父级指针
    let parent = this.root;
    // 记录是父级节点的左子节点还是右子节点
    let isLeftChild = false;
    // 查找需要删除的节点
    while (current.key !== key) {
      parent = current;
      // 左子树查找
      if (key < current.key) {
        isLeftChild = true;
        current = current.left;
      } else {
        // 右子树查找
        isLeftChild = false;
        current = current.right;
      }

      if (current === null) return false;
    }
    // 1.第一种情况,删除的是叶节点,考虑是否为根元素和非根元素两种情况
    // 如果是根元素直接删除,如果不是根则根据isLeftChild,将parent的left||right置为null
    if (current.left === null && current.right === null) {
      // 只有一个根元素
      if (current === this.root) {
        this.root = null;
      } else if (isLeftChild) {
        parent.left = null;
      } else {
        parent.right = null;
      }
    }
    // 2.第二种情况,当前遍历到的节点有一个子节点,考虑current是否为根和非根两种情况
    // 左子树不为null,current只有左子树,这里赋值的都是current.left;
    else if (current.right === null) {
      if (current === this.root) {
        // 这里是因为右子树为null
        this.root = current.left;
      } else if (isLeftChild) {
        // 注意isLeftChild是用来决定使用的是parent的哪个指针
        parent.left = current.left;
      } else {
        parent.right = current.left;
      }
    } else if (current.left === null) {
      // 右子树不为null,current只有左子树,这里赋值的都是current.left;
      if (current === this.root) {
        this.root = current.right;
      } else if (isLeftChild) {
        parent.left = current.right;
      } else {
        parent.right = current.right;
      }
    }

    // 3.第三种情况,左右子树都有,这时候需要考虑使用查找前驱、后继节点来替换删除的节点
    // 前驱:比current小一点点的节点, 称为current节点的前驱.
    // 后继:比current大一点点的节点, 称为current节点的后继.
    else {
      // 1.获取后继节点
      let successor = this.getSuccessor(current);

      // 2.判断是否是根节点
      if (current == this.root) {
        this.root = successor;
      } else if (isLeftChild) {
        parent.left = successor;
      } else {
        parent.right = successor;
      }

      // 3.将删除节点的左子树赋值给successor
      successor.left = current.left;
    }
    return true;
  }

  getSuccessor(delNode) {
    let parent = delNode;
    let target = delNode;
    // 从右子树开始
    let current = delNode.right;
    // 查找后继,左子树最小节点
    while (current !== null) {
      parent = target;
      target = current;
      current = current.left;
    }

    // 对于跨域层级的删除
    if (target != delNode.right) {
      parent.left = target.right;
      target.right = delNode.right;
    }

    return target;
  }
}

// 测试代码
const bst = new BinarySearchTree();

// 插入数据
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
bst.insert(6);

// 测试前序遍历结果
let resultString = "";
bst.preOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 先序遍历resultString:", resultString); // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25

resultString = "";
bst.inOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 中序遍历resultString:", resultString); // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25

resultString = "";
bst.postOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

// 获取最值
console.log("✅ ~ bst.min():", bst.min());
console.log("✅ ~ bst.max():", bst.max());

// 查找特定的值
console.log("✅ ~ bst.search(10):", bst.search(10));
console.log("✅ ~ bst.search(21):", bst.search(21));

// 查找特定的值
console.log("✅ ~ bst.searchV2(10):", bst.searchV2(10));
console.log("✅ ~ bst.searchV2(21):", bst.searchV2(21));

// 删除测试
console.log("✅ ~ bst.searchV2(10):", bst.remove(7));
resultString = "";
bst.postOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

image.png

7.2 ts 版本

/**
 * * 实现二叉树
 */

/** 节点类:保存节点信息和指针 */
class TreeNode<T> {
  left: null;
  right: null;
  key: T;
  constructor(key: T) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

/** 二叉搜索树 */
class BinarySearchTree<T> {
  root: TreeNode<T>;
  constructor() {
    this.root = null;
  }

  /**
   * 插入节点
   * @param {*} key
   */
  insert(key: T) {
    const newNode = new TreeNode(key);
    // 如果根节点为空
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  /**
   * 插入节点的方法
   * @param {*} root
   * @param {*} newNode
   */
  insertNode(root, newNode) {
    // 如果已经有了,则禁止插入
    if (root.key === newNode.key) {
      console.log("======>该节点已经插入过了");
    } else if (root.key > newNode.key) {
      // 左侧查找
      if (root.left === null) {
        root.left = newNode;
      } else {
        this.insertNode(root.left, newNode);
      }
    } else {
      // 右侧查找
      if (root.right === null) {
        root.right = newNode;
      } else {
        this.insertNode(root.right, newNode);
      }
    }
  }

  /**
   * 先序遍历
   * @param {*} root
   * @param {*} handler
   */
  preOrderTraversal(handler) {
    this.preOrderTranversalNode(this.root, handler);
  }

  preOrderTranversalNode(root, handler) {
    // 边界判断
    if (!root) return;
    // 访问根节点
    handler(root.key);
    // 访问左子树
    this.preOrderTranversalNode(root.left, handler);
    // 访问右子树
    this.preOrderTranversalNode(root.right, handler);
  }

  /**
   * 中序遍历
   * @param {*} handler
   */
  inOrderTraversal(handler) {
    this.inOrderTraversalNode(this.root, handler);
  }

  inOrderTraversalNode(root, handler) {
    if (!root) return;
    this.inOrderTraversalNode(root.left, handler);
    handler(root.key);
    this.inOrderTraversalNode(root.right, handler);
  }

  /**
   * 后序遍历
   * @param {*} handler
   */
  postOrderTraversal(handler) {
    this.postOrderTraversalNode(this.root, handler);
  }

  postOrderTraversalNode(root, handler) {
    if (!root) return;
    this.postOrderTraversalNode(root.left, handler);
    this.postOrderTraversalNode(root.right, handler);
    handler(root.key);
  }

  min() {
    let node = this.root;
    while (node.left !== null) {
      node = node.left;
    }
    return node.key;
  }

  max() {
    let node = this.root;
    while (node.right !== null) {
      node = node.right;
    }
    return node.key;
  }

  // 递归的方式
  search(key) {
    return this.searchNode(this.root, key);
  }

  searchNode(root, key) {
    if (!root) return false;
    if (root.key < key) {
      return this.searchNode(root.right, key);
    } else if (root.key > key) {
      return this.searchNode(root.left, key);
    } else {
      return true;
    }
  }

  // 非递归的方式
  searchV2(key: T) {
    return this.searchNodeV2(this.root, key);
  }

  searchNodeV2(root, key: T) {
    if (!root) return false;
    while (root !== null) {
      if (root.key < key) {
        root = root.right;
      } else if (root.key > key) {
        root = root.left;
      } else {
        return true;
      }
    }
    return false;
  }

  remove(key: T) {
    // 当前遍历到的节点
    let current = this.root;
    // 当前节点的父级节点,因为需要更改父级指针
    let parent = this.root;
    // 记录是父级节点的左子节点还是右子节点
    let isLeftChild = false;
    // 查找需要删除的节点
    while (current.key !== key) {
      parent = current;
      // 左子树查找
      if (key < current.key) {
        isLeftChild = true;
        current = current.left;
      } else {
        // 右子树查找
        isLeftChild = false;
        current = current.right;
      }

      if (current === null) return false;
    }
    // 1.第一种情况,删除的是叶节点,考虑是否为根元素和非根元素两种情况
    // 如果是根元素直接删除,如果不是根则根据isLeftChild,将parent的left||right置为null
    if (current.left === null && current.right === null) {
      // 只有一个根元素
      if (current === this.root) {
        this.root = null;
      } else if (isLeftChild) {
        parent.left = null;
      } else {
        parent.right = null;
      }
    }
    // 2.第二种情况,当前遍历到的节点有一个子节点,考虑current是否为根和非根两种情况
    // 左子树不为null,current只有左子树,这里赋值的都是current.left;
    else if (current.right === null) {
      if (current === this.root) {
        // 这里是因为右子树为null
        this.root = current.left;
      } else if (isLeftChild) {
        // 注意isLeftChild是用来决定使用的是parent的哪个指针
        parent.left = current.left;
      } else {
        parent.right = current.left;
      }
    } else if (current.left === null) {
      // 右子树不为null,current只有左子树,这里赋值的都是current.left;
      if (current === this.root) {
        this.root = current.right;
      } else if (isLeftChild) {
        parent.left = current.right;
      } else {
        parent.right = current.right;
      }
    }

    // 3.第三种情况,左右子树都有,这时候需要考虑使用查找前驱、后继节点来替换删除的节点
    // 前驱:比current小一点点的节点, 称为current节点的前驱.
    // 后继:比current大一点点的节点, 称为current节点的后继.
    else {
      // 1.获取后继节点
      let successor = this.getSuccessor(current);

      // 2.判断是否是根节点
      if (current == this.root) {
        this.root = successor;
      } else if (isLeftChild) {
        parent.left = successor;
      } else {
        parent.right = successor;
      }

      // 3.将删除节点的左子树赋值给successor
      successor.left = current.left;
    }
    return true;
  }

  getSuccessor(delNode) {
    let parent = delNode;
    let target = delNode;
    // 从右子树开始
    let current = delNode.right;
    // 查找后继,左子树最小节点
    while (current !== null) {
      parent = target;
      target = current;
      current = current.left;
    }

    // 对于跨域层级的删除
    if (target != delNode.right) {
      parent.left = target.right;
      target.right = delNode.right;
    }

    return target;
  }
}

// 测试代码
const bst = new BinarySearchTree();

// 插入数据
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
bst.insert(6);

// 测试前序遍历结果
let resultString = "";
bst.preOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 先序遍历resultString:", resultString); // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25

resultString = "";
bst.inOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 中序遍历resultString:", resultString); // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25

resultString = "";
bst.postOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

// 获取最值
console.log("✅ ~ bst.min():", bst.min());
console.log("✅ ~ bst.max():", bst.max());

// 查找特定的值
console.log("✅ ~ bst.search(10):", bst.search(10));
console.log("✅ ~ bst.search(21):", bst.search(21));

// 查找特定的值
console.log("✅ ~ bst.searchV2(10):", bst.searchV2(10));
console.log("✅ ~ bst.searchV2(21):", bst.searchV2(21));

// 删除测试
console.log("✅ ~ bst.searchV2(10):", bst.remove(7));
resultString = "";
bst.postOrderTraversal(function (key) {
  resultString += key + " ";
});
console.log("✅ ~ 后序遍历resultString:", resultString); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

image.png

排序算法

  • 冒泡排序
  • 计数排序
  • 快速排序
  • 归并排序
  • 插入排序
  • 选择排序

练习题目