在各種欄目以及分類設定中,無限分類經常會被用到,而無限分類在進行排序的時候必然要用到遞迴,這裡進行一次詳細的分析解讀。
首先我們先瞭解一下遞迴函式:
遞迴函式在語言學習的時候會單獨拿出來學習,因為它非常常用,本質上來講遞迴函式就是調用自己的函數。
舉個例子:
<?phpfunction test(){static $a=0;if($a<10){$a++;test();}echo $a."<br/>";}test();
test函數裡面又再調用了自身,這就是俗稱的遞迴函式。遞迴函式都有條件設定,不然的會無限迴圈下去,這樣會導致程式奔潰。
所以一般來講我總結遞迴函式有兩個特點:
一個是記錄條件值,記錄的條件值必須保證不會再下一次調用時丟失。
test函數裡$a便是記錄條件值,它是依靠使用static關鍵字來保證記錄每次增加的數值不會再下一次調用test()函數而丟失,因為函數中static修飾的變數僅僅在第一次初始化,並保留變數值。所以只要保證這一點,不光static,其他的方式也可以達到目的,例如global還有&修飾符。
另一個是條件檢查。test裡面對$a大小的限制就是該條件的檢查過程。
test函數中if($a<10)就是這個條件檢查的過程,它限制了test()函數對自身的調用,這樣就可以防止無限調用導致程式奔潰。
函數a內部調用另外的函數b,如果b函數沒有完成,那麼a函數就會一直等待下去,直到b函數完成,才會回到a函數繼續執行。遞迴的過程中利用了這個特性,正是這個能協助我們對無限分類進行排序,用上面test()遞迴函式說明:
上面test()列印出來的結果是:
10,10,10,10,10,10,10,10,10,10,10
可能和很多人想的不一樣,大概有許多人會覺得不應該是0,1,2,3,4,5,6,7,8,9,10這樣的結果嗎。
這是因為函數test調用過程中,只要$a<10,就會調用自身的test(),每次調用的test()都沒有到達echo處,也就是每次調用的test函數並沒有完結,直到$a遞增到了10,才第一次echo $a,這個時候$a已經是10了,因此第一個10實際是遞迴了11次後的$a,第十一次遞迴的test由於不符合<10的條件該函數完結,這個時候才開始回到遞迴第十次test函數裡執行echo,這個時候由於$a是靜態變數,值已經是10了,因此echo出的結果是10,下面依次回到之前的test函數完成前面未完成的echo步驟,因此echo出11個10,最後一個10實際是第一次執行test函數的echo結果。
無限分類的排序完成也是用的這個原理,遞迴排序函數不斷的通過parentid等於上一級id的子類來匹配該類別的子類別,只要找到第一個子類,就用找到的這個子類的id去找下一級的子類,直到沒有更下級的子類的時候,才返回上一級接著繼續找,找到後又開始尋找該子類下一級子類,直到沒有為止才返回,這個過程不斷迴圈。可能用文字不太能理解,下面的執行個體中我會畫出圖例,請先往後看。
我們先來看無限分類的資料er圖:
例如一個裙子的類目,它有父系類別女裝,女裝又屬於衣服的類目,假定裙子的id為3,女裝為2,衣服為1,那麼裙子的parentid就是直接的上級類目裙子的id,因此parentid=2,而child是裙子所在的樹形結構上全部的祖先元素id,那麼child應該是1,2,3,因此裙子的深度deep為3,這裡我假設的是用,隔開,也可以用其他的符號隔開,title不用說就是裙子。
具體我們來看這個樹形結構:
從樹形結構分類還可以繼續延伸下去,上一級的id是下一級的parentid,就是通過id和parent這兩個列來實現基本的無限分類的,再進行無限分類的遞迴排序的時候也是依靠這兩個欄位的關係。
下面是一個無限分類的表結構以及資料的例子:
為了更清楚下面畫了樹形圖:
上一代的類別的id就是自身的parentid,最高一級的parentid則為0,這點非常重要,是無限分類的排序的依據。
我們所要的無限分類排序效果應該是各欄目下的子欄目都放到該欄目下方,用id進行排序,我們要的效果就是下面這樣,為了方便查看我用了tab以及|——來間隔區分欄目之間的關係:
衣服
|——男裝
|——休閑上衣
|——短袖
|——長袖
|——休閑褲
|——女裝
|——女裝上衣
|——女裝下裝
|——牛仔褲
|——裙子
由於類別可以無限延伸下去,所以這裡明顯我們需要使用遞迴函式進行排序分類。
擷取資料所有結果,並按id排序
$mysqli=new mysqli('localhost','root','root','test')or die("串連失敗");$mysqli->set_charset("utf8");$re=$mysqli->query("select * from col order by id asc");$result=$re->fetch_all(MYSQLI_ASSOC);
遞迴排序函數
function recursion($result,$parentid=0){/*記錄排序後的類別數組*/static $list=array();foreach ($result as $k => $v){ if($v['parentid']==$parentid){ /*將該類別的資料放入list中*/ $list[]=$v; recursion($result,$v['id']); } } return $list;}
這個時候我們就能得到按類別排序好的數組了,但是如果要有相應的格式,例如上面的|——,就需要對函數進行改進。
function recursion($result,$parentid=0,$format="|--"){/*記錄排序後的類別數組*/static $list=array();foreach ($result as $k => $v){ if($v['parentid']==$parentid){ if($parentid!=0){ $v['title']=$format.$v['title']; } /*將該類別的資料放入list中*/ $list[]=$v; recursion($result,$v['id']," ".$format); } } return $list;}$list=recursion($result,0,'|--');
這樣排序的結果如下:
整個遞迴函式的執行情況大概是什麼樣的,我們接上面說過的要畫的執行個體圖,看圖後就很清楚這個原理了,這個圖只畫了部分,不過足夠理解這個過程了: