【算法专题】环状图(置换群)

环状图(置换群)

1 概念

  • 一个有向图如果有n个点,n条边,并且每个点的入度和出度都为1,这样的图被称为环状图。

  • 根据定义可知,环状图一定是由一定数量的环构成的。

  • 环状图一般和置换群的联系十分紧密,因为一个置换群对应一个环状图。

2 例题

AcWing 1224. 交换瓶子

问题描述

在这里插入图片描述

分析

  • 给定我们数组a,我们可以构造一个环状图,对于每个数据a[i],在图中连一条a[i]指向i的边。

  • 对于本题而言,对于样例1:3 1 2 5 4,构造的环状图如下:

在这里插入图片描述

  • 存在两种情况:

    • (1)当我们交换同一个环中的两个点(对应交换数组中的两个数),会让一个环裂开成两个环;

    • (2)当我们交换不同环中的两个点,会让两个环合并成一个环。

  • 针对上述两种情况图示如下:

在这里插入图片描述

  • 我们最终的目标是让整个图中的所有点变为自环。因此只要存在环,我们每次就可以交换环中的两个点,可以让环多一个。

  • 因此本题的做法是:统计环的数量,假设为cnt,如果点数为n,则我们还需要操作n-cnt次得到n个自环。

代码

#include <iostream>

using namespace std;

const int N = 10010;

int n;
int p[N];  // 每个数指向其对应的下标
bool st[N];

int main() {
    
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int id;
        cin >> id;
        p[id] = i;
    }
    
    int cnt = 0;
    for (int i = 1; i <= n; i++)
        if (!st[i]) {
            cnt++;
            for (int j = i; !st[j]; j = p[j])
                st[j] = true;
        }
    
    cout << n - cnt << endl;
    
    return 0;
}

AcWing 1553. 用 Swap(0, i) 操作进行排序

问题描述

在这里插入图片描述

分析

  • 对于每个数id,让其指向其下标。这样我们可以得到一个环状图。

  • 下列演示了不同操作的影响:

在这里插入图片描述

  • 我们最终的目标是将所有的点变为自环。

  • 通过和0交换,可以分为两大类操作:

    (1)0与环内的点交换:① 和0后面一个点next交换,会让next变为自环,有效操作;② 和非0后面的一个点交换位置,会让0所在的环变为两个环,操作无意义;

    (2)0和环外的点交换:合成一个环,有效操作

  • 具体操作时,可以先找到0所在的环,让0和后面的一个点交换,让所有该环内的点变为自环;然后找到其他环,首先和0合并,然后再将合并后的环中的每个点变为自环。

代码

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int p[N];  // 每个数指向其对应的下标

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int id;
        scanf("%d", &id);
        p[id] = i;
    }
    
    int res = 0;
    for (int i = 1; i < n; ) {
        while (p[0]) swap(p[0], p[p[0]]), res++;
        while (i < n && p[i] == i) i++;
        if (i < n) swap(p[0], p[i]), res++;
    }
    
    printf("%d\n", res);
    
    return 0;
}

AcWing 3775. 数组补全

问题描述

在这里插入图片描述

分析

  • 使用数组a记录输入的数组,使用数组p记录每个数对应的下标。

  • 数组a相当于下标指向数据,根据数组a可以得到一个图。我们的目的是让图中没有自环,例如对于测试用例5 0 0 2 4,可以得到下图:

在这里插入图片描述

  • 可以看到这个图是有缺口的,对于这种情况,我们可以将所有没有归属的点放到环中,即接到链尾y的后面。

  • 如果得到的图中都可以构成环,则将其余剩余的点单独构成一个环即可。因为题目保证一定有解,因此如果存在这种情况的话,一定不止一个点。

代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 200010;

int n;
int a[N];  // 下标指向数字
int p[N];  // 数字指向下标
bool st[N];  // st[i]表示: i是否已经在某个环中

int main() {
    
    int T;
    scanf("%d", &T);
    while (T--) {
        memset(p, 0, sizeof p);
        memset(st, 0, sizeof st);
        
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            int id;
            scanf("%d", &id);  
            a[i] = id;  // i -> id  下标指向数字
            p[id] = i;  // id -> i  数字指向下标
        }
        
        bool flag = false;  // 所有缺失的元素是否填补完成
        for (int i = 1; i <= n; i++) {
            
            if (st[i] || !a[i]) continue;  // i已经在某个环中,或者第i个位置没缺失
            st[i] = true;
            
            int x = i, y = i;  // 首x,尾y
            while (p[x] && !st[p[x]]) {
                x = p[x];
                st[x] = true;
            }
            while (a[y] && !st[a[y]]) {
                y = a[y];
                st[y] = true;
            }
            
            if (a[y] == x) continue;  // 说明是一个环
            
            if (!flag) {  // 不构成一个环,其余缺失元素可以填补到y后面补成一个环
                flag = true;
                for (int j = 1; j <= n; j++)
                    if (!a[j] && !p[j]) {
                        st[j] = true;
                        a[y] = j;
                        y = j;
                    }
            }
            a[y] = x;
        }
        
        if (!flag) {  // 之前都是环,不存在右缺口的环,则让剩余的点构成一个环
            int x = 0, y = 0;  // 首x,尾y
            for (int i = 1; i <= n; i++) 
                if (!a[i]) {
                    if (!x && !y) x = y = i;
                    else {
                        a[y] = i;
                        y = i;
                    }
                }
            a[y] = x;
        }
        
        for (int i = 1; i <= n; i++) printf("%d ", a[i]);
        puts("");
    }
    
    return 0;
}

