Öncelikle kendi uyguladığım basit mantığı anlatacağım, daha ileri seviye alt kategori sistemleri için php recursive function şeklinde google da arama yapabilirsin.

Şimdi ilk olarak veri tabanından başlayalım. Veritabanın da kategoriler tablomuzda normal şekilde, kategoriID,kategoriAdi sütunlarımız olacak, buna ek olarak alt kategori sisteminde ustID diye bir sütun daha oluşturuyoruz, bu sütun eklenen kategorinin üst kategorisini çekmek için kullanılacak. Sistem şöyle işliyor, eğer eklediğimiz kategori ana kategori ise ustID = 0 yapıyoruz, eğer alt kategori ekliyor isek o alt kategorinin üst kategorisinin kategoriID değerini ekliyoruz. Sizin örnek gösterdiğiniz listeleme şeklini oluşturmak içinde şu kodu kullanabiliriz.
<?php
 $anaSql = mysql_query("select * from kategoriler where ustID=0);
?>
Bu şekilde ana kategorileri veritanından çektik, şimdi listeleyelim.
<?php 
  while($anaRow = mysql_fetch_array($anaSql)){
      echo ' <ul><span>'.$anaRow["kategoriAdi"].'</span>';
      $altSql = mysql_query("select * from kategoriler where ustID='$anaRow[katID]'");
        if(mysql_affected_rows()){
           while($altRow = mysql_fetch_array($altSql)){
           echo '<li> '.echo $altRow["kategoriAdi"].'</li>';
            }
       }
echo '</ul>';
  }

?>
Bu şekilde listeleme işlemini tamamladık. Yaptığımız şey ise, ilk başta ki kod ile ana kategorileri getirmiştik onları while döngüsüne sokup, tek tek o ana kategoriye ait alt kategori olup olmadığını kontrol ediyoruz. mysql_affected_rows() fonksiyonu da kaba tabirle, en son sql sorgusundan geriye true değer dönüp dönmediğini kontrol ediyor. Yani eğer alt kategori var ise, o if blokunun içerisini yazdırıyor. Alt kategorileri de sql ile çektikten sonra bir while daha oluşturup, onları da yazdırmış olduk. Eğer ki hiç alt kategorisi olmayan bir ana kategori eklendiğinde, sadece ana kategorinin ismi yazar. Umarım anlatabilmişimdir