JavaScript數據結構 - 集合

前言

你們好,我是圖圖。上一篇文章聊了鏈表的數據結構,鏈表包括:單向鏈表、雙向鏈表、循環鏈表和有序鏈表這幾個常見的鏈表。而在操做的過程當中,也是比較複雜的。由於它具有有一個指向下一個節點的指針,所以在操做的過程當中要多加當心。那麼這一章咱們就來聊聊集合。下面咱們廢話很少說,一切盡在代碼中。數組

集合

集合是由一組無序而且不能存在重複的元素組成的。你能夠把集合當成一個既沒有重複元素,又沒有順序的數組。markdown

建立集合

學過 ES6 的同窗都知道,加入了一種新的數據結構Set。也是本章所說的集合。下面將基於 ES6 的Set數據結構來實現一個屬於本身的Set類。數據結構

下面來聲明一個Set類。函數

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

這裏用一個對象items來表示集合,用對象的主要緣由是,對象不容許用一個鍵指向兩個不一樣的屬性。這樣也能確保集合中的元素是惟一的,不過也能夠用數組來表示。性能

下面是集合的方法。測試

  • add(ele):向集合中添加一個元素。
  • delete(ele):從集合中刪除一個元素。
  • has(ele):查看集合中是否有該元素存在,若是存在返回true,不然返回false
  • clear():刪除集合中的全部元素。
  • size():返回集合中元素的數量。
  • values():返回一個包含集合中全部元素的數組。

has方法

首先來實現has方法,由於它會被adddelete方法調用。優化

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

這裏使用了Object原型上的hasOwnProperty方法。該方法返回一個布爾值,查看對象上是否具備指定的屬性。固然你也能夠用in操做符。這兩個方法的區別在於hasOwnProperty是檢查實例中是否具備指定的屬性。而in操做符是不管指定的屬性是在原型中仍是在實例中都會返回trueui

add方法

下面是添加元素的方法。this

add(ele) {
    if (!this.has(ele)) {
      this.items[ele] = ele;
      return true;
    }
    return false;
}
複製代碼

add方法相對於比較簡單。首先,檢查要添加的元素是否存在於集合中,沒有就添加並返回true,有就直接返回falsespa

添加元素時,最好用它自己來看成鍵和值來保存,這樣有利於查找該元素。

delete、clear方法

delete(ele) {
    if (this.has(ele)) {
      delete this.items[ele];
      return true;
    }
    return false;
}
複製代碼

delete方法首先檢查元素是否存在於集合中,存在就刪除並返回true表示已刪除,不存在直接返回false

clear方法和其餘數據結構的clear方法同樣。

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

直接將items初始化就能夠了。還有一種方法是經過迭代集合,使用delete操做符逐個將元素刪除,不過這樣顯得麻煩。

size方法

實現size方法有三種:

  1. 像棧、隊列那樣,在使用adddelete方法是用一個count變量控制它。
  2. 使用Object.keys方法,該方法返回對象中可枚舉屬性組成的數組。而後用這個數組的長度length返回items對象中的屬性個數。
  3. for-in遍歷對象中的屬性,用一個count變量記錄屬性的個數並返回count變量。
size() {
    return Object.keys(this.items).length;
}

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

迭代items對象的全部屬性,並用has方法檢查是否爲自身的屬性。若是是,就遞增count變量。

has方法這步的緣由在於,假設在對象的原型上有額外的屬性,也會致使count遞增。

values方法

對於values方法,用Object.valuesfor-in迭代均可以。

values() {
    return Object.values(this.items);
}

theOtherOneValues() {
    const arr = [];

    for (const key in this.items) {
      if (this.has(key)) {
        arr.push(key);
      }
    }
    return arr;
}
複製代碼

for-in迭代就和上面的size方法同樣,只不過這裏不是計算屬性的個數。

測試Set類

const set = new Set();

console.log(set.size()); // 0

