相信很多朋友在學PHP的時候都會想製作一個網站來提高自己的技術,像是企業網站,商城網站這種他們個最近的功能,欄目的管理,用到的都是無限級分類的方法,我們接下來將透過對無限級分類技術的學習來探討其深層的實現邏輯。
什麼是無限級分類?
無限級分類是一種分類技巧,例如部門組織,文章分類,學科分類等常用到無限級分類,將其簡單理解成分類就好了。其實我們仔細想一下,生活中的分類簡直太多了,衣服可以分成男裝和女裝,也可以分為上衣和褲子,也可以依照年齡分類。分類無所不在,分類顯得「無限」。我這裡就不說無限分類的必要性了。
無限級分類原理簡介
無限分類看似"高大上",實際上原理是非常簡單的 。無限分類不僅需要程式碼的巧妙性,也要依托資料庫設計的合理性。要滿足無限級分類,資料庫需要有兩個必須的字段,id,pid。 id用來標識自身,而pid則是用來表示父級id。也就是說,每個分類記錄不僅描述了自身,還描述了與其關心最為緊密的另一個id。看似複雜的事情被這樣一個小技巧解決了。
實例
建庫
表名:category
name varchar 分類名稱pid int 父類id,預設0
頂級分類的pid 預設就是0了。當我們想取出某個分類的子分類樹的時候,基本思路就是遞歸,當然,出於效率問題不建議每次遞歸都查詢數據庫,通常的做法是先講所有分類取出來,保存到PHP數組裡,再進行處理,最後還可以將結果快取起來以提高下次請求的效率。
先來建立一個原始數組,這個直接從資料庫中拉出來就行:$categories = array(
array('id'=>1,'name'=>'电脑','pid'=>0),
array('id'=>2,'name'=>'手机','pid'=>0),
array('id'=>3,'name'=>'笔记本','pid'=>1),
array('id'=>4,'name'=>'台式机','pid'=>1),
array('id'=>5,'name'=>'智能机','pid'=>2),
array('id'=>6,'name'=>'功能机','pid'=>2),
array('id'=>7,'name'=>'超级本','pid'=>3),
array('id'=>8,'name'=>'游戏本','pid'=>3),
)
用陣列來表示的話,可以增加一個children 鍵來儲存它的子分類:array(
//1对应id,方便直接读取
1 => array(
'id'=>1,
'name'=>'电脑',
'pid'=>0,
children=>array(
&array(
'id'=>3,
'name'=>'笔记本',
'pid'=>1,
'children'=>array(
//此处省略
)
),
&array(
'id'=>4,
'name'=>'台式机',
'pid'=>1,
'children'=>array(
//此处省略
)
),
)
),
//其他分类省略
)
處理過程:
$tree = array(); //第一步,将分类id作为数组key,并创建children单元 foreach($categories as $category){ $tree[$category['id']] = $category; $tree[$category['id']]['children'] = array(); } //第二部,利用引用,将每个分类添加到父类children数组中,这样一次遍历即可形成树形结构。 foreach ($tree as $k=>$item) { if ($item['pid'] != 0) { $tree[$item['pid']]['children'][] = &$tree[$k]; } } print_r($tree);
Array( [1] => Array ( [id] => 1 [name] => 电脑 [pid] => 0 [children] => Array ( [0] => Array ( [id] => 3 [name] => 笔记本 [pid] => 1 [children] => Array ( [0] => Array ( [id] => 7 [name] => 超级本 [pid] => 3 [children] => Array ( ) ) [1] => Array ( [id] => 8 [name] => 游戏本 [pid] => 3 [children] => Array ( ) ) ) ) [1] => Array ( [id] => 4 [name] => 台式机 [pid] => 1 [children] => Array ( ) ) ) ) [2] => Array ( [id] => 2 [name] => 手机 [pid] => 0 [children] => Array ( [0] => Array ( [id] => 5 [name] => 智能机 [pid] => 2 [children] => Array ( ) ) [1] => Array ( [id] => 6 [name] => 功能机 [pid] => 2 [children] => Array ( ) ) ) ) [3] => Array ( [id] => 3 [name] => 笔记本 [pid] => 1 [children] => Array ( [0] => Array ( [id] => 7 [name] => 超级本 [pid] => 3 [children] => Array ( ) ) [1] => Array ( [id] => 8 [name] => 游戏本 [pid] => 3 [children] => Array ( ) ) ) ) [4] => Array ( [id] => 4 [name] => 台式机 [pid] => 1 [children] => Array ( ) ) [5] => Array ( [id] => 5 [name] => 智能机 [pid] => 2 [children] => Array ( ) ) [6] => Array ( [id] => 6 [name] => 功能机 [pid] => 2 [children] => Array ( ) ) [7] => Array ( [id] => 7 [name] => 超级本 [pid] => 3 [children] => Array ( ) ) [8] => Array ( [id] => 8 [name] => 游戏本 [pid] => 3 [children] => Array ( ) ) )
function test($a=0,&$result=array()){ $a++; if ($a<10) { $result[]=$a; test($a,$result); } echo $a; return $result; }
function test($a=0,$result=array()){ global $result; $a++; if ($a<10) { $result[]=$a; test($a,$result); } return $result; }
缺點:使用PHP處理,如果分類數量龐大,效率也會降低。
引申-------遞歸函數的遞歸函數
函數在調用自身前有條件判斷,否則無限無限調用下去。實作遞歸函數可以採取什麼方式呢?本文列出了三種基本方式。理解其原來需要一定的基礎知識水品,包括對全域變量,引用,靜態變量的理解,也需對他們的作用範圍有所理解。遞歸函數也是解決無限級分類的一個很好地技巧。如果對無限級分類感興趣,請參考php利用遞歸函數實現無限級分類。我習慣套用通俗的話解釋複雜的道理,您確實不明白請參考手冊。 利用引用做參數先不管引用做不做參數,必須先明白引用到底是什麼?引用不過是指兩個不同名的變數指向同一塊儲存位址。本來每個變數有各自的儲存位址,賦值刪除各行其道。現在可好,兩個變數共用一塊儲存位址。 $a=&$b; 。實際上指的是 $a 不管不顧自己原來的儲存地址,非要和 $b 共用一室了。因而任何對儲存位址數值的改變都會影響兩個值。
函數之間本來也是各行其是,即便是同名函數。遞歸函數是考慮將引用作為參數,成為一個橋樑,形成兩個函數間的資料共享。雖然兩個函數見貌似操作的是不同地址,但是實際上操作的是一塊兒內存地址。function test(){ static $count=0; echo $count; $count++; } test(); test(); test(); test(); test();
上面的例子非常简答,以a<10作为判断条件,条件成立,则把a<10作为判断条件,条件成立,则把a赋给result[];将result[];将result的引用传入函数,会将每一次递归产生的a添加到结果数组a添加到结果数组result。因而本例生成的$result数组是 Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 [6] => 7 [7] => 8 [8] => 9 ) 。
本例比较有意思的是echo a的值。相信很多人认为是12345678910吧,其实不然,是1098765432。为什么呢?因为函数还没执行echoa的值。相信很多人认为是12345678910吧,其实不然,是1098765432。为什么呢?因为函数还没执行echoa前就进行了下一次的函数递归。真正执行echo a是当a是当a<10条件不满足的时候,echo a,返回a,返回result,对于上一层而言,执行完递归函数,开始执行本层的echo $a,依次类推。
利用全局变量
利用全局变量完成递归函数,请确保你确实理解什么是全局变量。global在函数内申明变量不过是外部变量的同名引用。变量的作用范围仍然在本函数范围内。改变这些变量的值,外部同名变量的值自然也改变了。但一旦用了&,同名变量不再是同名引用。利用全局变量实现递归函数没必要理解到这么深的一层,还保持原有对全局变量的看法就可以顺理成章理解递归函数。
function test($a=0,$result=array()){ global $result; $a++; if ($a<10) { $result[]=$a; test($a,$result); } return $result; }
利用静态变量
我们常常在类中见到static,今天我们把它利用到递归函数中。请记住static的作用:仅在第一次调用函数的时候对变量进行初始化,并且保留变量值。
例子:
function test(){ static $count=0; echo $count; $count++; } test(); test(); test(); test(); test();
请问这一段代码的执行结果是多少?是00000么?必然不是。是01234。首先第一次调用test(),static对 $count 进行初始化,其后每一次执行完都会保留 $count 的值,不再进行初始化,相当于直接忽略了 static$count=0; 这一句。
因而将static应用到递归函数作用可想而知。在将需要作为递归函数间作为“桥梁"的变量利用static进行初始化,每一次递归都会保留"桥梁变量"的值。
function test($a=0){ static $result=array(); $a++; if ($a<10) { $result[]=$a; test($a); } return $result; }
总结
所谓递归函数,重点是如何处理函数调用自身是如何保证所需要的结果得以在函数间合理"传递",当然也有不需要函数之间传值得递归函数,例如:
function test($a=0){ $a++; if ($a<10) { echo $a; test($a); } }
面对这样的函数,深入理解变量引用相关知识对解决这类问题大有裨益。
相关文章: