深度優先遍歷,廣度優先遍歷實現物件的深拷貝

魑魅魍魎_killer發表於2019-08-05

深度優先遍歷

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

相關文章