在以前已經介紹了數據結構:棧、隊列、鏈表,而且知道了這些數據結構的特性和實現方式,以及如何在實際的開發過程當中經過這些數據結構來巧妙的解決一些實際問題,包括怎麼去實現一個四則運算、怎麼去實現最優取幣方式等等;這篇接着介紹數據結構:集合、字典散列;
前端算法系列之二:數據結構鏈表、雙向鏈表、閉環鏈表、有序鏈表
前端算法系列之一:時間複雜度、空間複雜度以及數據結構棧、隊列的實現前端
集合是由一組無序且惟一(即不能重複)的項組成的。該數據結構使用了與有限集合相同的數學概念,但應用在計算機科學的數據結構中。注意集合的特性:一、無序;二、惟一;這和咱們數學概念的集合大致是一致的,在數學中咱們用大括號來表示集合,把知足條件的元素歸類於放在一塊造成了集合,例如把大於等於0的整數放入集合N = {0,1,2,3,4,5,6...};集合也多是沒有任何元素的,好比把沒寫過bug的程序猿歸類在一個集合那結果就是N = {};那怎麼去實現一個集合呢?
在es6之後JavaScript有一個原生支持集合的Set,這個就是一個集合,那咱們在es6之前的怎麼去實現一個集合呢?咱們能夠模仿es6中Set相關來實現;
定義相關api:
add(ele):向集合中添加一個元素;
delete(ele): 刪除集合中一個元素;
has(ele): 集合中是否包含某個元素
clear(): 清空集合
size(): 計算集合的大小
values(): 獲取集合的全部元素,返回一個數組git
class MySet { constructor() { this.items = {}; } add(element) { const key = this.keyToString(element); if(!this.has(element)) { this.items[key] = element; return true; } return false; } delete(element) { if(this.has(element)) { delete this.items[this.keyToString(element)]; return true; } return false; } keyToString(str) { return keyToString(str); } has(element) { return Object.prototype.hasOwnProperty.call(this.items, this.keyToString(element)); } clear() { this.items = {}; return true; } size() { return Object.keys(this.items).length; } values() { return Object.values(this.items); } }
注:上面代碼中有一個將鍵轉換爲字符串的函數keyToString,這是爲了當添加的元素是對象或者其餘特殊類型時候直接做爲對象的鍵可能會引起錯誤;es6
function keyToString(str) { if (str === null) { return 'null'; } else if (str === undefined) { return 'undefined'; } else if(typeof str === 'function') { return str.toString(); } return JSON.stringify(str) }
實例測試:github
const myset = new MySet(); myset.add(1); // true myset.add(2); // true myset.add(3); // true myset.add({a:1}); // true myset.add({b:1}); // true console.log(myset.has(2)); // true console.log(myset.has({a:1})); // true console.log(myset.has({a:2})); // false console.log(myset.size());// 5 console.log(myset.values());// [1, 2, 3, {a:1}, {b:1}] myset.delete({a:1}); console.log(myset.values());// [1, 2, 3, {b:1}] console.log(myset.has({a:1})); // false console.log(myset.size(); // 4 myset.clear(); console.log(myset.values());// [] console.log(myset.size(); // 0
經過上面的代碼實現集合仍是比較容易理解的,就是對一個對象鍵值對的增刪改查之類的操做,沒有涉及到什麼複雜的運用,但咱們知道集合的應用遠遠不止是這樣;集合的應用其實在於集合間的操做,好比咱們查找數據的時候,從兩個不一樣的表裏面查出了兩個集合的數據,咱們能夠經過集合的運算來實現集合的並集、交集、差集等操做,從而獲得知足需求的數據;算法
在數學概念中兩個集合A\B的並集是在元素存在於A或者存在於B中,用數學表達式表達以下:
A∪B = {x | x ∈ A ∨ x ∈ B};segmentfault
// 並集 union(otherSet) { const unionSet = new MySet(); const values = this.values(); const otherSetValues = otherSet.values(); for (let i = 0; i < values.length; i++) { unionSet.add(values[i]); } for (let i = 0; i < otherSetValues.length; i++) { unionSet.add(otherSetValues[i]); } return unionSet; }
測試並集:api
const setA = new MySet(); setA.add(1); setA.add(2); setA.add(3); const setB = new MySet(); setB.add(3); setB.add(4); setB.add(5); setB.add(6); const unionSet = setA.union(setB); unionSet.values(); // [1,2,3,4,5,6]
A∩B = {x | x ∈ A ∧ x ∈ B}意思是x(元素)存在於A中,且x存在於B中。下圖展現了交集運算。數組
// 交集 intersection(otherSet) { const intersectionSet = new MySet(); const values = this.values(); const otherSetValues = otherSet.values(); const lenSelf = values.length; const lenOther = otherSetValues.length; if (lenSelf < lenOther) { for (let i = 0; i < lenSelf; i++) { if(otherSet.has(values[i])){ intersectionSet.add(values[i]) } } } else { for (let i = 0; i < lenOther; i++) { if(this.has(otherSetValues[i])){ intersectionSet.add(otherSetValues[i]) } } }; return intersectionSet; }
測試代碼:數據結構
const setA = new MySet(); setA.add(1); setA.add(2); setA.add(3); const setB = new MySet(); setB.add(2); setB.add(3); setB.add(4); const interse = setA.intersection(setB); console.log(interse.values()); // [2,3]
集合A和集合B的差集表示爲A - B,定義以下。A-B= {x|x∈A∧x∉B}意思是x(元素)存在於A中,且x不存在於B中。下圖展現了集合A和集合B的差集運算。
實現代碼:函數
// 差集 difference(otherSet) { const differenceSet = new MySet(); this.values().forEach(v => { if(!otherSet.has(v)) { differenceSet.add(v); } }); return differenceSet; }
測試代碼:
const setA = new MySet(); setA.add(1); setA.add(2); setA.add(3); const setB = new MySet(); setB.add(2); setB.add(3); setB.add(4); const differenceSet = setA.difference(setB); console.log(differenceSet.values()); // [1]
子集表示以下。
A ⊆ B該集合定義以下。{x | ∀x ∈ A ⇒ x ∈ B}意思是集合A中的每個x(元素),也須要存在於集合B中。下圖展現了集合A是集合B的子集。
實現方式:
isChildSet(otherSet) { if (this.size() > otherSet.size()) { return false; } let res = false; otherSet.values().every(v => { if (!otherSet.has(v)) { res = false; return false } return true; }) return res; }
下面我們來驗證一下測試一會兒集的驗證
const setA = new MySet(); setA.add(1); setA.add(2); setA.add(3); const setB = new MySet(); setB.add(1); setB.add(2); setB.add(3); setB.add(4); const setChild = setA.isChildSet(setB); console.log(setChild.values()); // true
es6新增了Set類做爲JavaScript API的一部分。咱們上面是實現了本身的集合,如今來看一下es6原生的set有哪些不一樣;
const setA = new Set(); setA.add(1); setA.add(2); console.log(setA.size); // 2 setA.delete(2);// true console.log(setA.values());//SetIterator
原生api的Set對於集合長度有一個屬性size,經過屬性能夠拿到集合的長度,咱們實現的時候也能夠把size方法轉變成屬性值的,此外原生values方法返回的不是直接的一個數組,而是一個Set迭代器SetIterator可進行迭代訪問;原生Set沒有提供集合的交集、並集、差集、子集的方法,不過也能夠進行擴展實現,並且運用es6的一些新型的語法特性以及api可以比較簡單的實現,這裏再也不贅述;
想了解更多請看:源碼
或者搜索公衆號:非著名bug認證師