TypeScript数据结构与算法:集合

1,325 阅读2分钟

上一篇《TypeScript 数据结构与算法:链表》实现了 Typescript 中链表的数据结构与算法,本篇继续实现集合。

集合 是由一组无序且唯一(即不能重复)的项组成的。该数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。

集合(Set)之间的运算包括并集、交集、差集等,如下图所示:

集合运算.png

数据结构

集合

一个集合中,应该有以下方法:

  • add(element):向集合添加一个新元素。
  • delete(element):从集合移除一个元素。
  • has(element):如果元素在集合中,返回 true,否则返回 false
  • clear():移除集合中的所有元素。
  • size():返回集合所包含元素的数量。它与数组的 length 属性类似。
  • values():返回一个包含集合中所有值(元素)的数组。
  • union(otherSet):返回与其他集合的并集。
  • intersection(otherSet):返回与其他集合的交集。
  • difference(otherSet):返回与其他集合的差集。
  • isSubsetOf(otherSet):返回是否为其他集合的子集。

ES2015 后,已经存在原生的 Set 数据结构了,但是没有上面所述的所有方法,所以以原生 Set 为基础来构造一个 CustomSet

export default class CustomSet<T> {
  private items: Set<T>;

  constructor(array?: T[]) {
    this.items = new Set(array);
  }

  /**
   * @description: 向集合添加一个新元素。
   * @param {T} element
   * @return {boolean} 是否添加成功
   */
  add(element: T): boolean {
    if (!this.has(element)) {
      this.items.add(element);
      return true;
    }
    return false;
  }

  /**
   * @description: 从集合移除一个元素。
   * @param {T} element
   * @return {boolean} 是否删除成功
   */
  delete(element: T): boolean {
    if (this.has(element)) {
      return this.items.delete(element);
    }
    return false;
  }

  /**
   * @description: 如果元素在集合中,返回 true,否则返回 false。
   * @param {T} element
   * @return {boolean}
   */
  has(element: T): boolean {
    return this.items.has(element);
  }

  /**
   * @description: 返回一个包含集合中所有值(元素)的数组。
   * @return {Array<T>}
   */
  values(): T[] {
    return [...this.items];
  }

  /**
   * @description: 并集
   * @param {CustomSet} otherSet
   * @return {CustomSet}
   */
  union(otherSet: CustomSet<T>): CustomSet<T> {
    const unionSet = new CustomSet<T>();

    // 迭代两个集合,把元素都add进来
    this.values().forEach((value) => unionSet.add(value));
    otherSet.values().forEach((value) => unionSet.add(value));

    return unionSet;
  }

  /**
   * @description: 交集
   * @param {CustomSet} otherSet
   * @return {CustomSet}
   */
  intersection(otherSet: CustomSet<T>): CustomSet<T> {
    const intersectionSet = new CustomSet<T>();

    // 在当前集合中过滤掉otherSet中不存在的元素
    this.values()
      .filter((v) => otherSet.has(v))
      .forEach((v) => {
        intersectionSet.add(v);
      });

    return intersectionSet;
  }

  /**
   * @description: 差集
   * @param {CustomSet} otherSet
   * @return {CustomSet}
   */
  difference(otherSet: CustomSet<T>): CustomSet<T> {
    const differenceSet = new CustomSet<T>();

    // 在当前集合中过滤掉otherSet中也存在的元素
    this.values()
      .filter((v) => !otherSet.has(v))
      .forEach((v) => {
        differenceSet.add(v);
      });

    return differenceSet;
  }

  /**
   * @description: 是否为子集
   * @param {CustomSet} otherSet
   * @return {boolean}
   */
  isSubsetOf(otherSet: CustomSet<T>): boolean {
    if (this.size() > otherSet.size()) {
      return false;
    }

    let isSubset = true;
    // 判据:当前集合的所有元素在otherSet中都存在
    this.values().forEach((value) => {
      if (!otherSet.has(value)) {
        isSubset = false;
      }
    });

    return isSubset;
  }

  /**
   * @description: 是否为空
   * @return {boolean}
   */
  isEmpty(): boolean {
    return this.size() === 0;
  }

  /**
   * @description: 集合的元素数
   * @return {number}
   */
  size(): number {
    return this.items.size;
  }

  /**
   * @description: 清空集合
   */
  clear() {
    this.items = new Set();
  }

  /**
   * @description: 替换原生toString
   * @return {string}
   */
  toString(): string {
    return `${this.values()}`;
  }
}

多重集

集合数据结构不允许存在重复的元素。但是,在数学中有一个叫作 多重集 的概念,允许我们向集合中插入重复元素。多重集(MultiSet)在计算集合中元素的出现次数时很有用。

多重集.png

多重集有一些方法和普通集合有区别:

  • count(element):返回集合中指定元素的数目。
  • set(element, count):将集合中指定元素设为 count 个。
  • add(element, count):向集合添加 count 个指定元素。
  • remove(element, count):在集合删除 count 个指定元素。
  • delete(element):从集合移除所有指定元素。
  • dimension():返回集合所包含元素的种类数。它与数组的 length 属性类似。

具体实现如下:

export default class MultiSet<T> {
  private items: Map<T, number>;

  constructor() {
    this.items = new Map();
  }

  /**
   * @description: 如果元素在集合中,返回 true,否则返回 false。
   * @param {T} element
   * @return {boolean}
   */
  has(element: T): boolean {
    return this.items.has(element);
  }

  /**
   * @description: 返回元素的维数,也就是元素的种类数
   * @return {number}
   */
  dimension(): number {
    return this.items.size;
  }

  /**
   * @description: 返回该元素的个数
   * @param {T} element
   * @return {number}
   */
  count(element: T): number {
    return this.items.get(element) ?? 0;
  }

  /**
   * @description: 删除所有的该元素
   * @param {T} element
   * @return {boolean}
   */
  delete(element: T): boolean {
    return this.items.delete(element);
  }

  /**
   * @description: 设置该元素的个数
   * @param {T} element
   * @param {number} count
   * @return {boolean}
   */
  set(element: T, count: number): boolean {
    if (count <= 0) {
      return this.delete(element);
    }
    this.items.set(element, count);
    return true;
  }

  /**
   * @description: 给该元素添加count个
   * @param {T} element
   * @param {number} count
   * @return {boolean}
   */
  add(element: T, count: number = 1): boolean {
    let newCount = this.count(element) + count;
    return this.set(element, newCount);
  }

  /**
   * @description: 给该元素移除count个
   * @param {T} element
   * @param {number} count
   * @return {boolean}
   */
  remove(element: T, count: number = 1): boolean {
    return this.add(element, -count);
  }

  /**
   * @description: 是否为空
   * @return {boolean}
   */
  isEmpty(): boolean {
    return this.dimension() === 0;
  }

  // 👇 以下方法与CustomSet的同名方法实现一致

  /**
   * @description: 返回一个包含集合中所有值(元素)的数组。
   * @return {Array<T>}
   */
  values(): T[] {
    return Array.from(this.items.entries()).reduce((acc, cur) => {
      const [key, value] = cur;
      for (let i = 0; i < value; i++) {
        acc.push(key);
      }
      return acc;
    }, []);
  }

  /**
   * @description: 集合的元素数
   * @return {number}
   */
  size(): number {
    return this.values().length;
  }

  /**
   * @description: 清空集合
   */
  clear() {
    this.items = new Map();
  }

  /**
   * @description: 并集
   * @param {MultiSet} otherSet
   * @return {MultiSet}
   */
  union(otherSet: MultiSet<T>): MultiSet<T> {
    const unionSet = new MultiSet<T>();

    // 迭代两个集合,把元素都add进来
    this.values().forEach((value) => unionSet.add(value));
    otherSet.values().forEach((value) => unionSet.add(value));

    return unionSet;
  }

  /**
   * @description: 交集
   * @param {MultiSet} otherSet
   * @return {MultiSet}
   */
  intersection(otherSet: MultiSet<T>): MultiSet<T> {
    const intersectionSet = new MultiSet<T>();

    // 在当前集合中过滤掉otherSet中不存在的元素
    this.values()
      .filter((v) => otherSet.has(v))
      .forEach((v) => {
        intersectionSet.add(v);
      });

    return intersectionSet;
  }

  /**
   * @description: 差集
   * @param {MultiSet} otherSet
   * @return {MultiSet}
   */
  difference(otherSet: MultiSet<T>): MultiSet<T> {
    const differenceSet = new MultiSet<T>();

    // 在当前集合中过滤掉otherSet中也存在的元素
    this.values()
      .filter((v) => !otherSet.has(v))
      .forEach((v) => {
        differenceSet.add(v);
      });

    return differenceSet;
  }

  /**
   * @description: 是否为子集
   * @param {MultiSet} otherSet
   * @return {boolean}
   */
  isSubsetOf(otherSet: MultiSet<T>): boolean {
    if (this.size() > otherSet.size()) {
      return false;
    }

    let isSubset = true;
    // 判据:当前集合的所有元素在otherSet中都存在
    this.values().forEach((value) => {
      if (!otherSet.has(value)) {
        isSubset = false;
      }
    });

    return isSubset;
  }

  /**
   * @description: 替换原生toString
   * @return {string}
   */
  toString(): string {
    return `${this.values()}`;
  }
}

下一篇来分析字典和散列表。


前端记事本,不定期更新,欢迎关注!