讲师中心 微信公众号
AI工具推荐 视频效率加速

高效重排数组:基于父子关系与显示优先级的复杂排序策略

老芳君_5171

老芳君_5171

发布时间:2025-09-17 08:50:11

|

381人浏览过

|

来源于php中文网

原创

高效重排数组:基于父子关系与显示优先级的复杂排序策略

本文旨在提供一个全面的教程,详细讲解如何根据元素的父子关系(id与reference_id)和显示优先级(display_priority)对复杂数组进行重排序。我们将通过构建数据索引、组织父子关系、分步应用排序规则,最终生成一个结构清晰、符合预期的有序数组。

引言:理解复杂数组重排序需求

在前端或后端开发中,我们经常会遇到需要对数据列表进行复杂排序的场景。其中一种常见的需求是,数据项之间存在层级关系(例如,父子关系),同时每个数据项还有自身的显示优先级。我们的目标是将子项紧随其父项之后显示,并且在同级(包括顶层父项和同一父项下的子项)之间,需要根据display_priority进行升序排列。

考虑以下原始数据结构,它是一个包含多个对象的数组,每个对象有id、name、reference_id和display_priority字段:

const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

我们期望的输出结果应遵循以下规则:

  1. 如果一个元素的reference_id为null,它是一个顶层父项。
  2. 如果一个元素的reference_id不为null,它是一个子项,其父项的id与reference_id匹配。
  3. 子项必须紧随其父项之后。
  4. 所有顶层父项之间,以及同一父项下的所有子项之间,都应根据display_priority进行升序排列。

根据上述规则,期望的输出如下:

[
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10 }, // 父项
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 }, // 子项,display_priority: 110
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80 }, // 父项
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 }, // 父项
  { id: 6, name: 'hello world', reference_id: 2, display_priority: 30}, // 子项,display_priority: 30
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40}, // 子项,display_priority: 40
]

可以看到,id: 3是第一个父项(display_priority: 10),其子项id: 5(reference_id: 3)紧随其后。接着是id: 4(display_priority: 80)和id: 2(display_priority: 100)这两个顶层父项,它们之间按display_priority排序。最后是id: 2的子项id: 6和id: 1,它们也按display_priority排序。

核心挑战分析

要实现这种复杂的重排序,我们面临以下几个主要挑战:

  1. 父子关系的建立与维护: 需要高效地识别每个元素的父项或子项,并能够快速地找到特定父项的所有子项。
  2. 多维度排序: 既要考虑父子层级,又要考虑display_priority。简单的sort函数无法一次性解决。
  3. 动态插入: 在构建最终数组时,需要将子项动态插入到其父项之后,而不是简单地追加。

原始尝试的reduce方法虽然试图解决父子插入问题,但它存在局限性:它依赖于父项在数组中出现的顺序,如果父项在子项之后,则可能无法正确插入。更重要的是,它没有完全处理display_priority的排序逻辑。

分步解决方案

为了克服上述挑战,我们将采用一个结构化的分步方法:

Java注释规范Skills
Java注释规范Skills

当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。

下载

步骤一:构建数据索引与分类

首先,我们需要对原始数据进行预处理,以便快速查找元素及其子项,并将所有元素分为顶层父项和子项。

  1. 创建父项映射: 使用一个Map来存储所有父项(reference_id为null)的id到其完整对象的映射,以及一个列表来存储顶层父项。
  2. 创建子项映射: 使用另一个Map来存储每个父项id对应的所有子项列表。
// 示例数据
const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

const parents = []; // 存储所有顶层父项
const childrenMap = new Map(); // 存储父项ID到其子项列表的映射

rawData.forEach(item => {
  if (item.reference_id === null) {
    parents.push(item);
  } else {
    if (!childrenMap.has(item.reference_id)) {
      childrenMap.set(item.reference_id, []);
    }
    childrenMap.get(item.reference_id).push(item);
  }
});

步骤二:应用显示优先级排序

在构建最终数组之前,我们需要确保所有顶层父项和每个父项下的子项都已根据display_priority进行排序。

  1. 排序顶层父项: 对parents数组进行display_priority升序排序。
  2. 排序子项列表: 遍历childrenMap中的每个子项列表,并对它们进行display_priority升序排序。
// 排序顶层父项
parents.sort((a, b) => a.display_priority - b.display_priority);

// 排序每个父项下的子项
childrenMap.forEach(childList => {
  childList.sort((a, b) => a.display_priority - b.display_priority);
});

步骤三:构建最终有序数组

现在我们有了排序好的顶层父项和每个父项下排序好的子项列表,可以开始构建最终的有序数组。

遍历排序后的parents数组。对于每个父项,将其添加到结果数组中,然后查找其在childrenMap中对应的子项列表,并将这些子项也依次添加到结果数组中。

const reorderedArray = [];

parents.forEach(parent => {
  reorderedArray.push(parent); // 添加父项
  if (childrenMap.has(parent.id)) {
    const sortedChildren = childrenMap.get(parent.id);
    reorderedArray.push(...sortedChildren); // 添加排序后的子项
  }
});

JavaScript 示例代码

将上述步骤整合到一个函数中,可以得到一个完整的解决方案:

/**
 * 根据父子关系和显示优先级对数组进行重排序。
 * 父项通过 reference_id: null 标识,子项通过 reference_id 关联父项 id。
 * 所有同级元素(包括顶层父项和同一父项下的子项)按 display_priority 升序排列。
 *
 * @param {Array<Object>} data 原始数据数组,每个对象需包含 id, reference_id, display_priority。
 * @returns {Array<Object>} 重排序后的数组。
 */
function reorderArrayByParentAndPriority(data) {
  if (!data || data.length === 0) {
    return [];
  }

  const parents = []; // 存储所有顶层父项
  const childrenMap = new Map(); // 存储父项ID到其子项列表的映射

  // 步骤一:构建数据索引与分类
  data.forEach(item => {
    // 确保 display_priority 存在且为数字,否则默认为一个大值以防排序异常
    const priority = typeof item.display_priority === 'number' ? item.display_priority : Infinity;
    const itemWithPriority = { ...item, display_priority: priority };

    if (item.reference_id === null) {
      parents.push(itemWithPriority);
    } else {
      if (!childrenMap.has(item.reference_id)) {
        childrenMap.set(item.reference_id, []);
      }
      childrenMap.get(item.reference_id).push(itemWithPriority);
    }
  });

  // 步骤二:应用显示优先级排序
  // 排序顶层父项
  parents.sort((a, b) => a.display_priority - b.display_priority);

  // 排序每个父项下的子项
  childrenMap.forEach(childList => {
    childList.sort((a, b) => a.display_priority - b.display_priority);
  });

  // 步骤三:构建最终有序数组
  const reorderedArray = [];
  parents.forEach(parent => {
    reorderedArray.push(parent); // 添加父项
    if (childrenMap.has(parent.id)) {
      const sortedChildren = childrenMap.get(parent.id);
      reorderedArray.push(...sortedChildren); // 添加排序后的子项
    }
  });

  return reorderedArray;
}

// 原始数据
const rawData = [
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10},
  { id: 6, name: 'hello world', reference_id: 2 , display_priority: 30},
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80},
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
];

const result = reorderArrayByParentAndPriority(rawData);
console.log(result);

/*
期望输出:
[
  { id: 3, name: 'hello world', reference_id: null, display_priority: 10 },
  { id: 5, name: 'hello world', reference_id: 3, display_priority: 110 },
  { id: 4, name: 'hello world', reference_id: null, display_priority: 80 },
  { id: 2, name: 'hello world', reference_id: null, display_priority: 100 },
  { id: 6, name: 'hello world', reference_id: 2, display_priority: 30 },
  { id: 1, name: 'hello world', reference_id: 2, display_priority: 40 }
]
*/

代码解析与注意事项

  1. 数据预处理与健壮性: 在forEach循环中,我们添加了对display_priority的类型检查。如果display_priority不存在或不是数字,我们将其视为Infinity。这确保了排序的健壮性,防止因数据不规范而导致程序崩溃,并将这些项推到排序结果的末尾。
  2. Map的优势: 使用Map (childrenMap) 来存储子项,可以实现 O(1) 的平均时间复杂度来查找特定父项的所有子项,这比每次遍历数组查找要高效得多,尤其是在数据量较大时。
  3. 分步排序的清晰性: 将分类、排序和重构三个逻辑步骤分开处理,使得代码逻辑更加清晰,易于理解和维护。
  4. push与spread操作符: reorderedArray.push(parent)用于添加单个父项,而reorderedArray.push(...sortedChildren)则利用展开运算符(spread operator)将一个数组的所有元素一次性添加到另一个数组中,这比循环push更简洁高效。
  5. 性能考虑:
    • 时间复杂度: 初始遍历分类的时间复杂度是 O(N)。排序顶层父项和所有子项列表的总时间复杂度取决于子项的数量和分布,最坏情况下(所有元素都是顶层父项或所有子项都在一个父项下)接近 O(N log N)。最后重构数组的时间复杂度是 O(N)。因此,整体时间复杂度为 O(N log N),对于大多数应用场景来说是高效的。
    • 空间复杂度: parents数组、childrenMap以及最终的reorderedArray都需要额外的空间,空间复杂度为 O(N)。

总结

通过本教程,我们学习了如何处理一个常见的复杂数组重排序问题,即同时考虑父子关系和显示优先级。关键在于将问题分解为几个可管理的步骤:首先进行数据索引和分类,然后对各个层级的数据独立进行优先级排序,最后将这些排序好的数据结构化地组合起来。这种分步处理的方法不仅使代码逻辑清晰,而且在处理大规模数据时也能保持较好的性能。这种策略可以推广到更复杂的树形结构排序场景,只需递归地应用类似的分层排序思想。

热门AI工具

更多
二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

549

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6184

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3144

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

1832

2025.10.17

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

1138

2023.09.04

php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

798

2025.12.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2681

2023.12.01

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

20

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

React 教程
React 教程

共58课时 | 12.3万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn