你們好,我是圖圖。上一篇文章聊了鏈表的數據結構,鏈表包括:單向鏈表、雙向鏈表、循環鏈表和有序鏈表這幾個常見的鏈表。而在操做的過程當中,也是比較複雜的。由於它具有有一個指向下一個節點的指針,所以在操做的過程當中要多加當心。那麼這一章咱們就來聊聊集合。下面咱們廢話很少說,一切盡在代碼中。數組
集合是由一組無序而且不能存在重複的元素組成的。你能夠把集合當成一個既沒有重複元素,又沒有順序的數組。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
方法,由於它會被add
和delete
方法調用。優化
has(ele) {
return Object.prototype.hasOwnProperty.call(this.items, ele);
}
複製代碼
這裏使用了Object
原型上的hasOwnProperty
方法。該方法返回一個布爾值,查看對象上是否具備指定的屬性。固然你也能夠用in
操做符。這兩個方法的區別在於hasOwnProperty
是檢查實例中是否具備指定的屬性。而in
操做符是不管指定的屬性是在原型中仍是在實例中都會返回true
。ui
下面是添加元素的方法。this
add(ele) {
if (!this.has(ele)) {
this.items[ele] = ele;
return true;
}
return false;
}
複製代碼
add
方法相對於比較簡單。首先,檢查要添加的元素是否存在於集合中,沒有就添加並返回true
,有就直接返回false
。spa
添加元素時,最好用它自己來看成鍵和值來保存,這樣有利於查找該元素。
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
方法有三種:
add
和delete
方法是用一個count
變量控制它。Object.keys
方法,該方法返回對象中可枚舉屬性組成的數組。而後用這個數組的長度length
返回items
對象中的屬性個數。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
方法,用Object.values
和for-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
方法同樣,只不過這裏不是計算屬性的個數。
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]
複製代碼
須要注意的是,在setA
和setB
中都添加了元素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
方法,由於setA
和setB
之間的差集可能與setB
和setA
之間的差集不同。
驗證一個集合是不是另外一個集合的子集。
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
複製代碼
這裏有三個集合:setA
是setB
的子集,因此輸出了true
。setA
不是setC
的子集,由於setC
中只包含了setA
中的2
,因此輸出了false
。
下面咱們用 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
複製代碼
若是文中出現錯誤,歡迎各位大佬指點。若是本章內容對你有收穫,歡迎點贊加關注哦!