算法课设 - 众数问题,最小权顶点覆盖问题,C++

众数问题

算法分析

题意描述

输入一个大小为n的有序数组,求该数组中出现次数最多的数,及其出现的次数,注:当有多个数出现次数最多时,只输出一个。

算法过程描述

假设我们先求出中位数的重数,再求出中位数往左往右延伸的距离,假设当前数组左右边界为l,r,中位数位置为mid,向左向右延伸为nl,nr。这样就把原数组分为三个子数组:[l,nl-1],[nl,nr],[nr+1,nr],用第二个子数组的长度更新答案,对第一个和第三个子数组继续重复刚才的动作,为了优化搜索,当子数组大小小于当前最优解时,进行剪枝

递归方程及时间复杂度分析

分治算法解决众数问题,由代码可得递归方程为:
T(n) = T(n-x) + x
其中x代表当前搜索过程中位数的重数大小,范围为1~n,计算可得代码总的时间复杂度为O(n)

流程图

算法关键代码

#include "bits/stdc++.h"
using namespace std;
//众数,分治,假设数组有序
int mx,ans;
vector<int> a;
void dfs(int l,int r)
{
    int mid = (l+r)>>1;
    int ll=mid,rr=mid;
    while(ll-1>=l&&a[ll-1]==a[mid]) --ll;
    while(rr+1<=r&&a[rr+1]==a[mid]) ++rr;
    if(rr-ll+1>mx) mx=rr-ll+1,ans=a[mid];
    if(ll-1-l+1>mx) dfs(l,ll-1);
    if(r-rr-1+1>mx) dfs(rr+1,r);
}
signed main()
{
    freopen("input.txt", "r",stdin);
    freopen("output.txt","w",stdout);
    int n;cin>>n;
    a.resize(n);
    for(int i=0;i<n;++i) cin>>a[i];
    mx = 1,ans = a[0];
    dfs(0,n-1);
    cout<<"众数"<<ans<<" "<<"重数"<<mx<<endl;
    return 0;
}

测试结果及分析

input.txt

8
1 2 2 2 3 4 6 8

output.txt

众数2 重数3

最小权顶点覆盖

算法分析

题意描述

(1)给定一个图,将原点集分为U,V两个集合,使得U的总权值最小,且满足对于集合V中每个顶点,集合U中都至少有一个点与他相连;
(2)除此之外,如果把题意理解为,选出一个原点集的子集U,使得对于原图中任意一条边,集合U中都至少有一个点为他的顶点,又可以写出另一种代码,这里将两种题意都使用分支界限实现

算法过程描述

(1)搜索树的每个节点代表当前搜索到的集合U的状态,有三个特征值:深度,总权值,解向量,map标记。优先队列中的优先级定义为搜索树节点的代价函数。根节点中集合U为空集,初始化根节点放入优先队列中,之后开始进行广度优先搜索;对于当前搜索到的节点,如果总权值大于当前的界,进行剪枝,否则,如果为叶子节点,则更新界并返回,否则将他的两个儿子节点放入优先队列中,继续搜索过程,知道队列中不存在可以搜素的节点为止。
(2)两种题意得整体思路是一致的,只是在标记数组,子节点更新略有不同。前一种情况下map标记记录的为当前覆盖过的点,子节点更新时需要更新当前加入的点相连的每一个节点及它自身,后一种情况下map标记当前覆盖过的边,子节点更新时更新当前点相连的每一条边。

代价函数及分支限界法优化分析

因为题目要求的是最小权,需要寻找当前节点的下界,所以将代价函数定义为当前已加入集合U的总权值(假设后来的点都不会加入集合U中)。如果当前节点代价函数大于界,则进行剪枝。原搜索空间为一个子集树,最坏情况下有2^n个叶子节点。在代码中,除了使用剪枝,还使用了优先队列进行最小耗费优先搜索,大大提升了算法效率。
现通过搜索树中叶节点数目对算法优化进行分析,在原代码中加上tag标记叶节点数目,对于同一个输入样例(样例在测试结果中),测试如下:
(1)未剪枝时tag值为128,即2^7,剪枝后tag值为28,相比之下优化了79%
(2)未剪枝时tag值为128,即2^7,剪枝后tag值为32,相比之下优化了75%

流程图

算法关键代码

(1)

