【数据集合】并集、交集、差集、子集

什么是集合?

  • 集合是由一组无序且惟一的项组成。你能够把集合想象成一个既没有重复元素,也没有顺序概念的数组。还有一个概念叫空集。空集就是不包括任何元素的集合。空集用{}表示。

建立集合类

  • ECMAScript2015介绍了Set类是Javascript API的一部分,咱们将基于ES2015的Set类来实现咱们本身的Set类。咱们也会实现一些原生ES2015没有提供的集合运算,例如并集、交集和差集。
class Set {
    constructor() {
        this.items = {};
    }
}
复制代码
  • 咱们使用对象而不是数组来表示集合(items)的缘由有两个:
    • 第一个是由于在使用数组时,大部分方法的时间复杂度是O(n)。即咱们须要迭代整个数组直到找到要找的那个元素,在最坏的状况下须要迭代数组的全部位置。若是数组有更多元素的话,所需的时间会更长。另外,数组是元素的一个有序集合。为了保证元素排列有序,它会占用更多的内存空间。
    • 另外一个就是由于Javascript的对象不容许一个键指向两个不一样属性,也保证了集合里的元素都是惟一的。

老规矩-来搞几个Set类的方法

1. has() 检验某个元素是否存在于集合中javascript

has(element) {
    return Object.prototype.hasOwnProperty.call(this.items, element); // 返回一个代表对象是否具备特定属性的布尔值
  }
复制代码
  • Object原型有hasOwnProperty方法。该方法返回一个代表对象是否具备特定属性的布尔值。
  • 也能够使用this.items.hasOwnProperty(element),但这样的话代码检查工具如(ESLint)会抛出一个错误。错误的缘由为不是全部的对象都继承了Object.prototype,甚至继承的Object,prototype的对象上的hasOwnProperty方法也有可能会被覆盖,致使代码不能正常工做。
  • element in items也能够。in运算符返回表示对象在原型链上是否有特定属性的布尔值。

2. add() 向集合添加一个新元素java

add(element) {
    if (!this.has(element)) { // 检查
      this.items[element] = element; // 若是不存在,就把element添加到集合中,返回true {element:element}
      return true;
    }
    return false; // 若是集合中有相同的元素,返回false便可
  }
复制代码
  • 添加一个元素的时候,把它同时做为键和值保存,由于这样有利于查找该元素

3. delete()从集合中删除一个元素算法

delete(element) {
    if (this.has(element)) { // 检查,只能删除存在与集合中的,(集合中都没有,你删谁去~)
      delete this.items[element]; // 从集合中移除element
      return true; // 返回true
    }
    return false; // 若是集合中没有,返回false
  }
复制代码
  • 既然咱们用对象来存储集合的items对象,就能够简单的使用delete运算符从items对象中移除属性。

4. size()返回集合中有多少元素数组

size() { // 返回集合中有多少元素
    return Object.keys(this.items).length; // 使用原生的内置方法,把对象的key转化为数组,再返回其length
  }
复制代码

5. values()返回一个包含集合中全部全部值的数组浏览器

values() { // 返回一个包含集合中全部全部值的数组
    return Object.values(this.items); // 一样使用原生方法(它是在ECMAscript2017中被添加进来的,目前只在如今浏览器中可用)
  }
复制代码
  • 如今就实现了一个和ECMASscript2015中很是相似的Set类实现。

集合运算

  • 咱们能够对集合进行以下运算:
    • 并集:对于给定的两个集合,返回一个包含两个集合中全部元素的新集合。
    • 交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。
    • 差集:对于给定的两个集合,返回一个包含全部存在于第一个集合且不存在于第二个集合的元素的新集合。
    • 子集:验证一个给定集合是不是另外一个集合的子集。

1.并集 数据结构

  • 该集合的定义是:x(元素)存在于A中,或x存在于B中。实现代码以下:
union(otherSet) {
    const unionSet = new Set(); // 建立一个新的集合
    this.values().forEach(value => unionSet.add(value)); // 获取第一个集合(当前的set类实例)全部的值并添加到新集合中
    otherSet.values().forEach(value => unionSet.add(value)); // 获取第二个集合(传入的set类实例)全部的值并添加到新集合中
    return unionSet; // 最后返回建立的新集合
  }
复制代码

2.交集 工具

  • 由全部属于集合A且属于集合B的元素所组成的集合,叫作集合A与集合B的交集。实现代码以下:
intersection(otherSet) {
    const intersectionSet = new Set(); // 建立一个新的集合
    const values = this.values(); // 获取第一个集合(当前的set类实例)
    const otherValues = otherSet.values(); // 获取第二个集合(传入的set类实例)
    let biggerSet = values; // 假设当前集合的元素较多
    let smallerSet = otherValues; // 传入集合的元素较少
    if (otherValues.length - values.length > 0) { // 比较两个集合的元素个数
      biggerSet = otherValues; // 若是传入集合的元素个数比当前集合的元素个数多的话,就交换较多的等于传入的
      smallerSet = values; // 较少的等于当前集合
    }
    smallerSet.forEach(value => { // 最后迭代较少集合
      if (biggerSet.includes(value)) { // 若是较大的集合中也有这个元素
        intersectionSet.add(value); // 添加到新集合当中
      }
    });
    return intersectionSet; // 返回新集合
  }
复制代码
  • 首先建立一个新的集合来存放intersection方法返回的结果。
  • 而后要获取当前集合实例中的值和传入集合中的值。
  • 先假设当前集合的元素较多, 传入集合的元素较少。
  • 再比较两个集合的元素个数,若是另外一个集合的元素个数比当前集合的元素个数多的话,就交换较多的等于传入的,较少的等于当前集合。
  • 迭代较少集合,若是两个集合中都有当前元素,把它插入到新集合当中
  • 最后返回新集合
  • 对两个集合的元素个数作比较的目的是为了尽可能最少循环迭代,更少的迭代次数意味这更少的过程消耗。

3. 差集学习

(没找到更好的图片,本身也懒的画了,意思你们表达清除就好 哈~~)

  • 元素存在A中,且x不存在于B中。实现代码以下:
difference(otherSet) {
    const differenceSet = new Set(); // 建立一个新的集合
    this.values().forEach(value => { // 当前集合的值转换为数组,并循环
      if (!otherSet.has(value)) { // 若是传入的集合中没有这个元素
        differenceSet.add(value); // 把它添加到新集合中
      }
    });
    return differenceSet; // 返回新的集合
  }
复制代码

4. 子集优化

  • 集合A的每个元素,也须要存在于集合B中。实现代码以下:
isSubsetOf(otherSet) {
    if (this.size() > otherSet.size()) { // 若是当前集合的元素比传入集合多,那它确定不是传入集合的子集,返回false
      return false;
    }
    let isSubset = true; // 先假设当前集合是传入集合的子集
    // 迭代当前集合,当发现一个返回false,便再也不执行。
    this.values().every(value => {
      if (!otherSet.has(value)) { // 验证迭代的元素是否也存在传入集合中
        isSubset = false; // 只要有一个不是就改变变量
        return false; // 返回false 再也不往下执行
      }
      return true; // 若是都是,返回true
    });
    return isSubset; // 最后返回变量isSubset
  }
复制代码
  • 首先要验证当前集的元素比传入集的小,若是当前集合的元素比传入集合还多,那它确定不是传入集合的子集,返回false
  • 而后先假设当前集合是传入集合的子集。
  • 迭代当前集合的全部元素,验证这些元素也存在于传入集合中。
  • 若是有任何元素不存在于传入集合中,就意味着它不是一个子集,返回false。
  • 若是全部元素都存在于传入集合中,就返回true,isSubset变量不会改变。
  • 在子集方法中咱们用的是every方法。当咱们发现一个值不存在于传入集合时,能够中止迭代值,表示这不是一个子集。

ECMAScript2015——Set类

  • 先来看看原生的set类怎么用:
const set = new Set();
set.add(1);
console.log(set.values()); // 输出@Iterator
console.log(set.has(1)); // 输出true
console.log(set.values()); // 输出1
复制代码
  • ES2015的set的values方法返回Iterator(一个包含键值对的迭代器对象),而不是值构成的数组。
  • 另外一个区别是,咱们实现的size方法返回set中存储的值的个数,而ES2015的 Set则有一个size属性。
  • 咱们实现的set类实现了并集、交集、差集、子集这些数学运算,然而ES6原生的Set并无这些功能。

ES6Set运算模拟ui

  • 首先建立两个集合,等会儿会用到
const setA = new Set();
setA.add(1);
setA.add(2);
setA.add(3);

const setB = new Set();
setB.add(2);
setB.add(3);
setB.add(4);
复制代码
  1. 模拟并集运算
const union = (set1, set2) => { // 普通模拟
  const unionAb = new Set();
  set1.forEach(value => unionAb.add(value));
  set2.forEach(value => unionAb.add(value));
  return unionAb;
};
console.log(union(setA, setB)); // [1,2,3,4]

console.log(new Set([...setA, ...setB]));  // 使用 扩展运算符模拟 [1,2,3,4]
复制代码
  1. 模拟交集运算
const intersection = (set1, set2) => { // 普通模拟, 未经优化的
  const intersectionSet = new Set();
  set1.forEach(value => {
    if (set2.has(value)) {
      intersectionSet.add(value);
    }
  });
  return intersectionSet;
};
console.log(intersection(setA, setB)); // [2,3]

console.log(new Set([...setA].filter(x => setB.has(x)))); // 使用 扩展运算符模拟 [2,3]
复制代码
  1. 模拟差集运算
const difference = (set1, set2) => { // 普通模拟
  const differenceSet = new Set();
  set1.forEach(value => {
    if (!set2.has(value)) {
      differenceSet.add(value);
    }
  });
  return differenceSet;
};
console.log(difference(setA, setB));// [1]

console.log(new Set([...setA].filter(x => !setB.has(x)))); // 使用 扩展运算符模拟 [1]
复制代码

最后

好了,今天的随手笔记完事儿了。本文内容全来自本人阅读过《学习Javascript数据结构与算法》第七章后稍加整理而成。