Vertex Cover问题

最近算法课上完成了一道作业题,Vertex Cover,拿出来与大家分享一下。算法不能说有多好,有问题欢迎致信[email protected]

1,问题描述

首先Vertex Cover问题是NP完全问题,所以写的代码仅仅针对该问题的小规模情况。大规模情况下可能就需要使用近似算法。在算法导论的34章NP完全性的34.5.2节就给出了近似解法。当然,这道作业题是要求求出准确的解,所以就不考虑近似算法了。

先介绍一下这个问题。Vertex Cover问题指的是给定一个N个点,M条边的无向图G(点的编号从1至N),问是否存在一个不超过K个点的集合S,使得G中的每条边都至少有一个点在集合S中。

解释这个问题,N个点,M条边的无向图,每次去掉一个点以及该点相邻的边,直到把所有的边都去掉。一个可能的过程如下,红色为待去除的点:

Vertex Cover问题_第1张图片

Vertex Cover问题_第2张图片

Vertex Cover问题_第3张图片

Vertex Cover问题_第4张图片

问题的目的就是在所有可能的情况中,找出一种使用的点最少的情况(可能并不唯一,只要求出一个即可)。

2,思路

首先每次找到当前情况下度数最大的点来删除未必能够得到最优解,反例如下图,如果开始就去掉度最多的最中间的点,那么就得不到最优解了:

Vertex Cover问题_第5张图片

但是每次都尝试当前度最高的点在很多情况下的确能够帮助尽快找到最优解。

其次,如果按照对于某个点选择,不选择情况分别进行处理,则算法需要耗费大量的时间,最坏时候要尝试近2^n种情况。所以必须有一定的准则进行剪枝。

综上,处理的思路主要有两个方面,一个方面是选择优先局部最优的点进行处理,以期望尽早找到最优的一个情况;另一个方面是设计一定的准则,去除掉一些不可能产生更优解的情况,或者是把一些必然会发生的情况直接进行确认都不用进行探寻。

3,准则

为了减少要遍历的情况的数目,按照一定的准则1,去除不可能产生更优解的情况;2,一些必然发生的情况直接加入当前解。

1)去除不可能产生更优解的情况

如果当前已经探索过的结点数仅仅比当前最优解少一个,那么就不用继续探索了,因为最好也只能找到和当前最优解一样优的解。
如果当前最大的结点的度,加上(当前第二大结点的度*(当前最优解-当前已探索过的结点-2))仍然小于未消除过的边的数目,那么就不用继续探索了,因为不可能得到更优解了。

2)必然发生的情况直接加入当前解

如果当前的结点中存在度为1的结点,该度为1的结点(设为A)所在边的另一个结点(设为B)必然会存在于当前解中,因为无论B的度是否为1,那么要消除这条边则A,B两个结点任意选一个就可以消除,选B符合情况。而且B还有可能度大于1,也就是如果A可以存在于当前解中,则B替代A得到的解只会和A一样优或者更优。
如果不选择某个结点,那么该结点相邻的点都必须加入当前解中,否则会存边无法被消除。

4,代码

所以得到的代码如下:
#include 
#include 
#include 

const int MAX_VERTEX = 100;
// 测试次数,点数,边数,限制的k,如果不能得到小于k的结果,就输出-1
int num_of_test, num_of_vertex, num_of_edge, k;
// 存储最优的点的信息
std::vector result_vertex_list;

// 处理度为1的结点
void deal_with_degree_one(std::map > &info, std::map &searched, int &checked_vertex, int &checked_edge){
	bool flag = true;
	int tmp;
	int length;
	while (flag){
		flag = false;
		for (std::map >::iterator iter = info.begin(); iter != info.end();){
			length = iter->second.size();
			if (length == 1){
				// 增加search过的边和元素
				tmp = iter->second.begin()->first;
				if (searched.count(tmp) == 0)
					++checked_vertex;
				// iter->second.erase(tmp);
				searched[tmp] = 1;
				// 必定加入的点要删除所有相关的点
				for (std::map::iterator iter1 = info[tmp].begin(); iter1 != info[tmp].end();){
					info[iter1->first].erase(tmp);
					++checked_edge;
					info[tmp].erase(iter1++);
				}
				// 删除边和元素
				info.erase(iter++);
				flag = true;
			} else if (length == 0) {
				info.erase(iter++);
			} else {
				++iter;
			}
		}
	}
}

