You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于URL的搜索查询聚类:内存优化方案及现成类库咨询

基于共同URL聚类搜索查询的优化方案

嘿,我明白你现在遇到的问题了——基于共享URL对搜索查询做聚类,核心是把有至少3个共同URL的查询归为一组,但原来的递归遍历算法内存占用太高,想找更高效的实现方式对吧?先梳理下你的场景和现有代码的问题,再给你推荐最优的解决方案。

首先看你给出的示例数据结构:

$words = [
    [
        "word" => "wardrobe in the bedroom",
        "id" => 1,
        "urls" => ["url_1", "url_2", "url_3", "url_4", "url_5"]
    ],
    [
        "word" => "wardrobe in the living room",
        "id" => 2,
        "urls" => ["url_1", "url_2", "url_3", "url_4", "url_5"]
    ],
    [
        "word" => "white wardrobe in the bedroom",
        "id" => 3,
        "urls" => ["url_1", "url_2", "url_3", "url_4", "url_5"]
    ]
    // ... 更多查询元素
];

你当前的递归实现确实存在内存和性能问题:

function cluster($words, $group = array()) {
    $first_elem = array_shift($words);
    $first_group = $first_elem['id'];
    $array_urls = $first_elem['urls'];
    foreach ($words as $i=>$data) {
        $check = array_intersect_key($array_urls, $data['urls']);
        if(count($check) >= 3) {
            $group[$first_group][$i] = $data['word'];
            unset($words[$i]);
        }
    }
    if(!empty($words)) return cluster($words, $group);
    return $group;
}

现有代码的核心问题

  • 递归栈溢出风险:每次递归都会创建新的函数栈帧,当查询数量很大时,栈内存会快速累积,甚至触发PHP的栈溢出限制
  • 数组重构开销:array_shift和unset操作会让PHP重新分配数组内存,频繁的数组修改会大幅增加内存占用
  • 低效的双重遍历:外层递归+内层foreach的逻辑是O(n²)的时间复杂度,数据量越大,计算和内存消耗越夸张

优化方案:使用并查集(Union-Find)数据结构

并查集是专门处理连通分量分组问题的高效数据结构,它的时间复杂度接近O(α(n))(α是阿克曼函数的反函数,增长极慢,几乎可以看作常数),内存开销远低于递归遍历。

第一步:实现轻量并查集类

class UnionFind {
    private $parent = [];
    private $rank = [];

    public function __construct($elementIds) {
        foreach ($elementIds as $id) {
            // 每个元素初始父节点是自己
            $this->parent[$id] = $id;
            $this->rank[$id] = 0;
        }
    }

    // 查找元素的根节点(带路径压缩,减少后续查找开销)
    public function find($x) {
        if ($this->parent[$x] !== $x) {
            $this->parent[$x] = $this->find($this->parent[$x]);
        }
        return $this->parent[$x];
    }

    // 合并两个元素所在的集合(按秩合并,保持树的平衡)
    public function union($x, $y) {
        $xRoot = $this->find($x);
        $yRoot = $this->find($y);

        if ($xRoot === $yRoot) {
            return; // 已经在同一组,无需合并
        }

        // 把秩小的树合并到秩大的树下,保持平衡
        if ($this->rank[$xRoot] < $this->rank[$yRoot]) {
            $this->parent[$xRoot] = $yRoot;
        } else {
            $this->parent[$yRoot] = $xRoot;
            if ($this->rank[$xRoot] === $this->rank[$yRoot]) {
                $this->rank[$xRoot]++;
            }
        }
    }

    // 获取所有分组(键是根节点ID,值是该组的所有元素ID)
    public function getGroups() {
        $groups = [];
        foreach ($this->parent as $id => $root) {
            $rootId = $this->find($id);
            $groups[$rootId][] = $id;
        }
        return $groups;
    }
}

第二步:编写聚类逻辑

function clusterQueries($words) {
    // 提前建立ID到查询的映射,避免后续重复遍历查找
    $idToQuery = [];
    $allIds = [];
    foreach ($words as $query) {
        $id = $query['id'];
        $idToQuery[$id] = $query;
        $allIds[] = $id;
    }

    // 初始化并查集
    $uf = new UnionFind($allIds);

    // 提前把每个查询的URL转为关联数组,方便快速计算交集
    $queryUrlMaps = [];
    foreach ($words as $query) {
        $queryUrlMaps[$query['id']] = array_flip($query['urls']);
    }

    // 遍历所有查询对,检查共同URL数量,满足条件则合并分组
    $wordCount = count($words);
    for ($i = 0; $i < $wordCount; $i++) {
        $queryA = $words[$i];
        $idA = $queryA['id'];
        $urlsA = $queryUrlMaps[$idA];

        for ($j = $i + 1; $j < $wordCount; $j++) {
            $queryB = $words[$j];
            $idB = $queryB['id'];

            // 已经在同一组的跳过,避免重复计算
            if ($uf->find($idA) === $uf->find($idB)) {
                continue;
            }

            $urlsB = $queryUrlMaps[$idB];
            // 计算共同URL数量
            $commonCount = count(array_intersect_key($urlsA, $urlsB));
            if ($commonCount >= 3) {
                $uf->union($idA, $idB);
            }
        }
    }

    // 转换为最终的分组结果(包含查询文本)
    $rawGroups = $uf->getGroups();
    $result = [];
    foreach ($rawGroups as $rootId => $ids) {
        $result[$rootId] = array_map(function($id) use ($idToQuery) {
            return $idToQuery[$id]['word'];
        }, $ids);
    }

    return $result;
}

关于现成类库

PHP生态中没有专门针对这类URL聚类的专属类库,但并查集是通用的数据结构,你可以找到一些第三方PHP数据结构包包含它的实现。不过我更推荐自己实现,因为代码量很小,轻量且完全可控,不需要引入额外依赖。

内容的提问来源于stack exchange,提问作者gooex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 13:07:45