深度優先遍歷
深度優先遍歷(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]
}
*/