//遍历
void dfs(int index, int *minimum, int checked_vertex, int checked_edge, std::map > &info, std::map &searched){
	// 标记此点已经检查过
	searched[index] = 1;
	// 如果加上该点已经检查过比minimum多的点,则不可能更优了,返回
	if (checked_vertex + 1 >= *minimum){
		searched.erase(index);
		return;
	}
	// 假如加入该点
	// 如果已经覆盖了全部的边
	if (checked_edge + info[index].size() == num_of_edge){
		if (checked_vertex + 1 < *minimum){
			*minimum = checked_vertex + 1;
			result_vertex_list.clear();
			for (std::map::iterator iter = searched.begin(); iter != searched.end(); iter++)
				result_vertex_list.push_back(iter->first);
		}
		searched.erase(index);
		return;
	}
	// 存储修改后的变量
	std::map > tmp_info, tmp_info_not;
	std::map tmp_searched, tmp_searched_not;
	int tmp_checked_vertex = checked_vertex, tmp_checked_vertex_not = checked_vertex;
	int tmp_checked_edge = checked_edge, tmp_checked_edge_not = checked_edge;
	for (std::map >::iterator iter = info.begin(); iter != info.end(); iter++){
		tmp_info[iter->first] = iter->second;
		tmp_info_not[iter->first] = iter->second;
	}
	for (std::map::iterator iter = searched.begin(); iter != searched.end(); iter++){
		tmp_searched[iter->first] = iter->second;
		tmp_searched_not[iter->first] = iter->second;
	}
	// 如果使用该点
	// 删除本点的信息和其他点的关于该点的信息,修改相关信息
	++tmp_checked_vertex;
	tmp_checked_edge += info[index].size();
	for (std::map::iterator iter = info[index].begin(); iter != info[index].end(); iter++)
		tmp_info[iter->first].erase(index);
	tmp_info.erase(index);
	// 处理度为1的点
	deal_with_degree_one(tmp_info, tmp_searched, tmp_checked_vertex, tmp_checked_edge);
	// 如果找到了所需要的边数
	if (tmp_checked_edge == num_of_edge){
		if (tmp_checked_vertex < *minimum){
			*minimum = tmp_checked_vertex;
			result_vertex_list.clear();
			for (std::map::iterator iter = tmp_searched.begin(); iter != tmp_searched.end(); iter++)
				result_vertex_list.push_back(iter->first);
		}
		searched.erase(index);
		return;
	}
	// 寻找下一个最大度数的点
	bool found = false;
	int temp_index = 0, tmp_max_length = 0, tmp_second_max_length = 0;
	for (std::map >::iterator iter = tmp_info.begin(); iter !=tmp_info.end(); iter++){
		if (tmp_searched.count(iter->first) == 0){
			int tmp_length = iter->second.size();
			if (tmp_length > tmp_max_length){
				found = true;
				temp_index = iter->first;
				tmp_second_max_length = tmp_max_length;
				tmp_max_length = tmp_length;
			} else if (tmp_length > tmp_second_max_length) {
				tmp_second_max_length = tmp_length;
			}
		}
	}
	int weight;
	if (*minimum - tmp_checked_vertex <= 2)
    {
        weight = tmp_max_length * (*minimum - tmp_checked_vertex -1);
    }
    else
    {
        weight = tmp_max_length + tmp_second_max_length * ((*minimum - tmp_checked_vertex - 2));
    }
	if (weight >= num_of_edge - tmp_checked_edge){
		dfs(temp_index, minimum, tmp_checked_vertex, tmp_checked_edge, tmp_info, tmp_searched);
	}
	// 如果不使用该点,那么所有与该点相邻的点都必须被使用,否则必存在着边无法被消去
	// 消去该点相邻的点
	// 本点不被使用
	tmp_searched_not.erase(index);
	// 找到该点相邻的点
	for (std::map::iterator iter = info[index].begin(); iter != info[index].end(); iter++){
		// 删除tmp_info_not中的该点相邻的点,标记使用
		tmp_searched_not[iter->first] = 1;
		++tmp_checked_vertex_not;

		for (std::map::iterator iter1 = info[iter->first].begin(); iter1 != info[iter->first].end(); iter1 ++){
			// 如果该边还未在之前其他点的时候被删除
			if (tmp_info_not.count(iter1->first) > 0){
				tmp_info_not[iter1->first].erase(iter->first);
				tmp_info_not[iter->first].erase(iter1->first);
				++tmp_checked_edge_not;
			}
		}
		tmp_info_not.erase(iter->first);
	}
	// 处理度为1的点
	deal_with_degree_one(tmp_info_not, tmp_searched_not, tmp_checked_vertex_not, tmp_checked_edge_not);
	// 如果找到了所需要的边数
	if (tmp_checked_edge_not == num_of_edge){
		if (tmp_checked_vertex_not < *minimum){
			*minimum = tmp_checked_vertex_not;
			result_vertex_list.clear();
			for (std::map::iterator iter = tmp_searched_not.begin(); iter != tmp_searched_not.end(); iter++)
				result_vertex_list.push_back(iter->first);
		}
		searched.erase(index);
		return;
	}
	// 寻找下一个最大度数的点
	found = false;
	temp_index = 0, tmp_max_length = 0, tmp_second_max_length = 0;
	for (std::map >::iterator iter = tmp_info_not.begin(); iter !=tmp_info_not.end(); iter++){
		if (tmp_searched_not.count(iter->first) == 0){
			int tmp_length = iter->second.size();
			if (tmp_length > tmp_max_length){
				found = true;
				temp_index = iter->first;
				tmp_second_max_length = tmp_max_length;
				tmp_max_length = tmp_length;
			} else if (tmp_length > tmp_second_max_length) {
				tmp_second_max_length = tmp_length;
			}
		}
	}
	if (*minimum - tmp_checked_vertex <= 2)
    {
        weight = tmp_max_length * (*minimum - tmp_checked_vertex -1);
    }
    else
    {
        weight = tmp_max_length + tmp_second_max_length * ((*minimum - tmp_checked_vertex - 2));
    }
	if (weight >= num_of_edge - tmp_checked_edge){
		dfs(temp_index, minimum, tmp_checked_vertex_not, tmp_checked_edge_not, tmp_info_not, tmp_searched_not);
	}
	searched.erase(index);
	return;
}