Leetcode 0765 情侣牵手

问题描述

在这里插入图片描述

分析

  • 本题可以转化为图论问题,每对情侣是图中的一个顶点,边是沙发,例如如果存在4对情侣:(1, 1)、(2, 2)、(3, 3)、(4, 4),则如果初始坐在一起的顺序是:(1, 2)、(2, 3)、(3, 4)、(4, 1),目标状态是每个点是一个自环,如下图:

在这里插入图片描述

  • 对于这种类型的图称为环状图(上图中的初始状态),所谓环状图是指由一堆环构成的图。环状图有如下两个性质:

    (1)交换环内的两个点(对应交换两个人),会多一个环;

    (2)交换两个环中的两个点(对应交换两个人),会少一个环。

  • 我们发现,每操作一次最多产生一个环,初始的话,如果有n个点(n为情侣对数),cnt个环,则目标相当于变为n个环,最少需要操作n-cnt次。

  • 本题相当于要求解环的个数cnt,可以使用并查集。

代码

  • C++
class Solution {
public:

    vector<int> p;

    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    int minSwapsCouples(vector<int>& row) {
        int n = row.size() / 2;
        for (int i = 0; i < n; i++) p.push_back(i);

        int cnt = n;
        for (int i = 0; i < n * 2; i += 2) {
            int a = row[i] / 2, b = row[i + 1] / 2;  // 沙发上的两个人row[i]、row[i + 1]分别属于情侣的编号
            if (find(a) != find(b)) {
                p[find(a)] = find(b);
                cnt--;  // 子图减少一个
            }
        }
        return n - cnt;
    }
};
  • Java
class Solution {

    int[] p;

    private int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    public int minSwapsCouples(int[] row) {
        int n = row.length / 2;
        p = new int[n];
        for (int i = 0; i < n; i++) p[i] = i;

        int cnt = n;
        for (int i = 0; i < n * 2; i += 2) {
            int a = row[i] / 2, b = row[i + 1] / 2;
            if (find(a) != find(b)) {
                p[find(a)] = find(b);
                cnt--;  // 子图减少一个
            }
        }
        return n - cnt;
    }
}
  • Python
class Solution:
    def minSwapsCouples(self, row: List[int]) -> int:
        n = len(row) // 2
        p = [i for i in range(n)]
        cnt = n
        for i in range(0, n * 2, 2):
            a = self.find(p, row[i] // 2); b = self.find(p, row[i + 1] // 2)
            if a != b:
                p[a] = b
                cnt -= 1
        return n -cnt
    
    def find(self, p, x):
        if p[x] != x:
            p[x] = self.find(p, p[x])
        return p[x]

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( n ) O(n) O(n)

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最佳置换算法是一种页面置换算法,其思想是查找内存中将来最长时间不使用的页面,然后将当前页面替换成该页面。以下是最佳置换算法的编程实现。 首先,我们需要定义一个列表来存储当前内存中的页面和它们的访问时间。每当页面被访问时,我们需要更新它的访问时间。 ``` # 定义列表来存储页面和它们的访问时间 memory = [] # 初始化内存 def initialize_memory(size): global memory memory = [[None, float("inf")] for _ in range(size)] # 更新页面的访问时间 def update_access_time(page): global memory for i in range(len(memory)): if memory[i][0] == page: memory[i][1] = float("inf") else: memory[i][1] -= 1 # 查找最长时间不使用的页面 def find_replace_page(): global memory replace_page = None for page, access_time in memory: if access_time < float("inf"): if replace_page is None or access_time > memory[replace_page][1]: replace_page = memory.index([page, access_time]) return replace_page ``` 然后,我们需要实现最佳置换算法的主要逻辑。当内存已满且新页面需要插入时,我们需要查找最长时间不使用的页面并将其替换成新页面。 ``` # 最佳置换算法 def best_fit(page): global memory if [page, float("inf")] not in memory: page_index = None for i in range(len(memory)): if memory[i][0] is None: memory[i] = [page, float("inf")] return elif memory[i][0] == page: update_access_time(page) return elif page_index is None or memory[i][1] > memory[page_index][1]: page_index = i memory[page_index] = [page, float("inf")] else: update_access_time(page) # 页面替换 def page_replace(algo, pages, size): global memory initialize_memory(size) page_faults = 0 for page in pages: if page not in [p[0] for p in memory]: page_faults += 1 if None in [p[0] for p in memory]: memory[[p[0] for p in memory].index(None)] = [page, float("inf")] else: replace_page_index = find_replace_page() memory[replace_page_index] = [page, float("inf")] else: update_access_time(page) return page_faults ``` 最后,我们可以调用 `page_replace` 函数来测试最佳置换算法。 ``` pages = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2] print("Page faults:", page_replace("best_fit", pages, 4)) ``` 输出结果为: ``` Page faults: 7 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值