贪心算法
问题描述
编程实现T/S的d森林问题。
设T为一带权树,树中的每个边的权都为整数。又设S为T的一个顶点的子集,从T中删除S中的所有结点,则得到一个森林,记为T/S。如果T/S中所有树从根到叶子节点的路径长度都不超过d,则称T/S是一个d森林。设计一个算法求T的最小顶点集合S,使T/S为一个d森林。
问题分析
使用贪心的思想,删除最少的节点,即S中元素个数最少。
原理分析
使用算法
使用贪心的思想,从叶子节点向上遍历,每个节点的priceOfson记录该节点子节点中路径长度最大值,若priceOfson大于d则删除该节点,每一次遍历访问 无子节点或子节点均被访问或删除 且 未被标记 的节点,直到所有节点均被访问,得到最优解。
性质证明
设a为一节点,b1, b2 …… bi 为他的子节点,从叶子节点到bi的路径长度设为Li,当Li>d的字节点个数大于2时,删除a为更优解,当Li>d的个数为1时,删除子节点和a的个数相同,当Li > d的节点个数为0时,不删除节点。
综上所述,删除高度更大的节点更符合贪心性质,可以得到更优解。
算法实现
#include <iostream>
#include <stack>
#include <vector>
#include <math.h>
using namespace std;
struct node
{
int id;
vector<int> son;
int parent;
int priceOfson;
int priceOfme;
int markedson;
};
bool cmp(int a, int b) { return a>b; }
int main()
{
int d, n, i = 0, j = 0;
cin>>n>>d;
node xx;
vector<node> a(n+1, xx);
vector<node> root;
vector<int> marked(n+1, 0);
xx.id = 1;
root.push_back(xx);
for(i = 1; i <=n; i++)
{
int x;
cin>>x;
a[i].id = i;
for(j = 0; j < x; j++)
{
int y = 0, z = 0;
cin>>y>>z;
a[i].son.push_back(y+1);
a[y+1].parent = i;
a[y+1].priceOfme = z;
}
}
cout<<"**"<<endl;
vector<int> S;
vector<int> cut(n+1, 0);
int ee = 0;
int cc = 0;
while(ee < n)
{
for(i = 1; i <= n; i++)
{
if(cut[i]) continue;
if(!marked[i] && !cut[i] && a[i].markedson == a[i].son.size())
// 该节点未被访问 且 未被删除 且 不是根结点 且 儿子节点已经全部被访问(即无子)
{
ee++;
// the price of me and max price of my son
if(a[i].priceOfson > d)
{
S.push_back(a[i].id);
cut[i] = 1;
marked[i] = 1;
a[a[i].parent].markedson++;
}
else
{
cc++;
// update parents' priceofson
if(a[a[i].parent].priceOfson < a[i].priceOfme + a[i].priceOfson)
a[a[i].parent].priceOfson = a[i].priceOfme + a[i].priceOfson;
a[a[i].parent].markedson++;
marked[i] = 1;
}
}
}
}
cout<<S[0]<<" "<<S.size()<<endl;
return 0;
}