【拓扑排序】【 图论】1203. 项目管理

本文涉及知识点

拓扑排序 图论

LeetCode1203. 项目管理

有 n 个项目,每个项目或者不属于任何小组,或者属于 m 个小组之一。group[i] 表示第 i 个项目所属的小组,如果第 i 个项目不属于任何小组,则 group[i] 等于 -1。项目和小组都是从零开始编号的。可能存在小组不负责任何项目,即没有任何项目属于这个小组。
请你帮忙按要求安排这些项目的进度,并返回排序后的项目列表:
同一小组的项目,排序后在列表中彼此相邻。
项目之间存在一定的依赖关系,我们用一个列表 beforeItems 来表示,其中 beforeItems[i] 表示在进行第 i 个项目前(位于第 i 个项目左侧)应该完成的所有项目。
如果存在多个解决方案,只需要返回其中任意一个即可。如果没有合适的解决方案,就请返回一个 空列表 。
示例 1:
输入:n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3,6],[],[],[]]
在这里插入图片描述

输出:[6,3,4,1,5,2,0,7]
示例 2:

输入:n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3],[],[4],[]]
输出:[]
解释:与示例 1 大致相同,但是在排序后的列表中,4 必须放在 6 的前面。

提示:

1 <= m <= n <= 3 * 104
group.length == beforeItems.length == n
-1 <= group[i] <= m - 1
0 <= beforeItems[i].length <= n - 1
0 <= beforeItems[i][j] <= n - 1
i != beforeItems[i][j]
beforeItems[i] 不含重复元素

拓扑排序

同一小组的项目,排序后在列表中彼此相邻。 → \rightarrow A小组的某项目依赖B小组的某项目,则B小组全部完成后,才能处理A小组的项目。
最容易想到的办法,先对项目组拓扑排序,再对组内项目拓扑排序。这样内存超了。时间也很可能超。错误代码如下:

class CTopSort
{
public:
void Init(const vector<vector>& vNeiBo)
{
m_c = vNeiBo.size();
m_vBackNeiBo.resize(m_c);
vector vOutDeg(m_c);
for (int cur = 0; cur < m_c; cur++)
{
vOutDeg[cur] = vNeiBo[cur].size();
for (const auto& next : vNeiBo[cur])
{
m_vBackNeiBo[next].emplace_back(cur);
}
}
queue que;
for (int i = 0; i < m_c; i++)
{
if (0 == vOutDeg[i])
{
que.emplace(i);
m_vLeaf.emplace_back(i);
OnDo(-1, i);
}
}
while (que.size())
{
const int cur = que.front();
que.pop();
for (const auto& next : m_vBackNeiBo[cur])
{
vOutDeg[next]–;
if (0 == vOutDeg[next])
{
que.emplace(next);
OnDo(cur, next);
}
}
};
}
int m_c;
vector m_vLeaf;
protected:
virtual void OnDo(int pre, int cur) = 0;
vector<vector> m_vBackNeiBo;
};

class CMyTopSort : public CTopSort
{
public:
vector m_vTopSort;
protected:
virtual void OnDo(int pre, int cur) override
{
m_vTopSort.emplace_back(cur);
}
};
class Solution {
public:
vector sortItems(int n, int m, vector& group, vector<vector>& beforeItems) {
int iMaxGroup = *std::max_element(group.begin(), group.end());
for (auto& n : group)
{
if (-1 == n)
{
n = ++iMaxGroup;
}
}
vector<vector> vNeiBo(1 + iMaxGroup);
vector<vector<vector>> vItemNeiBo(1 + iMaxGroup, vector<vector>(n));
for (int i = 0; i < beforeItems.size(); i++)
{
for (const auto& bef : beforeItems[i])
{
if (group[i] != group[bef]) {
vNeiBo[group[i]].emplace_back(group[bef]);
}
else {
vItemNeiBo[group[i]][i].emplace_back(bef);
}
}
}
for (auto& v : vNeiBo)
{
unordered_set tmp(v.begin(), v.end());
vector(tmp.begin(), tmp.end()).swap(v);
}
CMyTopSort top;
top.Init(vNeiBo);
vector<vector> vNodeOfGroup(iMaxGroup + 1);
for (int i = 0; i < group.size(); i++)
{
vNodeOfGroup[group[i]].emplace_back(i);
}
vector vRet;
for (const auto& iGroup : top.m_vTopSort)
{
CMyTopSort topItem;
topItem.Init(vItemNeiBo[iGroup]);
for (const auto& n : topItem.m_vTopSort)
{
if (group[n] == iGroup)
{
vRet.emplace_back(n);
}
}
}
return (vRet.size() == group.size()) ? vRet : vector{};
}
};

