Leetcode周赛274记录-基环内向树

这篇博客探讨了一个基于有向图的问题,其中每个员工节点代表一个人,喜欢关系通过有向边表示。弱连通分量在边变为无向后形成的连通组件被分析,每个组件可能包含一个环。文章详细阐述了如何确定每个弱连通分量中最大可组成的‘桌’数,特别是环长为2和大于2的情况。对于环长大于2的分量,环上的节点可以围成一桌,而环长为2的分量,可以通过寻找追随者的最大深度来增加人数。最后,提供了一种拓扑排序和深度优先搜索的解决方案来求解问题。

题干信息

在这里插入图片描述
在这里插入图片描述

图抽象-基环内向树

  • 每个员工视作一个结点,如果员工A喜欢员工B,则从A到B加上一条有向边。从而构成一个有向图。
  • 从而这个图有许多的弱连通分量。弱连通分量是指将有向边变为无向边后图的连通分量。
  • 对每个弱连通分量而言,假设其顶点数目为kkk,由于每个点仅能发出去一条边,因此边的数量也是kkk。假设不存在环,则由树的充要条件可知边的数目为k−1k-1k1。假设存在大于等于2个的环,若两个环无公共顶点,则这不是一个弱连通分量。若两个环有公共顶点,则对应原始有向图中这个公共顶点的出度为2,矛盾。因此每个弱连通分量有且仅有一个环。
  • 综上所述,每个弱连通分量可构成如下结构:
    在这里插入图片描述

在这里插入图片描述

求解思路

  • 对于每个弱连通分量,其具有唯一的环要么顶点数为2,要么顶点数目大于等于2

环结点数目大于2的弱连通分量

  • 对于环顶点数大于2的弱连通分量,如图所示: 在这里插入图片描述

  • 在此类型的弱连通分量中环上结点可以围成一桌

  • 环上结点间不能插入其他任何非环上结点,否则不满足条件

  • 非环结点不能自己组成一桌。可以考虑反证法,假设它们能组成一桌,那么一定会形成一个环,矛盾。

  • 而此弱连通分量上的所有结点与其他弱连通分量的任何结点都不能组成更大的一桌。只有这里环上的结点能组成一桌,且环上不能再添加新的结点,否则破坏了喜欢关系。

环结点数目等于2的弱连通分量

  • 对环顶点数目等于2的弱连通分量,如图所示:
  • 在这里插入图片描述
  • 首先,不在环上的结点肯定不能单独构成一桌,也不能和其他弱连通分量中的结点构成一桌
  • 考虑将环上的两个结点先安排在一桌上(图中的0,1结点),且一定要被安排在一起的。由于0喜欢1,1喜欢0,所以0的一侧还可以安排其追随者,这个追随者还可以再安排追随者的追随者,以此类推。1的一侧同理。最后将两个末尾追随者拼接在一起,依旧满足题意,且这对应是该弱连通分量所能安排的最大值
  • 除此之外,对于其他环长为2的弱连通分量,都可以将上述末尾追随者首位相连接,得到更多的人数。

求解思路

  • 设图中弱连通分量集合为AAA,每个弱连通分量用Gc∈AG_c \in AGcA进行表示,其所能围成最大的桌人数为g(Gc)g(G_c)g(Gc),所对应的环长为f(Gc)f(G_c)f(Gc),则最终结果为:max{maxf(Gc)>2∧Gc∈A{g(Gc)},∑f(Gc)=2∧Gc∈Ag(Gc)}max\{ max_{f(G_c)>2 \land G_c \in A } \{g(G_c)\} ,\sum_{f(G_c) = 2 \land G_c \in A} g(G_c)\}max{maxf(Gc)>2GcA{g(Gc)},f(Gc)=2GcAg(Gc)}

    • f(Gc)>2f(G_c) > 2f(Gc)>2时有f(Gc)=g(Gc)f(G_c) = g(G_c)f(Gc)=g(Gc)
    • f(Gc)=2f(G_c) = 2f(Gc)=2g(Gc)g(G_c)g(Gc)等于2加上两个成环结点追随者的最大长度。
  • f(Gc)f(G_c)f(Gc):先对图执行一次拓扑排序,剩下的所有结点入度均为1。从任意入度为1的结点开始遍历出当前环长度。

  • f(Gc)=2f(G_c)=2f(Gc)=2g(Gc)g(G_c)g(Gc),从环上两个结点开始分别在反向图中寻找追随者的最大深度。

