PHP递归的替换,优化递归太深栈溢出

178 阅读2分钟

前言:

在多级推广或无限极相连数据中,我们会通常使用递归来解决,但是递归深度过深会导致栈溢出,所以我们采用使用迭代(队列)来替代递归,从而实现性能更优化的查询优化。

1.使用技术SplQueue

SplQueue 是 PHP 标准库(SPL)中的一个类,用于实现队列数据结构。队列是一种先进先出(FIFO)的数据结构,适用于需要按顺序处理元素的场景。SplQueue 提供了多种方法来操作队列,如 enqueue(入队)、dequeue(出队)、isEmpty(检查队列是否为空)等。

2. 如何完美替代递归

在处理树形结构或图结构的遍历时,递归是一种常见的方法。然而,递归可能会导致栈溢出问题,尤其是在数据量较大或层级较深的情况下。SplQueue 可以通过迭代的方式完美替代递归,避免栈溢出的问题。

3.例子:使用 SplQueue 替代递归

假设我们需要遍历一个多级嵌套的用户团队结构,计算每个用户及其所有下级用户的销售数据总和。递归的方法可能会导致栈溢出,而使用 SplQueue 则可以避免这个问题。

4. 解释

  1. 初始化队列

     $queue = new SplQueue(); 
     foreach ($userIds as $userId) 
     { 
     $queue->enqueue($userId); 
     }
    

将初始用户ID列表中的每个ID加入队列。

2.迭代处理队列

while (!$queue->isEmpty()) {
    $currentUserId = $queue->dequeue(); // 统计当前用户的销售数据 
    $count_sum += self::countSaleData([$currentUserId]); // 查找当前用户的下级用户
    $subUserIds = model('user_son')->where('aid', $currentUserId)->column('uid'); 
    if (!empty($subUserIds)) { 
        foreach ($subUserIds as $subUserId) { 
            $queue->enqueue($subUserId); 
        }
    }
}


  • 使用 while 循环不断从队列中取出用户ID进行处理。
  • 调用 countSaleData 方法统计当前用户的销售数据,并累加到 count_sum 中。
  • 查找当前用户的下级用户,并将这些下级用户的ID加入队列,以便后续处理。
  • return $count_sum; // 返回结果

代码总览:

1.数据库结构

CREATE TABLE `user_son` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `aid` int(10) NOT NULL COMMENT '父级',
  `uid` int(10) NOT NULL COMMENT '子级',
  `create_time` int(10) NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户子级表';

2.代码内容

use SplQueue; // 引入 SplQueue 类
use think\Log; // 引入 Log 类

public function user() {
    $userIds = model('user')->field('id')->select();
    
    foreach($userIds as $v) {
           $v['num'] = 0;
           // 下级团队数据
           $userArray = model('user_son')->where('aid', $v['id'])->column('uid');
           $v['son_team'] = self::getAllSonTeamData($userArray, 0);
    }
}

/** 
 * @notes 获取所有下级团队的销售数据(包括无限级别) 
 * @param array $userIds 用户ID列表 
 * @param int $count_sum 销售数据总和 
 * @author Milo 
 * @return number 
 */
    public static function getAllSonTeamData(array $userIds, $count_sum = 0)
    {
        try {
            $queue = new SplQueue();
            foreach ($userIds as $userId) {
                $queue->enqueue($userId);
            }

            while (!$queue->isEmpty()) {
                $currentUserId = $queue->dequeue();
                // 统计当前用户的销售数据
                $count_sum += self::countSaleData([$currentUserId]);

                // 查找当前用户的下级用户
                $subUserIds = model('user_son')->where('aid', $currentUserId)->column('uid');
                if (!empty($subUserIds)) {
                    foreach ($subUserIds as $subUserId) {
                        $queue->enqueue($subUserId);
                    }
                }
            }

            return $count_sum;
        } catch (\Exception $e) {
            // 处理异常情况
            Log::error('Error in getAllSonTeamData: ' . $e->getMessage());
            return 0;
        }
    }
     
    /**
    * @notes 数据总和
    **/
    public static function countSaleData(array $userArray)
    {
        if (empty($userArray)) return 0;

        try {
            // 统计销售数据总和
            $count = model('user')->whereIn('id', $userArray)->sum('sale_num');
            return $count;
        } catch (\Exception $e) {
            // 处理异常情况
            Log::error('Error in countSaleData: ' . $e->getMessage());
            return 0;
        }
    }
    

谢谢观看,如有不足请指出,学习进步! Tomorrow will be better