JavaScript 数据结构和算法——集合

124 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 4 天,点击查看活动详情

集合介绍

集合相信大家都不会陌生,在数学当中,集合表示由一个或多个确定的元素所构成的整体。在计算机科学当中,集合是由一组无序且唯一(既不能重复)的项组成。该数据结构使用了和数学当中有限集合相同的数学概念。但应用在计算机科学的数据结构当中。

思路

集合既然是一种顺序的数据结构,那么我们首先应该想到是否可以使用数组或对象来实现呢?你想的没错,集合确实可以由数组或对象来实现,两者的实现差别不大,所以这里我们使用对象来实现集合。因为对象包含许多操作属性和值的 API。并且在 JavaScript 当中,对象的属性不允许指向内存当中两块不同的地址,所有,这也保证了集合当中的元素都是唯一的。

创建集合

class CustomizeSet implements ICustomizeSet<number> {
  public items: { [propName: string]: number };
  constructor() {
    this.items = {};
  }
}

上面的代码当中,我们定义了一个类,并且实现了接口ICustomizeSet,通过泛型我们可以传入这个集合值的类型number。泛型我们之后有时间在详细介绍。在CustomizeSet类的构造函数当中,我们初始化了一个对象this.items,这个介绍我们以后要来存数据的地方。

增加元素

add(element: number): boolean {
    if (this.has(element)) return false;
    this.items[this.toString(element)] = element;
    return true;
  }

上面的方法,我们先判断在集合当中是否包含这个值,如果包含,那么就返回,无法插入,因为集合具有唯一性,无法存储两个相同的值。然后我们通过toString方法将传入的参数变成字符串,用这个字符串来当值的属性,所以在集合当中,我们的属性和值是一样的,但是类型一个是string一个是number。最后添加成功,我们返回true

删除元素

deleteElm(element: number): number {
    let result: number;
    result = this.items[this.toString(element)];
    delete this.items[this.toString(element)];
    return result;
  }

deleteElm接收一个参数,类型是number,并且他还会返回删除的值。我们先定义一个变量result来保存要被删除的值,然后通过delete删除对象当中对应值的属性。

集合当中是否包含某个元素

has(element: number): boolean {
    return Object.prototype.hasOwnProperty.call(this.items, element);
  }

上面的代码当中,通过对象的Object.prototype.hasOwnProperty来判断当前对象当中是否包含某个属性,如果包含就返回true,否则就是false。这里要注意,我们为什么要通过call来调用呢?因为我们担心的操作对象上的该方法被覆盖,或者就没有该方法,到时候我们的has方法就会抛出异常。所以我们通过Object原型上的函数来帮我们判断。

集合包含元素数量

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

我们这里通过Object.keys直接获取对象上所有的可枚举的属性,返回一个数组。这样一来,我们定义的属性就都在数组当中了,我们再获取数组的长度,我们就可以直接获取集合当中元素的数量了。

集合是否为空

isEmpty(): boolean {
    return Object.keys(this.items).length === 0;
  }

这个方法的思路和上面类似,如果集合元素数量不为 0,那么我们就返回true,否则就返回fasle

返回集合当中所有的值

values(): number[] {
    let keys = Object.keys(this.items);
    let result: number[] = [];
    for (let key of keys) {
      if (Object.prototype.hasOwnProperty.call(this.items, key)) {
        result.push(this.items[key]);
      }
    }

    return result;
  }

这里我们通过数组遍历集合属性的方法来获取集合当中每个属性的值。其实我们还有更好的办法,在 ES6 当中,对象为我们提供了Object.values这个方法,我们可以直接将this.items传入,就会返回值的数组,但是这个方法只能 ES6 使用,如果我们需要兼容 ES5 的话,就没办法了。

集合清空集合

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

    return true;
  }

我们直接将this.items重新赋值{},至于之前的集合,如果没有其他的引用,那么下一轮的垃圾回收将会把它回收掉,所以不用担心造成内存浪费。

完整代码

//实现set集合数据结构
interface Items<T> {
  [propName: string]: T;
}

interface ICustomizeSet<T> {
  //存储数据
  items: Items<T>;

  //集合当中是否包含某个元素
  has(element: T): boolean;

  //添加元素
  add(element: T): boolean;

  //删除元素
  deleteElm(element: T): T;

  //清空集合
  clear(): boolean;

  //集合包含元素的数量
  size(): number;

  //返回一个包含集合当中所有值的数组
  values(): T[];

  //当前集合是否为空
  isEmpty(): boolean;

  //转为字符串
  toString(element: T): string;
}

class CustomizeSet implements ICustomizeSet<number> {
  public items: { [propName: string]: number };
  constructor() {
    this.items = {};
  }

  has(element: number): boolean {
    return Object.prototype.hasOwnProperty.call(this.items, element);
  }

  add(element: number): boolean {
    if (this.has(element)) return false;
    this.items[this.toString(element)] = element;
    return true;
  }

  deleteElm(element: number): number {
    let result: number;
    result = this.items[this.toString(element)];
    delete this.items[this.toString(element)];
    return result;
  }

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

    return true;
  }

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

  values(): number[] {
    let keys = Object.keys(this.items);
    let result: number[] = [];
    for (let key of keys) {
      if (Object.prototype.hasOwnProperty.call(this.items, key)) {
        result.push(this.items[key]);
      }
    }

    return result;
  }

  isEmpty(): boolean {
    return Object.keys(this.items).length === 0;
  }

  toString(element: number): string {
    if (element === null) {
      return "NULL";
    }

    if (element === undefined) {
      return "UNDEFINED";
    }

    return `${element}`;
  }
}

function createSet<T>(): CustomizeSet {
  return new CustomizeSet();
}

const customizeSet = createSet<number>();
customizeSet.add(1);
customizeSet.add(2);
customizeSet.add(3);
console.log(customizeSet.values()); //[ 1, 2, 3 ]
customizeSet.deleteElm(2);
console.log(customizeSet.has(1)); //true
console.log(customizeSet.values()); //[ 1, 3 ]
console.log(customizeSet.size()); //2