深度優先遍歷,廣度優先遍歷實現對象的深拷貝
- 2019 年 10 月 3 日
- 筆記
深度優先遍歷
深度優先遍歷(Depth-First-Search),是搜索演算法的一種,它沿著樹的深度遍歷樹的節點,儘可能深地搜索樹的分支。當節點v的所有邊都已被探尋過,將回溯到發現節點v的那條邊的起始節點。這一過程一直進行到已探尋源節點到其他所有節點為止,如果還有未被發現的節點,則選擇其中一個未被發現的節點為源節點並重複以上操作,直到所有節點都被探尋完成。
簡單的說,DFS就是從圖中的一個節點開始追溯,直到最後一個節點,然後回溯,繼續追溯下一條路徑,直到到達所有的節點,如此往複,直到沒有路徑為止。
DFS和BFS一般是用來解決圖的遍歷的,但是在這裡,既然是前端,我是用DFS和BFS來遍歷DOM樹。
下面採用棧的形式或者遞歸的形式實現:
DOM節點
<div class="parent"> <div class="c-1"> <div class="c-1-1"> </div> <div class="c-1-2"> </div> <div class="c-1-3"> </div> <div class="c-1-4"> </div> <div class="c-1-5"> </div> </div> <div class="c-2"> <div class="c-2-1"> </div> <div class="c-2-2"> </div> <div class="c-2-3"> </div> <div class="c-2-4"> <div class="c-2-4-1"> </div> <div class="c-2-4-2"> </div> <div class="c-2-4-3"> </div> <div class="c-2-4-4"> </div> <div class="c-2-4-5"> </div> </div> <div class="c-2-5"> </div> </div> <div class="c-3"> </div> <div class="c-4"> </div> </div>
DFS實現
var node = document.querySelectorAll('.parent')[0]; //遞歸寫法 function DFS1 (node, nodeList = []){ if (node != null){ nodeList.push(node); let children = node.children for(let i = 0; i < children.length; i++){ DFS1(children[i], nodeList) } } return nodeList } let nodeList = DFS1(node); console.log(nodeList); //棧寫法 function DFS2(node){ let nodeList = []; if (node){ //棧 後進先出 let stack = []; stack.push(node); while (stack.length) { let _node = stack.pop(); nodeList.push(_node); let children = _node.children; //這樣寫是從右向左 // for (let i = 0; i < children.length; i++) { // stack.push(children[i]); // } //從左向右 for (let i = children.length-1; i >= 0; i--) { stack.push(children[i]); } } } return nodeList; } let nodeList2 = DFS2(node); console.log(nodeList2);
運行結果,上面DFS1和DFS2的結果是一樣的
廣度優先遍歷
廣度優先遍歷(Breadth-First-Search)是從根節點開始,沿著圖的寬度遍歷節點,如果所有節點均被訪問過,則演算法終止,BFS 同樣屬於盲目搜索,一般用隊列數據結構來輔助實現BFS。
還是採用上面的DOM節點。BFS的寫法如下。
程式碼採用隊列的形式實現。
var node = document.querySelectorAll('.parent')[0]; function BFS1(node, nodeList = []) { if (!node){ return; } //隊列 先進先出 var sequeue = []; sequeue.push(node); while (sequeue.length){ var _node = sequeue.shift(); nodeList.push(_node) for(var i = 0; i < _node.children.length; i++){ sequeue.push(_node.children[i]) } } return nodeList } let nodeList = BFS1(node); console.log(nodeList);
結果如下
下面採用兩種方式來實現對象深度克隆的實現。
DFS深度克隆
深度克隆要注意兩個問題
1、環狀數據問題:如果一個對象具有環狀對象,比如obj.a.b.c === obj.a
,就會使遞歸進入死循環,從而導致爆棧錯誤。
2、邊界處理: 對象中不止原始類型,還存在如函數、Set等數據類型,我們需要一一做處理。下面程式碼只是解決了對函數的複製。
let _toString = Object.prototype.toString; let map = { array: 'Array', object: 'Object', function: 'Function', string: 'String', null: 'Null', undefined: 'Undefined', boolean: 'Boolean', number: 'Number' } function getType(obj){ return _toString.call(obj).slice(8, -1) } function isTypeOf(obj, type){ return map[type] && map[type] === getType(obj) } //深度克隆 //深度優先遍歷 /** * * 解決三個問題 遞歸問題 環狀數據問題 邊界處理(比如函數,Set等) */ const DFSdeepClone = function (obj, visitedArr = []){ let _obj = {}; if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')){ let index = visitedArr.indexOf(obj); if (index > -1){ _obj = visitedArr[index] } else{ visitedArr.push(obj) for (let key in obj){ _obj[key] = DFSdeepClone(obj[key], visitedArr) } } } else if(isTypeOf(obj, 'function')){ _obj = eval( '(' + obj.toString() + ')')//處理函數 } else{ _obj = obj;//處理原始值 } return _obj; } let testObj = { a: 1, b: { c: 1, d: 2 }, circle: null, e: function() { console.log(1); } } let cloneTestObj = DFSdeepClone(testObj); let cloneTestObj2 = testObj; console.log(cloneTestObj); console.log('經過深度克隆後的更改'); cloneTestObj.b = {};//經過深度克隆後的更改 console.log(cloneTestObj); console.log(testObj); cloneTestObj2.b = {}; //引用的更改 console.log('引用的更改'); console.log(cloneTestObj2); console.log(testObj); //環狀數據 let testCircle = { a: 1, b: { c: 1, d: 2, circle: null, }, e: function() { console.log(1); } } testCircle.b.circle = testCircle.b; cloneTestCircle = DFSdeepClone(testCircle);//不處理環問題是會爆棧的 進入死循環 console.log(cloneTestCircle);
BFS深度克隆
let _toString = Object.prototype.toString; let map = { array: 'Array', object: 'Object', function: 'Function', string: 'String', null: 'Null', undefined: 'Undefined', boolean: 'Boolean', number: 'Number' } function getType(obj){ return _toString.call(obj).slice(8, -1) } function isTypeOf(obj, type){ return map[type] && map[type] === getType(obj) } //廣度優先深度克隆, 利用隊列的方式實現 //利用copyObj建立一個與原對象相同的數據結構, 遇到可處理的值(比如原始值,函數,就處理後賦值到相應的節點下) const BFSdeepClone = function (obj, visitedArr = []){ let copyObj = {}; let sequeue = [obj];//進隊列 //同時copyObj也跟著一起進隊列 let copySequeue = [copyObj]; while(sequeue.length){ let _obj = sequeue.shift(); let _copyObj = copySequeue.shift(); if (isTypeOf(_obj, 'array') || isTypeOf(_obj, 'object')){ for(item in _obj){ let val = _obj[item]; if (isTypeOf(val, 'object')){ let index = visitedArr.indexOf(val) if (~index){ //是環形數據 _copyObj[item] = visitedArr[index]; } else{ //新的對象,給copyObj一個對應屬性的空對象 sequeue.push(val); _copyObj[item] = {}; copySequeue.push(_copyObj[item]); visitedArr.push(val); } } else if (isTypeOf(val, 'array')){ sequeue.push(val); _copyObj[item] = []; copySequeue.push(_copyObj[item]) } else if(isTypeOf(val, 'function')){ _copyObj[item] = eval( '(' + val.toString() + ')');//處理函數 } else{ _copyObj[item] = val;//處理原始值 } } } else if(isTypeOf(obj, 'function')){ _copyObj = eval( '(' + _obj.toString() + ')');//處理函數 } else{ _copyObj = _obj;//處理原始值 } } return copyObj } let testObj = { a: 1, b: { c: 1, d: 2 }, circle: null, e: function() { console.log(1); } } let cloneTestObj = BFSdeepClone(testObj); let cloneTestObj2 = testObj; console.log(cloneTestObj); //環狀數據 let testCircle = { a: 1, b: { c: 1, d: 2, circle: null, }, e: function () { console.log(1); } } testCircle.b.circle = testCircle.b; cloneTestCircle = BFSdeepClone(testCircle);//不處理環問題是會爆棧的 進入死循環 console.log(cloneTestCircle); /** * 列印如下 { a: 1, b: { c: 1, d: 2 }, circle: null, e: [Function] } { a: 1, b: { c: 1, d: 2, circle: { c: 1, d: 2, circle: [Circular] } }, e: [Function] } */