JavaScript數據結構與算法學習集合

什麼是集合

集合

集合是由一組無序且惟一 (即不能重複) 的項組成。算法

在數學中,集合是一組不一樣對象的集合。好比是說,一個又大於或等於 0 的整數組成的天然數集合: N = {0, 1, 2, 3,4,5, 6, ...}。集合中的對象列表用花括號 {} 包圍。數組

空集

空集是不包含任何元素的集合。空集用 {} 表示。瀏覽器

實現集合

定義集合類

咱們使用 ES6 的 class 語法來建立一個基於對象的 Set 類:數據結構

class Set {
  constructor() {
    this.items = {}
  }
}
複製代碼

接下來,咱們爲集合聲明一些可用的方法:this

  • add(element) 向集合添加一個新元素
  • delete(element) 從集合中移除一個元素
  • has(element) 判斷一個元素是否在集合中
  • clear() 移除集合中的全部元素
  • size() 返回集合中所包含元素的數量
  • values() 返回一個包含集合中全部元素的數組
  • isEmpty() 判斷集合是否爲空
  • size() 返回集合中元素個數
  • clear() 清空集合
  • toString() 將集合中的元素以字符串形式輸出

下面,咱們逐一實現這些方法:spa

add 向集合添加一個新元素

add(element) {
  if (!this.has(element)) {
    this.items[element] = element;
    return true;
  }

  return false;
}
複製代碼

向集合中添加一個 element 的時候,首先檢查它是否存在於集合中,若是不存在,就將 element 添加到集合中,並返回 true,表示添加了該元素;若是集合中已經有了這個element,則返回 false ,表示沒有添加這個元素。prototype

delete 從集合中移除一個元素

delete(element) {
  if (this.has(element)) {
    delete this.items[element];
    return true;
  }

  return false;
}
複製代碼

一樣的,從集合中移除一個元素時,首先判斷該元素是否存在於集合中,若是存在,就從集合中移除,並返回 true,表示元素被移除;若是不存在,則返回 false。3d

has 判斷一個元素是否在集合中

has(element) {
    return Object.prototype.hasOwnProperty.call(this.items, element);
}
複製代碼

咱們使用 Object 的原型方法 hasOwnProperty 來判斷元素是否在集合中。hasOwnProperty 方法返回一個代表對象是否具備特定屬性布爾值。code

clear 移除集合中的全部元素

clear() {
    this.items = {}
}
複製代碼

size 返回集合中所包含元素的數量

方法一:使用 Object.keys()對象

size() {
    return Object.keys(this.items).length;
}
複製代碼

在 size 方法中,咱們使用 Object 類的 keys 方法來獲取集合中所包含元素的數量。keys 方法返回一個包含給定對象全部屬性的數組,而後咱們能夠使用數組的 length 屬性來返回集合中的元素數量。

方法二:使用 for...in 循環

size() {
  let count = 0;
  for (let key in this.items) {
    if (this.items.hasOwnProperty(key)) {
        count++
    }
  }
  return count;
}
複製代碼

咱們定義一個 count 變量來存儲集合中元素的個數, 而後使用 for...in 循環迭代 items 對象的全部屬性,檢查它們是不是對象自身的屬性,若是是,就遞增 count 變量的值,最後在方法結束時返回 count 變量。

values 返回一個包含集合中全部元素的數組

方法一:使用 Object.values()

values() {
    return Object.values(this.items);
}
複製代碼

咱們使用 Object 類內置的 values 方法能夠很輕鬆的獲取集合中的全部元素,但Object.values 方法目前只在現代瀏覽器中可用。

方法二:使用 for...in 循環

values() {
  let values = [];
  for (let key in this.items) {
    if (this.items.hasOwnProperty(key)) {
      // 注意,咱們保存的集合元素 key 與 value 相同,所以能夠直接將 key  push 進 values 中
        values.push(key)
    }
  }
}
複製代碼

isEmpty 判斷集合是否爲空

isEmpty() {
    return this.size() === 0;
}
複製代碼

toString 將集合中的元素以字符串形式輸出

toString() {
  if (this.isEmpty()) {
    return '';
  }

  const values = this.values();
  let objString = `${values[0]}`;
  for (let i = 1; i < values.length; i++) {
    objString = `${objString}, ${values[i].toString()}`;
  }

  return objString;
}
複製代碼

集合運算

在數學中,集合有 並集、交集、差集、子集等運算,咱們也能夠使用咱們定義的集合類進行這些運算。

  • 並集:對於給定的兩個集合,返回一個包含兩個集合中全部元素的新集合
  • 交集:對於給定的兩個集合,返回一個包含兩個集合中共有元素的集合
  • 差集:對於給定的兩個集合,返回一個包含全部存在於第一個集合且不存在於第二個集合的元素的新集合
  • 子集:驗證一個給定集合是不是另外一個集合的子集

並集

在數學概念中,集合 A 和集合 B 的並集表示以下:

A ∪ B

該集合的定義以下:

A ∪ B = {x | x ∈ A ∨ x ∈ B}

意思是 x (元素) 存在於 A 中,或 x 存在於 B 中。下圖展現了並集運算:

下面,咱們實現 Set 類的 union 方法:

union(otherSet) {
  // 存放並集的結果
  const unionSet = new Set();
  // 迭代當前集合的全部值,將值添加到並集 集合中
  this.values().forEach(value => unionSet.add(value));
  // 迭代 otherSet 的全部值,將值添加到並集 集合中
  otherSet.values().forEach(value => unionSet.add(value));
  return unionSet;
}
複製代碼

首先使用 ES6 提供的數據結構 Set 來建立一個新的集合 unionSet表明兩個集合的並集。接着獲取第一個集合(當前的 Set 類實例)的全部值,迭代並所有添加到表明並集的集合中。而後對第二個集合作一樣的是,最後將表明並集的集合返回。

交集

在數學概念中,集合 A 和集合 B 的交集表示以下:

A ∩ B

該集合的定義以下:

A ∩ B = {x | x ∈ A ∧ x ∈ B}

意思是 x (元素) 存在於 A 中,且 x 存在於 B 中,下圖展現了交集運算:

下面,咱們實現 Set 類的 intersection 方法:

intersection(otherSet) {
  // 存放交集的結果
  const intersectionSet = new Set();
  // 當前集合(當前 Set 類的實例)的全部值
  const values = this.values();
  // 第二個集合的全部值
  const otherValues = otherSet.values();

  // 假設當前的集合元素較多
  let biggerSet = values;
  // 假設第二個集合元素較少
  let smallerSet = otherValues;

  // 比較兩個集合的元素個數,若是另一個集合的元素多於當前集合,則交換 biggerSet 和 smallerSet
  if (otherValues.length - values.length > 0) {
    biggerSet = otherValues;
    smallerSet = values;
  }

  // 迭代較小的集合,計算出兩個集合共有元素添加到 交集 集合中
  smallerSet.forEach(value => {
    if (biggerSet.includes(value)) {
        intersectionSet.add(value);
    }
  })

  // 將交集結果返回
  return intersectionSet;
}
複製代碼

intersection 方法會獲得全部同時存在於兩個集合中的元素。咱們首先建立一個新的集合 intersectionSet 來存放交集的結果。而後分別獲取當前 Set 實例中的全部元素和另外一個集合中的全部元素。咱們假定當前集合的元素較多,另外一個集合的元素較少,比較兩個集合的元素個數,若是另外一個集合的元素個數多於當前集合中的元素個數,咱們就交換 biggerSet 和 smallerSet 的值。最後迭代較小集合 smallerSet 來計算出兩個集合的共有元素。

差集

在數學概念中,集合 A 和集合 B 的差集表示爲:

A﹣B

該集合的定義以下:

A﹣B = {x | x ∈ A ∧ x ∉ B}

意思是 x (元素) 存在於 A 中,且 x 不存在於 B 中。下圖展現了集合 A 和集合 B 的差集運算:

下面,咱們來實現 Set 類的 subtract 方法:

subtract(otherSet) {
  // 存放差集結果
  const subtractionSet = new Set();
  // 迭代當前集合的全部值
  this.values().forEach(value => {
    // 當前值不存在於 otherSet 中,將其添加到 差集 集合中
    if (!otherSet.has(value)) {
      // 將元素添加到集合中
        subtractionSet.add(value);
    }
  })

  return subtractionSet;
}
複製代碼

subtract 方法會獲得全部存在於集合 A 但不存在於集合 B 的元素。咱們首先建立一個新的集合變量 subtractionSet 來存放差集結果。經過 this.values() 拿到集合A 中的全部值,而後迭代集合 A 中的全部值,將存在於集合A 但不存在於集合B 中的元素放入 subtractionSet 集合中,迭代完集合 A 中的全部元素後,subtractionSet 集合中的元素就是集合A 與集合B 差集。

子集

在數學概念中,集合 A 是集合 B 的一個子集(或者 B 包含 A),表示以下:

A ⊆ B

該集合的定義以下:

{x|∀x∈A ⇒ x ∈ B}

意思是集合A 中的每個 x (元素),也須要存在於集合B 中。下圖展現了集合A 是集合B 的子集:

下面,咱們來實現 Set 類的 isSubsetOf 方法:

isSubsetOf(otherSet) {
  // 驗證當前 Set 實例的大小
  // 當前 Set 實例的元素多於 otherSet,那麼當前 Set 實例不是 otherSet 的子集
  if (this.size() > otherSet.size()) {
    return false;
  }

  // 假定當前 Set 實例是給定集合的子集
  let isSubset = true;
  // 迭代當前 Set 實例的全部元素
  this.values().every(value => {
    // 有任何元素不存在於 otherSet 中,那麼當前 Set 實例就不是 otherSet 的子集
    if (!otherSet.has(value)) {
        isSubset = false;
      return false;
    }
    return true;
  })
  return isSubset;
}
複製代碼

isSubsetOf 方法驗證 集合 A 是不是 集合 B 的子集。咱們首先須要驗證當前 Set 實例的大小(集合A),若是當前實例中的元素比 otherSet 實例(集合B)更多,說明當前 Set 實例不是 otherSet 的子集。(子集的元素個數須要小於等於要比較的集合)。

接下來,咱們假定當前集合是給定集合的子集,而後迭代當前集合的全部元素,並驗證這些元素是否存在於 otherSet 中。若是有任何一個元素不存在於 otherSet 中,那麼當前集合就不是給定集合的子集,咱們就返回 false。若是全部元素都存在於 otherSet 中,那麼當前集合就是給定集合的子集。

完整代碼

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

  // 向集合添加一個新元素
  add(element) {
   if (!this.has(element)) {
     this.items[element] = element;
     return true;
   }

   return false;
  }

  // 從集合中移除一個元素
  delete(element) {
    if (this.has(element)) {
        delete this.items[element];
      return true;
    }

    return false;
  }

  // 判斷一個元素是否存在於集合中
  has(element) {
    return Object.prototype.hasOwnProperty.call(this.items, element);
  }

  // 移除集合中的全部元素
  clear() {
    this.items = {};
  }

  // 返回集合中所包含元素的數量
  size() {
    let count = 0;
    for(let key in this.items) {
        if (this.items.hasOwnProperty(key)) {
        count++;
      }
    }

    return count;
  }

  // 返回一個包含集合中全部元素的數組
  values() {
    let values = [];
    for (let key in this.items) {
        if (this.items.hasOwnProperty(key)) {
        values.push(key);
      }
    }
    return values;
  }

  // 並集
  union(otherSet) {
    const unionSet = new Set();
    this.values().forEach(value => unionSet.add(value));
    otherSet.values().forEach(value => unionSet.add(value));
    return unionSet;
  }

  // 交集
  intersection(otherSet) {
    const intersectionSet = new Set();
    const values = this.values();
    const otherValues = otherSet.values();

    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;
  }

  // 差集
  subtract(otherSet) {
    const subtractionSet = new Set();
    this.values().forEach(value => {
        if (!otherSet.has(value)) {
        subtractionSet.add(value)
      }
    })

    return subtractionSet;
  }

  // 子集
  isSubsetOf(otherSet) {
    if (this.size() > otherSet.size()) {
        return false;
    }

    let isSubset = true;
    this.values().every(value => {
        if (!otherSet.has(value)) {
        isSubset = false;
        return false;
      }
      return true;
    })
    return isSubset;
  }

}
複製代碼

ES6 Set 類的集合運算

並集

// A 和 B 的並集
function union(setA, setB) {
  const unionSet = new Set([...setA, ...setB]);
  return unionSet;
}
複製代碼

交集

// A 和 B 的交集
function intersection(setA, setB) {
  const intersect = new Set([...a].filter(x => b.has(x)));
  return intersect
}
複製代碼

差集

// A 相對於 B 的差集
function subTract(setA, setB) {
  const subTractSet = new Set([...a].filter(x => !b.has(x)));
  return subTractSet;
}
複製代碼

參考資料:

書籍:《JavaScript數據結構與算法》

做者:moozi  
相關文章
相關標籤/搜索