改进方法

一,修改拓扑排序封装类,使得支持哈希映射,这样可以处理非连续节点。
二,修改拓扑封装类,项目和项目组出度同时为0,才进行拓扑排序。
有一个简单的方法,不用修改拓扑类。
每个项目组增加两个虚拟节点,组前节点,组内所有项目都依赖它;组后节点,它依赖所有组内项目。
A组依赖B组,则A组的组前节点A1,依赖B组的组后节点B2。
下图展示了A组包括两个节点{0,1},B组也包括两个节点{2,3}。
在这里插入图片描述
2,3 处理完才会处理B2,B2处理完,才会处理A1。A1处理完才会处理0和1。

细节很多

组号为-1的项目要重复分配新的组号,避免把-1的项目看成同一项目组。
邻接点不能有重复节点。
这样并不能保证同一个项目组挨在一起。
m_vNodeOfGroup 记录各项目组的拓扑序。m_vGroupNo记录拓扑序各节点的项目组号。按m_vGroupNo的顺序将结果复制到vRet。m_vGroupNo可以用哈希集合去重,也可以复制完就删除。

代码

class CTopSort
{
public:	void Init(const vector<vector<int>>& vNeiBo){m_c = vNeiBo.size();m_vBackNeiBo.resize(m_c);vector<int> vOutDeg(m_c);for (int cur = 0; cur < m_c; cur++){vOutDeg[cur] = vNeiBo[cur].size();	for (const auto& next : vNeiBo[cur]){m_vBackNeiBo[next].emplace_back(cur);}}queue<int> que;for (int i = 0; i < m_c; i++){if (0 == vOutDeg[i]){que.emplace(i);m_vLeaf.emplace_back(i);OnDo(-1, i);}}while (que.size()){const int cur = que.front();que.pop();for (const auto& next : m_vBackNeiBo[cur]){vOutDeg[next]--;if (0 == vOutDeg[next]){que.emplace(next);OnDo(cur, next);}}};}int m_c;vector<int> m_vLeaf;
protected:virtual void OnDo(int pre, int cur) = 0;vector<vector<int>> m_vBackNeiBo;
};class CMyTopSort : public CTopSort
{
public:CMyTopSort(const vector<int>& vNodeToGroup,int iGroupCount):m_vNodeToGroup(vNodeToGroup){m_vNodeOfGroup.resize(iGroupCount);}vector<vector<int>> m_vNodeOfGroup;vector<int> m_vGroupNo;
protected:virtual void OnDo(int pre, int cur) override{if (cur < m_vNodeToGroup.size()){const int iGroup = m_vNodeToGroup[cur];m_vNodeOfGroup[iGroup].emplace_back(cur);m_vGroupNo.emplace_back(iGroup);}		}const vector<int>& m_vNodeToGroup;
};
class Solution {
public:vector<int> sortItems(int n, int m, vector<int>& group, vector<vector<int>>& beforeItems) {int iMaxGroup = *std::max_element(group.begin(), group.end());for (auto& tmp : group){if (-1 == tmp){tmp = ++iMaxGroup;}}vector<vector<int>> vNodeOfGroup(iMaxGroup + 1);for (int i = 0; i < group.size(); i++){vNodeOfGroup[group[i]].emplace_back(i);}vector<vector<int>> vNeiBo((1 + iMaxGroup)*2+n );for (int i = 0; i < vNodeOfGroup.size(); i++){for (const auto& tmp : vNodeOfGroup[i]){vNeiBo[tmp].emplace_back(n + 2 * i);//组内所有节点都依赖组前节点vNeiBo[n + 2 * i + 1].emplace_back(tmp);//组后节点依赖所有组内节点}}	for (int i = 0; i < beforeItems.size(); i++){for (const auto& bef : beforeItems[i]){if (group[i] == group[bef]) {vNeiBo[i].emplace_back(bef);}	else {vNeiBo[n + 2 * group[i]].emplace_back(n + 2 * group[bef] + 1);}}}for (auto& v : vNeiBo){//删除重复节点unordered_set<int> tmp(v.begin(), v.end());vector<int>(tmp.begin(), tmp.end()).swap(v);}		CMyTopSort top(group,iMaxGroup+1);top.Init(vNeiBo);		vector<int> vRet;for (const auto& tmp : top.m_vGroupNo){auto& v = top.m_vNodeOfGroup[tmp];vRet.insert(vRet.end(), v.begin(), v.end());v.clear();}return (vRet.size() == group.size()) ? vRet : vector<int>{};}
};

测试用例

template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{int n,  m;vector<int> group;vector<vector<int>> beforeItems;{Solution sln;n = 8, m = 2, group = { -1,-1,1,0,0,1,0,-1 }, beforeItems = { {},{6},{5},{6},{3},{},{4},{} };auto res = sln.sortItems(n, m, group, beforeItems);Assert({ }, res);}{Solution sln;n = 8, m = 2, group = { -1,-1,1,0,0,1,0,-1 }, beforeItems = { {},{6},{5},{6},{3,6},{},{},{} };auto res = sln.sortItems(n, m, group, beforeItems);//	Assert({ 6,3,4,1,5,2,0,7 }, res);}	
}

2023年4月

class Solution {
public:
vector sortItems(int n, int m, vector& group, vector<vector>& beforeItems) {
//没有分组的分配一个新组号
m_vGroupItems.resize(m );
for (int j = 0; j < n;j++ )
{
auto& g = group[j];
if (-1 == g)
{
g = m_vGroupItems.size();
m_vGroupItems.emplace_back();
}
m_vGroupItems[g].emplace_back(j);
}
const int GroupNum = m_vGroupItems.size();
vector<set> vGroupBefore(GroupNum), vGroupAfter(GroupNum);
for (int j = 0; j < n; j++)
{
const auto& v = beforeItems[j];
for (const auto& before : v)
{
const int iGroup = group[j];
const int iBeforeGroup = group[before];
if (iGroup == iBeforeGroup)
{
continue;
}
vGroupBefore[iGroup].emplace(iBeforeGroup);
vGroupAfter[iBeforeGroup].emplace(iGroup);
}
}
auto vGroups = Order(vGroupBefore, vGroupAfter);
if (vGroups.size() != m_vGroupItems.size())
{
return vector();
}
vector vRet;
for (const auto& iCurGroup : vGroups)
{
const auto& vItemsCurGroup = m_vGroupItems[iCurGroup];
const int iCurGroupItemSize = vItemsCurGroup.size();
std::unordered_map<int, int> mIndexToGroupIndex;
for (const int& item : vItemsCurGroup)
{
mIndexToGroupIndex[item] = mIndexToGroupIndex.size();
}
vector<set> vItemBefore(iCurGroupItemSize), vItemAfter(iCurGroupItemSize);
for (const int& item : vItemsCurGroup)
{
const int iCurItemIndex = mIndexToGroupIndex[item];
for (const int& before : beforeItems[item])
{
if (group[before] != iCurGroup)
{
continue;
}
const int iBeforeIndex = mIndexToGroupIndex[before];
vItemBefore[iCurItemIndex].emplace(iBeforeIndex);
vItemAfter[iBeforeIndex].emplace(iCurItemIndex);
}
}
vector vCurGroupItemOrder = Order(vItemBefore,vItemAfter);
if (iCurGroupItemSize != vCurGroupItemOrder.size())
{
return vector();
}
for (const int iCurGroupItem : vCurGroupItemOrder)
{
vRet.emplace_back(vItemsCurGroup[iCurGroupItem]);
}
//std::copy(vGroupItems[g].begin(), vGroupItems[g].end(), std::back_inserter(vRet));
}

	return vRet;
}
vector<int> Order(vector<set<int>>& vBefore, vector<set<int>>& vAfter)
{	vector<int> vOrders;for (int j = 0; j < vBefore.size(); j++){if (vBefore[j].size() == 0){vOrders.emplace_back(j);}}int hasDo = 0;while (hasDo < vOrders.size()){const auto cur = vOrders[hasDo];hasDo++;for (auto& after : vAfter[cur]){vBefore[after].erase(cur);if (vBefore[after].empty()){vOrders.emplace_back(after);}}vAfter[cur].clear();}return vOrders;
}
vector<vector<int>> m_vGroupItems;

};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/588274.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用ChatGPT出题,完全做不完

最近小朋友正在学习加减法&#xff0c;正好利用ChatGPT来生成加减法练习题&#xff0c;小朋友表示够了&#xff0c;够了&#xff0c;完全做不完。本文将给大家介绍如何利用ChatGPT来生成练习题。 尚未获得ChatGPT的用户&#xff0c;请移步&#xff1a;五分钟开通GPT4.0。 角色…

OpenKylin安装Kafka

一、操作系统 openKylin 1.0.1 X86 二、下载安装包 # 安装依赖jdk sudo apt-get update sudo apt-get install default-jdk # 下载kafka mkdir -p /data/software/kafka wget https://archive.apache.org/dist/kafka/2.4.1/kafka_2.13-2.4.1.tgz三、解压安装 # 解压缩Kafka…

【DPU微知识】NVIDIA-BlueFiled DPU概念之:BFB是什么?

BFB是BlueField Boot Stream的缩写&#xff0c;由Bootloader、Linux OS、Romfs组成。本质&#xff1a;bootload、系统、文件系统。&#xff08;其实就是DPU的上装类比标准host的grub、linux、文件系统&#xff0c;类似做Linux移植时候构建的最小文件系统的三件套差不多&#xf…

在idea中使用sql语言提醒

1.Settings中设置 2. 配置好数据库名字 3. altenter 注入方言 注入后是下面这样

Android多边形区域递归种子填充算法的示例代码

平面区域填充算法是计算机图形学领域的一个很重要的算法&#xff0c;区域填充即给出一个区域的边界&#xff08;也可以是没有边界&#xff0c;只是给出指定颜色&#xff09;&#xff0c;要求将边界范围内的所有象素单元都修改成指定的颜色&#xff08;也可能是图案填充&#xf…

苹果手表Apple Watch录了两个半小时的录音,却只能播放4秒,同步到手机也一样,还能修复好吗?

好多人遇到这个情况&#xff0c;用苹果手表Apple Watch录音&#xff0c;有的录1个多小时&#xff0c;有的录了3、4小时&#xff0c;甚至更长时间&#xff0c;因为手表没电&#xff0c;忘记保存等原因造成录音损坏&#xff0c;都是只能播放4秒&#xff0c;同步到手机也一样&…

Linux基础篇:VMware centos7虚拟机网络配置——桥接模式

VMware centos7虚拟机网络配置——桥接模式 1 搞清楚什么是桥接模式 桥接模式允许虚拟机直接连接到物理网络&#xff0c;就像它是物理网络中的一个独立设备一样。在这种模式下&#xff0c;虚拟机将具有与宿主机相同网络中的其他设备相同的网络访问权限。虚拟机将获得一个独立…

【C++庖丁解牛】高阶数据结构---红黑树详解(万字超详细全面介绍红黑树)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1.红黑树的概念2.红黑…

vue2 列表一般不使用索引删除的原因

在 Vue 中使用索引来删除列表项可能会导致一系列问题&#xff0c;尤其是在处理动态列表时。以下是一些可能的问题和相应的例子&#xff1a; 1. 数据不一致问题 当你使用索引来删除列表中的某个项时&#xff0c;如果列表中的其他项发生了变化&#xff08;比如新增或重新排序&a…

springdoc-openapi-用户界面如何将请求设置为HTTPS

一、问题描述 当我们的服务接口需要通过HTTPS访问时&#xff0c;通过swagger可视化页面请求接口的时候&#xff0c;发起的是HTTP请求&#xff0c;导致请求无法到达后端&#xff0c;影响测试。 二、解决方法 1、将服务的地址添加到配置文件中 swagger:server-list: #本地环境…

练习 13 Web [极客大挑战 2019]Secret File

php伪协议请求&#xff0c;php代码审计 参考&#xff1a;BUUCTF__[极客大挑战 2019]Secret File_题解 没有任何上传和登录页面 查看前端源码 发现 <a id"master" href"./Archive_room.php" style"background-color:#000000;height:70px;width:20…

OurBMC技术委员会2024年一季度例会顺利召开

3 月 28 日&#xff0c;OurBMC 社区技术委员会一季度例会顺利召开。本次会议汇报了 OurBMC 社区一季度工作总结&#xff0c;规划了二季度重点工作&#xff0c;同时针对产业化落地 SIG 实施方案开展了深入讨论。 本次会议采取线上线下形式举行&#xff0c;由 技术委员会主席李煜…