js遞歸遍歷講解

JavaScript的遞歸遍歷會常常遇到,適當的運用遞歸遍歷,能夠提升代碼性質量。javascript

1.某些時候遞歸能替換for循環

咱們先看一下下面2個例子。前端

var arrList = [1,2,3,5,100,500,10000,10000,1000,10000002]
 //for循環測試
 function forTest(){
     console.time("for循環")
     for(let i in arrList){
         console.log(((arrList[i] + arrList[i]) * 5 - arrList[i])/arrList[i])
     }
     console.timeEnd("for循環")
 }
 //遞歸遍歷測試
 function recursive() {
     console.time("遞歸遍歷")
     const testFun = function (i) {
         console.log(((arrList[i] + arrList[i]) * 5 - arrList[i])/arrList[i])
         if(i == arrList.length - 1){
             return
         }
         i++
         testFun(i)
     }
     testFun(0)
     console.timeEnd("遞歸遍歷")
 }
 forTest()
 recursive()
複製代碼

運行結果: vue

在這裏插入圖片描述

能夠看到,for循環去遍歷一個數組和用遞歸遍歷去遍歷同一個數組獲得的結果同樣,耗時也幾乎相同。可是寫法上有很大區別。java

遞歸特色

每一個遞歸都有一個結束遞歸的條件,上例中的:if(i == arrList.length - 1){ return }。每個遞歸都會在函數內部把函數自己調用一次,可是函數在每次運行的時候,都會發生一些變化,用來觸發遞歸的結束,上例中,testFun函數在內部調用了本身,而且每次調用i的值會+1,最終觸發告終束條件,讓遞歸結束。git

2.使用遞歸,能夠輕鬆實現多級遍歷

咱們先看下面的代碼:es6

var data = [
 {
     name: "全部物品",
     children: [
         {
             name: "水果",
             children: [{name: "蘋果", children: [{name: '青蘋果'}, {name: '紅蘋果'}]}]
         },
         {
             name: '主食',
             children: [
                 {name: "米飯", children: [{name: '北方米飯'}, {name: '南方米飯'}]}
             ]
         },
         {
             name: '生活用品',
             children: [
                 {name: "電腦類", children: [{name: '聯想電腦'}, {name: '蘋果電腦'}]},
                 {name: "工具類", children: [{name: "鋤頭"}, {name: "錘子"}]},
                 {name: "生活用品", children: [{name: "洗髮水"}, {name: "沐浴露"}]}
             ]
         }
  ]
}]
複製代碼

某些時候,坑逼後臺讓咱們遍歷上面的一個數組,最後在頁面上顯示沒一級的最後一個數據。就是下面的數據:github

青蘋果;紅蘋果;北方米飯;南方米飯;聯想電腦;蘋果電腦;鋤頭;錘子;洗髮水;沐浴露;數組

咱們先用遍歷的方式來實現:異步

//普通遍歷實現
var forFunction = function () {
    var str = ""
    data.forEach(function(row){
        row.children.forEach(function(row){
            row.children.forEach(function(row){
                row.children.forEach(function(row){
                    str += (row.name + ";")
                })
            })
        })
    })
    console.log(str)
}
forFunction()
//輸出:青蘋果;紅蘋果;北方米飯;南方米飯;聯想電腦;蘋果電腦;鋤頭;錘子;洗髮水;沐浴露;
複製代碼

能夠看到,前端累死累死寫了4個forEach實現了產品要的效果,這時候若是再加點別的什麼邏輯,就很難弄了。這時候咱們嘗試用遞歸的思想實現:函數

//遞歸遍歷實現
var recursiveFunction = function(){
    var str = ''
    const getStr = function(list){
        list.forEach(function(row){
            if(row.children){
                getStr(row.children)
            }else {
                str += row.name + ";"
            }
        })
    }
    getStr(data)
    console.log(str)
}
recursiveFunction()
//輸出:青蘋果;紅蘋果;北方米飯;南方米飯;聯想電腦;蘋果電腦;鋤頭;錘子;洗髮水;沐浴露;
複製代碼

能夠看到,遞歸的方式來實現的時候,咱們只須要一個for循環,每次遍歷接受到的數據,經過判斷是否還有children,沒有就表明是最後一級了,有就繼續把children這個list傳給函數繼續遍歷,最後就獲得了咱們想要的數據。

很明顯,forEach的遍歷的方式能實現多級的遍歷,而且數據格式能夠靈活一些,可是遍歷的層級有限,並且對於未知層級的狀況下,就無從下手了。 遞歸遍歷,理論上,只要內存夠用,你能實現任意層級的遍歷,但缺點也很明顯,沒一個層級裏面須要有固定的數據格式,不然沒法遍歷。

3.遞歸遍歷輕鬆實現多個異步結果的依次輸出

咱們先看一下下面的需求,須要依次輸出1,2,3,4,5,每一個輸出中間間隔1s。 咱們先嚐試下面的方式:

//常規實現
var forTest = function () {
   console.time("for時間")
    for (let i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log('for循環輸出:' + (i + 1))
            if(i+ 1 === 5){
                console.timeEnd("for時間")
            }
        }, 1000 * (i + 1))
    }
}
forTest()
//每隔一秒輸出了1,2,3,4,5
複製代碼

上面咱們用let當前做用域的特色實現了每隔1s輸出,其實能夠看出,是一塊兒執行的,可是因爲間隔時間不同,致使結果是每隔一秒輸出的。 咱們再用遞歸的方式實現:

//遞歸遍歷實現
var recursiveTest = function(){
   console.time("遞歸時間")
    const test = function (i) {
        setTimeout(function () {
            i = i + 1
            console.log('遞歸輸出:' + i)
            if(i < 5){
                test(i)
            }else {
                console.timeEnd("遞歸時間")
            }
        }, 1000)
    }
    test(0)
}
recursiveTest()
//每隔一秒輸出1,2,3,4,5
複製代碼

這裏利用遞歸實現就很好理解了,在setTimeout裏面輸出了以後,再開始下一個1s的輸出。 最後咱們看一下運行截圖:

輸出結果
經過截圖上的耗時來看:遞歸遍歷須要的時間更長,可是更好理解一下,並且不依賴es6的環境。

4.遞歸遍歷實現排序

var quickSort = function(arr) {
if (arr.length <= 1) {//若是數組長度小於等於1無需判斷直接返回便可
    return arr;
}
var pivotIndex = Math.floor(arr.length / 2);//取基準點
var pivot = arr.splice(pivotIndex, 1)[0];//取基準點的值,splice(index,1)函數能夠返回數組中被刪除的那個數
var left = [];//存放比基準點小的數組
var right = [];//存放比基準點大的數組
for (var i = 0; i < arr.length; i++){ //遍歷數組,進行判斷分配
    if (arr[i] < pivot) {
        left.push(arr[i]);//比基準點小的放在左邊數組
    } else {
        right.push(arr[i]);//比基準點大的放在右邊數組
    }
}
//遞歸執行以上操做,對左右兩個數組進行操做,直到數組長度爲<=1;
return quickSort(left).concat([pivot], quickSort(right));
};

var arr = [14, 50, 80, 7, 2, 2, 11];
console.log(quickSort(arr));
複製代碼

這是利用遞歸實現的快速排序,效率很高,有興趣的同窗能夠試試普通的遍歷實現,再進行比較。

5.總結

1.不少時候能夠用遞歸代替循環,能夠理解爲遞歸是一種特殊的循環,但一般狀況下不推薦這樣作。 2.遞歸通常是在函數裏面把函數本身給調用一遍,經過每次調用改變條件,來結束循環。 3.遞歸在數據格式一致,在數據層級未知的狀況下,比普通的遍歷更有優點。 4.遞歸在異步的時候,更容易理解,且更容易實現,由於能夠在異步的回調裏面,調用本身來實現每次都能拿到異步的結果再進行其餘操做。 5.遞歸實現的快速排序比普通遍歷實現的排序效率更好。

全部的代碼都能在GitHub下載:下載。 想看更多文章,能夠關注個人我的公衆號:

公衆號
相關文章
相關標籤/搜索