console.log(set.add(1)); // true
console.log(set.add(2)); // true
console.log(set.add(3)); // true

console.log(set.size()); // 3

console.log(set.has(1)); // true

console.log(set.values()); // [1, 2, 3]

console.log(set.delete(3)); // true
複製代碼

總體代碼

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

  has(ele) {
    return Object.prototype.hasOwnProperty.call(this.items, ele);
  }

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

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

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

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

  theOtherOneSize() {
    let count = 0;
    for (const key in this.items) {
      if (this.has(key)) {
        count++;
      }
    }
    return count;
  }

  values() {
    return Object.values(this.items);
  }

  theOtherOneValues() {
    const arr = [];

    for (const key in this.items) {
      if (this.has(key)) {
        arr.push(key);
      }
    }
    return arr;
  }
}
複製代碼

集合運算

並集

給定兩個集合,返回一個包含兩個集合中全部的元素的集合。

// 在Set類中添加方法
union(otherSet) {
    const unionSet = new Set();
    this.values().forEach((item) => unionSet.add(item));
    otherSet.values().forEach((item) => unionSet.add(item));
    return unionSet;
}
複製代碼

測試代碼

const setA = new Set();
const setB = new Set();

setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);

setB.add(2);
setB.add(4);
setB.add(6);

const otherSetB = setA.union(setB);

console.log(otherSetB.values());
// [1, 2, 3, 4, 5, 6]
複製代碼

須要注意的是,在setAsetB中都添加了元素6,可是在下面打印出來的數據中只出現一個6

交集

給定的兩個集合,返回一個包含兩個集合中共有元素的集合。

intersectionFn(otherSet) {
    const intersection = new Set();

    const values = this.values();
    for (let i = 0; i < values.length; i++) {
      if (otherSet.has(values[i])) {
        intersection.add(values[i]);
      }
    }
    return intersection.values();
}
複製代碼

首先建立一個Set實例,而後迭代當前Set實例中的全部值(values)。用has方法驗證是否存在otherSet集合當中,若是存在,就添加到interseciton集合中。最後以數組的形式返回它。

測試代碼

const setA = new Set();
const setB = new Set();

setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);

setB.add(2);
setB.add(4);
setB.add(6);

const otherSetB = setA.intersectionFn(setB);

console.log(otherSetB);
// [6]
複製代碼

改進版

假設如今有兩個這樣的集合:

  • setA的值是[1, 2, 3, 4, 5, 6];
  • setB的值是[2, 6];

用剛纔的intersectionFn方法,就要迭代六次setA的值,而後還要用這個六個值和setB的兩個值去做比較。那麼換過來,只要用setB去和setA作比較的話,這樣就減小了性能的消耗。由於只須要迭代兩次setB。下面來修改以前的intersectionFn方法。

otherIntersection(otherSet) {
    const intersection = new Set();
    const values = this.values();
    const otherVal = otherSet.values();
    // 數組長度大的集合
    let biggerSet = values;
    // 數組長度小的集合
    let smallerSet = otherVal;

    /* 若是傳入的Set集合的元素個數比當前Set集合的元素個數多,那麼就交換 若是當前的集合元素個數比傳入的Set集合的元素個數多,就不會走這一步 */
    if (otherVal.length - values.length > 0) {
      biggerSet = otherVal;
      smallerSet = values;
    }

    // 迭代較小的集合
    smallerSet.forEach((item) => {
      if (biggerSet.includes(item)) {
        intersection.add(item);
      }
    });

    return intersection;
}
複製代碼

差集

給定兩個集合,返回一個包含全部存在於第一個集合中但不存在於第二個集合中的元素的集合。

difference(otherSet) {
    const differenceSet = new Set();

    this.values().forEach((item) => {
      if (!otherSet.has(item)) {
        differenceSet.add(item);
      }
    });

    return differenceSet.values();
}
複製代碼

difference方法會返回一個存在於集合setA中但不存在於集合setB的元素數組。首先建立結果集合,而後迭代集合中的全部值。檢查當前值是否存在於給定集合中,存在的話,就把值添加到結果集合中。

測試代碼

const setA = new Set();
const setB = new Set();

setA.add(1);
setA.add(3);
setA.add(5);
setA.add(6);

setB.add(2);
setB.add(4);
setB.add(6);

const otherSetB = setA.difference(setB);

console.log(otherSetB);
// [1, 3, 5]
複製代碼

這裏輸出了[1, 3, 5],由於[1, 3, 5]只存在於setA中。若是執行setB.difference(setA),會輸出[2, 4],由於[2, 4]只存在於setB中。

這裏不能像優化intersecton方法那樣去優化difference方法,由於setAsetB之間的差集可能與setBsetA之間的差集不同。

子集

驗證一個集合是不是另外一個集合的子集。

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;
}
複製代碼

首先驗證當前Set實例的元素個數大小,若是當前實例中的元素比otherSet實例多,那就不是子集。直接返回false。記住,子集的元素個數始終小於或等於要比較的集合的。

上面的代碼中,假如當前實例是給定集合的子集(isSubset = true)。就迭代當前集合中的全部元素,驗證這些元素是否存在於otherSet中。若是都不存在,就說明它不是一個子集,返回false。若是都存在於otherSet中,那麼isSubset的值就不會改變。返回true

使用every方法的緣由在於,一個值不存在於otherSet中時,能夠中止迭代,表示這不是一個子集。只要回調函數返回true,就會執行下去。若是返回false,循環直接中止。

測試代碼

const setA = new Set();
const setB = new Set();
const setC = new Set();

setA.add(1);
setA.add(2);
setA.add(3);

setB.add(1);
setB.add(2);
setB.add(3);
setB.add(4);

setC.add(2);
setC.add(3);
setC.add(4);
setC.add(5);

console.log(setA.isSubsetOf(setB)); // true
console.log(setA.isSubsetOf(setC)); // false
複製代碼

這裏有三個集合:setAsetB的子集,因此輸出了truesetA不是setC的子集,由於setC中只包含了setA中的2,因此輸出了false

ES6中的Set類

下面咱們用 ES6 的 Set類來模擬並集、交集、差集、子集

首先來定義兩個集合,由於後面會用到。

const setA = new Set();
const setB = new Set();
setA.add(10);
setA.add(20);
setA.add(30);

setB.add(20);
setB.add(30);
setB.add(40);
setB.add(50);
複製代碼

模擬並集

const union = (setA, setB) => {
  let values = new Set();
  setA.forEach((item) => values.add(item));
  setB.forEach((item) => values.add(item));
  return values.values();
};

console.log(union(setA, setB));
// {10, 20, 30, 40, 50}
複製代碼

模擬交集

const intersection = (setA, setB) => {
  let values = new Set();

  setA.forEach((item) => {
    if (setB.has(item)) {
      values.add(item);
    }
  });
  return values.values();
};
console.log(intersection(setA, setB));
// {20, 30}
複製代碼

模擬差集

const difference = (setA, setB) => {
  const values = new Set();
  setA.forEach((item) => {
    if (!setB.has(item)) {
      values.add(item);
    }
  });

  return values.values();
};

console.log(difference(setA, setB));
// {10}
複製代碼

模擬子集

const isSubsetOf = (setA, setB) => {
  if (setA.size > setB.size) {
    return false;
  }

  let isSubset = true;
  let arr = [...setA];
  arr.every((value) => {
    if (!setB.has(value)) {
      isSubset = false;
      return false;
    }

    return true;
  });
  return isSubset;
};

console.log(isSubsetOf(setA, setB)); // false
複製代碼

結尾

若是文中出現錯誤,歡迎各位大佬指點。若是本章內容對你有收穫,歡迎點贊加關注哦!

相關文章
相關標籤/搜索