代码实现

class Solution {
    public int dfs(int start,int[] indegree,Map<Integer,List<Integer>> a)
    {
        int ans = 1;
        if(a.get(start) == null)
            return 1;
        for(int b : a.get(start))
        {
            if(indegree[b] == 0)  //不在环上的结点入度为0
                ans = Math.max(ans , dfs(b,indegree,a) + 1);
        }
        return ans;
    }

    public int maximumInvitations(int[] favorite) {
        int[] indegree = new int[favorite.length];
        Map<Integer,List<Integer>> a = new HashMap<>();  //反向图
        for(int i = 0;i<favorite.length;i++)
        {
            //建立反向图
            List<Integer> temp = a.getOrDefault(favorite[i],new ArrayList<>());
            temp.add(i);
            a.put(favorite[i],temp);
            //计算顶点入度
            indegree[favorite[i]]++;
        }
        //topological sort
        Queue<Integer> q = new LinkedList<>();
        //所有入度为0的顶点进入队列
        for(int i = 0;i<favorite.length;i++)
            if(indegree[i] == 0)
                q.add(i);
        while(!q.isEmpty())
        {
            int front = q.poll();
            indegree[favorite[front]]--;
            if(indegree[favorite[front]] == 0)
                q.add(favorite[front]);
        }
        int ans=  0;
        int total = 0;
        //选择入度大于0的顶点找环
        boolean[] visit = new boolean[favorite.length];
        for(int i = 0;i<indegree.length;i++)
        {
            if(indegree[i] > 0 && !visit[i])
            {
                int circle = 1;
                visit[i] = true;
                int j = i;
                while(favorite[j] != i)
                {
                    j = favorite[j];
                    visit[j] = true;
                    circle++;
                }
                if(circle > 2)
                    ans = Math.max(ans,circle);
                else
                    total += (dfs(i,indegree,a) + dfs(j,indegree,a));
            }
        }
        return Math.max(ans,total);
    }

    public static void main(String[] args)
    {
        new Solution().maximumInvitations(new int[]{2,2,1,2});
    }
}
#include <stdio.h> #include <stdlib.h> // 边节点(存储有向边:从u指向v) typedef struct ArcNode { int adjvex; // 目标顶点的索引(对应顶点数组下标) struct ArcNode* nextarc; // 指向下一条出边的指针(修正:添加指针符号) } ArcNode; // 顶点结构体(含入度信息) typedef struct VNode { int data; // 顶点数据(课程编号等) int inDegree; // 顶点入度 ArcNode* firstarc; // 指向第一条出边的指针(修正:添加指针符号) } VNode, AdjList[100]; // 顶点数组(最大100个顶点) // 有向图结构体 typedef struct { AdjList vertices; // 顶点数组 int vexnum; // 顶点数 int arcnum; // 边数 } Digraph; // 辅助函数:根据顶点编号查找对应的数组下标 int LocateVex(Digraph G, int x) { for (int i = 0; i < G.vexnum; i++) { if (G.vertices[i].data == x) { return i; // 找到返回下标 } } return -1; // 未找到返回-1 } // 创建有向图(邻接表存储) void CreateDigraph(Digraph* G) { printf("请输入顶点数n和边数e:"); scanf("%d%d", &G->vexnum, &G->arcnum); // 初始化顶点 printf("请输入%d个顶点的编号:", G->vexnum); for (int i = 0; i < G.vexnum; i++) { scanf("%d", &G->vertices[i].data); G->vertices[i].inDegree = 0; // 入度初始化为0 G->vertices[i].firstarc = NULL; // 出边链表初始化为空 } // 初始化边(构建邻接表+统计入度) for (int k = 0; k < G.arcnum; k++) { int u, v; printf("请输入第%d条边(前驱顶点 后继顶点):", k+1); scanf("%d%d", &u, &v); // 查找u和v对应的数组下标 int u_idx = LocateVex(*G, u); int v_idx = LocateVex(*G, v); // 创建新边节点(u→v) ArcNode* newArc = (ArcNode*)malloc(sizeof(ArcNode)); newArc->adjvex = v_idx; newArc->nextarc = G->vertices[u_idx].firstarc; // 头插法插入邻接表 G->vertices[u_idx].firstarc = newArc; G->vertices[v_idx].inDegree++; // v的入度+1(体现依赖关系) } } // 统计并输出各顶点的入度和出度 void PrintVexInfo(Digraph G) { printf("\n各顶点信息:\n"); printf("顶点编号\t入度\t出度\n"); for (int i = 0; i < G.vexnum; i++) { // 统计出度(遍历邻接表边节点数量) int outDegree = 0; ArcNode* p = G.vertices[i].firstarc; while (p != NULL) { outDegree++; p = p->nextarc; } // 输出信息 printf("%d\t\t%d\t%d\n", G.vertices[i].data, G.vertices[i].inDegree, outDegree); } } // 拓扑排序(于队列实现,topo数组存储结果,返回1成功/0失败) int TopologicalSort(Digraph G, int topo[]) { int queue[100], front = 0, rear = 0; // 数组模拟队列(存储顶点下标) int count = 0; // 记录拓扑序列长度 // 1. 初始化队列:入度为0的顶点入队 for (int i = 0; i < G.vexnum; i++) { if (G.vertices[i].inDegree == 0) { queue[rear++] = i; // 入队 } } // 2. 处理队列中的顶点 while (front < rear) { int u_idx = queue[front++]; // 出队顶点u的下标 topo[count++] = G.vertices[u_idx].data; // 加入拓扑序列 // 遍历u的所有出边u→v ArcNode* p = G.vertices[u_idx].firstarc; while (p != NULL) { int v_idx = p->adjvex; // v的下标 G.vertices[v_idx].inDegree--; // 解除u对v的依赖,入度-1 if (G.vertices[v_idx].inDegree == 0) { queue[rear++] = v_idx; // v入度为0,入队 } p = p->nextarc; // 遍历下一条出边 } } // 3. 判断是否存在(序列长度==顶点数则无) return count == G.vexnum ? 1 : 0; } // 销毁有向图(释放边节点内存) void DestroyDigraph(Digraph* G) { for (int i = 0; i < G->vexnum; i++) { ArcNode* p = G->vertices[i].firstarc; while (p != NULL) { ArcNode* temp = p; p = p->nextarc; free(temp); // 释放边节点 } G->vertices[i].firstarc = NULL; } G->vexnum = 0; G->arcnum = 0; } int main() { Digraph G; int topo[100]; // 存储拓扑序列 // 1. 创建有向图 CreateDigraph(&G); // 2. 输出顶点入度和出度 PrintVexInfo(G); // 3. 拓扑排序并输出结果 printf("\n拓扑排序结果:"); if (TopologicalSort(G, topo)) { for (int i = 0; i < G.vexnum; i++) { printf("%d ", topo[i]); } printf("\n"); } else { printf("图中存在,无法进行拓扑排序\n"); } // 4. 释放内存 DestroyDigraph(&G); return 0; }
最新发布
11-07
### 有向图创建 在C语言中创建有向图,通常可以使用邻接表来表示。邻接表是一种常用的图的存储结构,它由顶点表和边表组成。每个顶点对应一个链表,链表中存储了该顶点的所有出边。 ```c #include <stdio.h> #include <stdlib.h> // 定义边表节点 typedef struct EdgeNode { int adjvex; // 邻接顶点的下标 struct EdgeNode *next; // 指向下一个邻接顶点的指针 } EdgeNode; // 定义顶点表节点 typedef struct VertexNode { int data; // 顶点的数据 int in; // 顶点的入度 int out; // 顶点的出度 EdgeNode *firstedge; // 指向第一条依附该顶点的边的指针 } VertexNode, AdjList[100]; // 定义图的结构体 typedef struct { AdjList adjList; int numVertexes, numEdges; // 图的顶点数和边数 } GraphAdjList; // 创建有向图 void CreatGraph(GraphAdjList *G) { int i, j, k; EdgeNode *e; printf("请输入顶点数和边数:"); scanf("%d %d", &G->numVertexes, &G->numEdges); for (i = 0; i < G->numVertexes; i++) { G->adjList[i].data = i; G->adjList[i].in = 0; G->adjList[i].out = 0; G->adjList[i].firstedge = NULL; } for (k = 0; k < G->numEdges; k++) { printf("请输入边(vi, vj)的下标i, j:"); scanf("%d %d", &i, &j); e = (EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex = j; e->next = G->adjList[i].firstedge; G->adjList[i].firstedge = e; G->adjList[i].out++; G->adjList[j].in++; } } ``` ### 顶点入度和出度统计 在创建有向图的过程中,已经对每个顶点的入度和出度进行了统计。可以通过遍历邻接表来输出每个顶点的入度和出度。 ```c // 统计并输出每个顶点的入度和出度 void PrintInOut(GraphAdjList G) { int i; for (i = 0; i < G.numVertexes; i++) { printf("顶点 %d 的入度为:%d,出度为:%d\n", G.adjList[i].data, G.adjList[i].in, G.adjList[i].out); } } ``` ### 拓扑排序 拓扑排序是对有向无图的顶点进行排序的一种算法。可以使用队列来实现拓扑排序。 ```c #include <queue> // 拓扑排序 int TopologicalSort(GraphAdjList G) { std::queue<int> q; int i, j, k; int count = 0; EdgeNode *e; for (i = 0; i < G.numVertexes; i++) { if (G.adjList[i].in == 0) { q.push(i); } } while (!q.empty()) { j = q.front(); q.pop(); printf("%d ", G.adjList[j].data); count++; e = G.adjList[j].firstedge; while (e) { k = e->adjvex; if (!(--G.adjList[k].in)) { q.push(k); } e = e->next; } } if (count < G.numVertexes) { return 0; // 存在 } else { return 1; // 拓扑排序成功 } } ``` ### 图销毁 在程序结束时,需要释放图所占用的内存,避免内存泄漏。 ```c // 销毁图 void DestroyGraph(GraphAdjList *G) { int i; EdgeNode *e, *p; for (i = 0; i < G->numVertexes; i++) { e = G->adjList[i].firstedge; while (e) { p = e; e = e->next; free(p); } } } ``` ### 主函数 ```c int main() { GraphAdjList G; CreatGraph(&G); PrintInOut(G); printf("拓扑排序结果:"); if (TopologicalSort(G)) { printf("\n拓扑排序成功!\n"); } else { printf("\n图中存在,无法进行拓扑排序!\n"); } DestroyGraph(&G); return 0; } ``` ### 代码解释 1. **有向图创建**:通过`CreatGraph`函数创建有向图,使用邻接表存储图的信息,并统计每个顶点的入度和出度。 2. **顶点入度和出度统计**:通过`PrintInOut`函数遍历邻接表,输出每个顶点的入度和出度。 3. **拓扑排序**:使用队列实现拓扑排序,通过`TopologicalSort`函数进行拓扑排序,并判断图中是否存在。 4. **图销毁**:通过`DestroyGraph`函数释放图所占用的内存,避免内存泄漏。 ### 复杂度分析 - **时间复杂度**:创建图的时间复杂度为$O(V + E)$,其中$V$是顶点数,$E$是边数。拓扑排序的时间复杂度也为$O(V + E)$。 - **空间复杂度**:主要用于存储邻接表,空间复杂度为$O(V + E)$。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值