
在处理复杂的树形数据结构时,例如网站的分类目录或产品层级,我们经常会遇到需要清理“空”节点的情况。这里的“空”节点指的是那些自身不包含任何内容,并且其所有子节点(包括深层子节点)也都不包含任何内容的节点。我们的目标是移除这些冗余节点,只保留那些直接包含内容,或者其任意子孙节点包含内容的路径。这不仅能使数据结构更紧凑,还能提高数据查询和展示的效率。
假设我们有一个PHP数组表示的类别树,其结构大致如下:
[
'uid_of_category_A' => [
'content' => [], // 空内容
'sub_categories' => [
'uid_of_category_B' => [
'content' => [], // 空内容
'sub_categories' => []
],
'uid_of_category_C' => [
'content' => ['item1', 'item2'], // 包含内容
'sub_categories' => []
]
]
],
'uid_of_category_D' => [
'content' => [], // 空内容
'sub_categories' => [
'uid_of_category_E' => [
'content' => [], // 空内容
'sub_categories' => [
'uid_of_category_F' => [
'content' => ['item3'], // 包含内容
'sub_categories' => []
]
]
]
]
]
]我们的任务是:
例如,在上述结构中,uid_of_category_B 是一个空节点,应该被移除。uid_of_category_A 自身没有内容,但其子节点 uid_of_category_C 包含内容,所以 uid_of_category_A 应该被保留。uid_of_category_D 自身没有内容,但其子孙节点 uid_of_category_F 包含内容,所以 uid_of_category_D 也应该被保留。
解决这类树形结构问题,递归是首选的强大工具。为了清晰地分离判断逻辑和清理操作,我们可以采用两个辅助函数协同工作:
立即学习“PHP免费学习笔记(深入)”;
isCleanable 函数的职责是自底向上地判断一个类别是否“可清理”。它的逻辑是:如果一个类别自身有内容,那么它就不可清理。如果它没有内容,那么它是否可清理取决于它的所有子类别是否都可清理。
/**
* 判断一个类别是否可以被清理(即自身无内容且所有子孙节点均无内容)。
*
* @param array $category 待判断的类别节点
* @return bool 如果类别及其所有子孙节点均无内容,则返回 true;否则返回 false。
*/
function isCleanable(array $category): bool
{
// 1. 如果当前类别自身包含内容,则它不可被清理。
if (!empty($category['content'])) {
return false;
}
// 2. 遍历其子类别,如果任何一个子类别不可被清理,则当前类别也不可被清理。
// 这里递归调用 isCleanable,实现自底向上检查。
foreach ($category['sub_categories'] as $subCategory) {
if (!isCleanable($subCategory)) {
return false; // 发现一个子类别不可清理,则当前类别也不可清理
}
}
// 3. 如果当前类别无内容,且所有子类别都可被清理(即所有子孙节点都无内容),
// 那么当前类别是可被清理的。
return true;
}逻辑解析:
cleanCategories 函数负责遍历给定的类别列表,并利用 isCleanable 的判断结果来移除符合条件的类别。这个函数需要通过引用传递类别数组,以便直接修改原始数据结构。
/**
* 清理类别数组,移除所有符合清理条件的空类别。
*
* @param array $categories 待清理的类别数组,通过引用传递。
*/
function cleanCategories(array &$categories): void
{
foreach ($categories as $key => &$category) { // 注意:这里 $category 也要通过引用传递,以便修改其子类别
// 1. 判断当前类别是否可以被清理
if (isCleanable($category)) {
unset($categories[$key]); // 如果可清理,则从数组中移除
} else {
// 2. 如果当前类别不可清理(因为它自身有内容,或其子孙有内容),
// 则递归清理其子类别。
// 注意:这里 $category['sub_categories'] 必须是数组,即使为空。
if (isset($category['sub_categories']) && is_array($category['sub_categories'])) {
cleanCategories($category['sub_categories']);
}
}
}
}逻辑解析:
结合上述两个函数,我们可以构建完整的清理逻辑。
<?php
// 示例类别数据结构
$categoryTree = [
'uid_A' => [
'content' => [],
'sub_categories' => [
'uid_B' => [
'content' => [],
'sub_categories' => [] // 这个节点是空的,应该被移除
],
'uid_C' => [
'content' => ['item1'],
'sub_categories' => [] // 这个节点有内容,其父节点A应保留
]
]
],
'uid_D' => [
'content' => [],
'sub_categories' => [
'uid_E' => [
'content' => [],
'sub_categories' => [
'uid_F' => [
'content' => ['item2'],
'sub_categories' => [] // 这个节点有内容,其父节点E和D应保留
]
]
]
]
],
'uid_G' => [
'content' => [],
'sub_categories' => [] // 这个节点是空的,应该被移除
]
];
/**
* 判断一个类别是否可以被清理(即自身无内容且所有子孙节点均无内容)。
*
* @param array $category 待判断的类别节点
* @return bool 如果类别及其所有子孙节点均无内容,则返回 true;否则返回 false。
*/
function isCleanable(array $category): bool
{
if (!empty($category['content'])) {
return false;
}
// 确保 'sub_categories' 键存在且为数组
if (!isset($category['sub_categories']) || !is_array($category['sub_categories'])) {
return true; // 没有子类别且自身无内容,则可清理
}
foreach ($category['sub_categories'] as $subCategory) {
if (!isCleanable($subCategory)) {
return false;
}
}
return true;
}
/**
* 清理类别数组,移除所有符合清理条件的空类别。
*
* @param array $categories 待清理的类别数组,通过引用传递。
*/
function cleanCategories(array &$categories): void
{
foreach ($categories as $key => &$category) {
if (isCleanable($category)) {
unset($categories[$key]);
} else {
// 确保 'sub_categories' 键存在且为数组,避免访问不存在的键
if (isset($category['sub_categories']) && is_array($category['sub_categories'])) {
cleanCategories($category['sub_categories']);
}
}
}
}
echo "清理前的类别树:\n";
print_r($categoryTree);
// 执行清理操作
cleanCategories($categoryTree);
echo "\n清理后的类别树:\n";
print_r($categoryTree);
?>运行结果示例: 清理前的 uid_B 和 uid_G 节点将消失,uid_A 和 uid_D 节点会保留,但其内部结构会变得更精简。
清理前的类别树:
Array
(
[uid_A] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_B] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
)
)
[uid_C] => Array
(
[content] => Array
(
[0] => item1
)
[sub_categories] => Array
(
)
)
)
)
[uid_D] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_E] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_F] => Array
(
[content] => Array
(
[0] => item2
)
[sub_categories] => Array
(
)
)
)
)
)
)
[uid_G] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
)
)
)
清理后的类别树:
Array
(
[uid_A] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_C] => Array
(
[content] => Array
(
[0] => item1
)
[sub_categories] => Array
(
)
)
)
)
[uid_D] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_E] => Array
(
[content] => Array
(
)
[sub_categories] => Array
(
[uid_F] => Array
(
[content] => Array
(
[0] => item2
)
[sub_categories] => Array
(
)
)
)
)
)
)
)通过 isCleanable 和 cleanCategories 这两个协同工作的递归函数,我们能够优雅且高效地清理复杂的树形结构。isCleanable 负责自底向上地判断节点的“空”状态,而 cleanCategories 则负责自顶向下地遍历并执行实际的删除操作,确保只有有意义的路径被保留。这种模式不仅适用于类别树清理,也可以推广到其他需要根据子节点状态来决定父节点去留的树形数据处理场景。掌握递归思维和按引用传递的技巧,是处理复杂数据结构的关键。
以上就是高效修剪:递归算法清理PHP类别树中的空节点的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号