#include "bits/stdc++.h"
using namespace std;
#define stop system("pause")
#define rpp(i,b) for(register int i=1,i##len=(int)(b);i<=i##len;++i)
template <typename T> ostream& operator<<(ostream &os,const vector<T> &v) {for(auto i = begin(v);i!=end(v);i++) os<<*i<<(i==end(v)-1?"\n":" ");return os;}
// -------------------------------------------------------------------------------------------------------------------------------------------------------
const int MX = 2e5 + 10;
//将原点集分为U,V两个集合,使得对于V中的每个点,至少与U集合中的某个点直接相连
int w[MX];          //每个点的权
vector<int> mp[MX]; //图
class IN
{
public:
    int sumw, cnt;     //目前总的权值,覆盖点数
    vector<int> ans;   //答案数组
    map<int, int> tag; // 标记目前覆盖到的点
    IN(){sumw = cnt = 0;}
    IN(const IN &o)
    {
        sumw = o.sumw, cnt = o.cnt, tag = o.tag;
        for(auto x:o.ans) ans.push_back(x);
    }
    bool operator<(IN a) const {return sumw > a.sumw;}
};
signed main()
{
    int n, m,mn = 1e9;cin >> n >> m;
    rpp(i,n) cin>>w[i];
    rpp(_,m)
    {
        int x, y;cin >> x >> y;
        mp[x].push_back(y), mp[y].push_back(x);
    }
    priority_queue<IN> q;
    q.push(IN());
    vector<int> ans;
    while (q.size())
    {
        IN x = q.top();q.pop();
        if (x.sumw >= mn)  continue; //剪枝:当前权值已经大于ans
        if ((int)x.ans.size() == n)
        {
            if (x.cnt >= n && x.sumw < mn)
                mn = x.sumw, ans = x.ans;
            continue;
        }
        IN tmp = x;//不放该点
        tmp.ans.push_back(0);
        q.push(tmp);
        IN tmp1 = x;//放该点
        int nowid = x.ans.size() + 1;                 //当前放进来的是哪个点
        tmp1.ans.push_back(1), tmp1.sumw += w[nowid]; //更新权值和,解向量
        for(auto v:mp[nowid])    //把这个点连的点更新
        {
            if (tmp1.tag[v] == 0)  tmp1.cnt++; 
            if (tmp1.tag[nowid] == 0) tmp1.cnt++;
            tmp1.tag[v] = tmp1.tag[nowid] = 1;
        }
        q.push(tmp1);
    }
    cout << mn << endl<< ans <<endl;
    return 0;
}

(2)

#include "bits/stdc++.h"
using namespace std;
#define stop system("pause")
#define rpp(i,b) for(register int i=1,i##len=(int)(b);i<=i##len;++i)
template <typename T> ostream& operator<<(ostream &os,const vector<T> &v) {for(auto i = begin(v);i!=end(v);i++) os<<*i<<(i==end(v)-1?"\n":" ");return os;}
// --------------------------------------------------------------------------------------------------------------------------------------------------------
const int MX = 2e5 + 10;
//用总点权最少的一组点使原图每条边至少与子集一个点相连
int n,m,w[MX];             //点数,边数,点权
vector<int> mp[MX];             //边
class IN
{
public:
    int sumw, cnt;             //目前总的权值,当前节点深度
    vector<int> ans;              //答案数组
    map<pair<int, int>, int> tag; //覆盖到的边
    IN(){sumw = cnt = 0;}
    IN(const IN &o)
    {
        sumw = o.sumw, cnt = o.cnt, tag = o.tag;
        for (auto x : o.ans) ans.push_back(x);
    }
    bool operator<(IN a) const {return sumw > a.sumw;}
};
signed main()
{
    cin >> n >> m;
    rpp(i, n) cin >> w[i];
    rpp(_, m)
    {
        int x, y;cin >> x >> y;
        mp[x].push_back(y), mp[y].push_back(x);
    }
    priority_queue<IN> q;
    q.push(IN());
    int mn = 1e9;
    vector<int> ans;
    while (q.size())
    {
        IN x = q.top();q.pop();
        if(x.sumw>=mn) continue;//剪枝:当前权值已经大于ans
        if ((int)x.ans.size() == n)
        {
            if (x.cnt >= m && x.sumw < mn)
                mn = x.sumw, ans = x.ans;
            continue;
        }
        //不放该点
        IN tmp = x;
        tmp.ans.push_back(0);
        q.push(tmp);
        //放该点
        IN tmp1 = x;
        int nowid = x.ans.size() + 1;                 //当前放进来的是哪个点
        tmp1.ans.push_back(1), tmp1.sumw += w[nowid]; //更新权值和,答案数组
        for (auto v : mp[nowid])                      //把这个点加的所有边更新
        {
            if (tmp1.tag[make_pair(nowid, v)] == 0) //这个边还没有被覆盖过
                tmp1.cnt++;
            tmp1.tag[make_pair(nowid, v)] = tmp1.tag[make_pair(v, nowid)] = 1;
        }
        q.push(tmp1);
    }
    cout << mn << endl << ans << endl;
    return 0;
}

测试结果及分析

输入

7 7
1 100 1 1 1 100 10
1 6
2 4
2 5
3 6
4 5
4 6
6 7

(1)输出

13
1 0 1 0 1 0 1

(2)输出

14
1 0 1 1 1 0 1

版权声明:本文为weixin_44116061原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。