int main(){
	// 读取的边
	int u = 0, v = 0;
	// 记录已经寻找到的点数, 边数
	int checked_vertex, checked_edge;
	// 保存边的信息
	std::map > info;
	// 保存已经搜索的点的信息
	std::map searched;
	// 开始读取
	scanf("%d", &num_of_test);
	for (int i = 0; i < num_of_test; i++){
		info.clear();
		searched.clear();
		result_vertex_list.clear();
		scanf("%d %d %d", &num_of_vertex, &num_of_edge, &k);
		// 初始化
		checked_vertex = 0, checked_edge = 0;
		// 读取数据
		for (int j = 0; j < num_of_edge; j++){
			scanf("%d %d", &u, &v);
			info[u][v] = 1;
			info[v][u] = 1;
		}
		// 处理度为1的点
		deal_with_degree_one(info, searched, checked_vertex, checked_edge);
		// 需要传送的参数
		int minimum = k+1;
		// 如果去除了为1的点之后已经所有的边都去掉了
		if (checked_edge == num_of_edge){
			if (checked_vertex < minimum){
				minimum = checked_vertex;
				result_vertex_list.clear();
				for (std::map::iterator iter = searched.begin(); iter != searched.end(); iter++)
					result_vertex_list.push_back(iter->first);
			}
		} else {
			// 仍然有边剩余,进行处理
			// int minimum = k+1;
			int temp_index = 0, tmp_max_length = 0;
			for (std::map >::iterator iter = info.begin(); iter != info.end(); iter++){
				if (searched.count(iter->first) == 0){
					int tmp_length = iter->second.size();
					if (tmp_length > tmp_max_length){
						temp_index = iter->first;
						tmp_max_length = tmp_length;
					}
				}
			}
			dfs(temp_index, &minimum, checked_vertex, checked_edge, info, searched); 
		}
		// 如果找不到小等于k个节点的情况则输出-1,否则输出点数和一种结果的情况
		if (minimum > k){
			printf("%d\n", -1);
		} else {
			printf("%d\n", minimum);
			for (std::vector::iterator iter = result_vertex_list.begin(); iter != result_vertex_list.end(); iter++)
                printf("%d ", *iter);
            printf("\n");
		}
	}
	return 0;
}
因为过程中很多是查询,所以数据结构上使用了map来存储边和结点的信息.其实其中有很多地方可以精简。




你可能感兴趣的:(算法)