匈牙利指派问题

#include <iostream>
using namespace std;

#define Max 5	//维数
int orgin[Max][Max];        //原始矩阵
int sub[Max][Max];        //归约矩阵
int draw[Max][Max];        //0:未被画线 1:画了1次 2: 画了2次(交点)
int row0[Max], col0[Max];    //行列0计数器; 记录行列0个数
int dep0[Max][Max];        //元素类型记录器; 0:非0元素 1:非独立0元素 2:独立0元素
int hookX[Max], hookY[Max];        //画线时是否被打勾,1是0不是

void printArray(int arr[][Max], int x, int y) {
	for (int i = 0; i < x; ++i) {
		for (int j = 0; j < y; ++j) {
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}
	cout << endl;
}

void print(int* arr, int len) {
	for (int i = 0; i < len; ++i) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

//计算行列0个数
void countZero() {
	//重置行列0计数器,元素类型记录器
	memset(row0, 0, sizeof(int) * Max);
	memset(col0, 0, sizeof(int) * Max);
	memset(dep0, 0, sizeof(int) * Max * Max);

	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			if (sub[i][j] == 0) {
				row0[i]++;
				col0[j]++;
			}
		}
	}
}

int drawLine() {
	//重置划线记录,行列勾选记录
	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			draw[i][j] = 0;
		}
		hookX[i] = 1;
		hookY[i] = 0;
	}

	//对不含独立0元素的行打勾,这里是含独立0去勾
	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			if (dep0[i][j] == 2) {
				hookX[i] = 0;
				break;
			}
		}
	}

	bool isStop = false;

	while (!isStop) {
		isStop = true;

		//hookY 对打勾的行中含0元素的未打勾的列打勾
		for (int i = 0; i < Max; ++i) {
			if (hookX[i] == 1) {
				for (int j = 0; j < Max; ++j) {
					if (sub[i][j] == 0 && hookY[j] == 0) {
						hookY[j] = 1;
						isStop = false;
					}
				}
			}
		}
		//hookX 对打勾的列中含独立0元素的未打勾的行打勾
		for (int i = 0; i < Max; ++i) {
			if (hookY[i] == 1) {
				for (int j = 0; j < Max; ++j) {
					if (sub[j][i] == 0 && dep0[j][i] == 2 && hookX[j] == 0) {
						hookX[j] = 1;
						isStop = false;
					}
				}
			}
		}
	}

	//没有打勾的行和有打勾的列画线,这就是覆盖所有0元素的最少直线数
	int line = 0;
	for (int i = 0; i < Max; ++i) {
		if (hookX[i] == 0) {
			for (int j = 0; j < Max; ++j) {
				draw[i][j]++;
			}
			line++;
		}

		if (hookY[i]) {
			for (int j = 0; j < Max; ++j) {
				draw[j][i]++;
			}
			line++;
		}
	}
	return line;
}

//试指派,标记独立0元素位置
/*1.找含0最少(至少一个)的那一行/列    2.划掉,更新该行/列0元素所在位置的row[],col[]
 3.直到所有0被划线,这里定义为row[]col[]全为INT_MAX,表示该行/列无0元素*/
int find() {
//	printArray(sub, Max, Max);
	countZero();

	int zero = 0;

	while (1) {
		for (int i = 0; i < Max; ++i) {
			//至少一个0, 将0个设置为INT_MAX避免重复
			if (row0[i] == 0) {
				row0[i] = INT_MAX;
			}
			if (col0[i] == 0) {
				col0[i] = INT_MAX;
			}
		}

		bool stop = true;
//		找含0最少(至少一个)的那一行/列
		if (*min_element(row0, row0 + Max) <= *min_element(col0, col0 + Max)) {
			int min = row0[0], rowId = 0;
			for (int i = 1; i < Max; ++i) {
				if (min > row0[i]) {
					min = row0[i];
					rowId = i;
				}
			}

			int colId = -1;
			for (int i = 0; i < Max; ++i) {
				if (sub[rowId][i] == 0 && col0[i] != INT_MAX) {
					colId = i;
					stop = false;
					zero++;
					break;
				}
			}

			if (stop) {
				break;
			}
			//所在行列上的0划去
			row0[rowId] = col0[colId] = INT_MAX;
			dep0[rowId][colId] = 1;

			//更新独立0所在的行/列上的0对应的未被划线的行列0的计数
			for (int i = 0; i < Max; ++i) {
				if (sub[rowId][i] == 0 && col0[i] != INT_MAX) {
					col0[i]--;
				}
				if (sub[i][colId] == 0 && row0[i] != INT_MAX) {
					row0[i]--;
				}
			}
		} else {
			int min = col0[0], colId = 0;
			for (int i = 1; i < Max; ++i) {
				if (min > col0[i]) {
					min = col0[i];
					colId = i;
				}
			}

			int rowId = -1;
			for (int i = 0; i < Max; ++i) {
				if (sub[i][colId] == 0 && row0[i] != INT_MAX) {
					rowId = i;
					stop = false;
					zero++;
					break;
				}
			}

			if (stop) {
				break;
			}

			row0[rowId] = col0[colId] = INT_MAX;
			dep0[rowId][colId] = 1;

			for (int i = 0; i < Max; ++i) {
				if (sub[rowId][i] == 0 && col0[i] != INT_MAX) {
					col0[i]--;
				}
				if (sub[i][colId] == 0 && row0[i] != INT_MAX) {
					row0[i]--;
				}
			}
		}
	}

	//r[i][j] 0:非0元素 1:非独立0元素 2:独立0元素
	for (int i = 0; i < Max; ++i)
		for (int j = 0; j < Max; ++j)
			if (sub[i][j] == 0)
				dep0[i][j]++;

	return zero;
}

//初始化数据
void init() {
	int orginData[Max][Max] = { { 12, 7, 9, 7, 9 }, { 8, 9, 6, 6, 6 }, { 7, 17,
			12, 14, 9 }, { 15, 14, 6, 6, 10 }, { 4, 10, 7, 10, 9 } };

	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			orgin[i][j] = orginData[i][j];
		}
	}
}

//行列规约
void subRowAndCol() {
	//行规约
	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			//求和最大
			//sub[i][j] = *max_element(orgin[i], orgin[i]+Max)-orgin[i][j];
			//求和最小
			sub[i][j] = orgin[i][j] - *min_element(orgin[i], orgin[i] + Max);
		}
	}

	//列规约
	for (int i = 0; i < Max; ++i) {
		int min = sub[0][i];
		for (int j = 0; j < Max; ++j) {
			if (min > sub[j][i]) {
				min = sub[j][i];
			}
		}
		for (int j = 0; j < Max; ++j) {
			sub[j][i] -= min;
		}
	}
}

//找出独立0元素和划线
void findDependent0() {
	//找到足够独立0元素即可
	while (find() < Max) {
		//当前矩阵无法满足,划线
		drawLine();
		int min = INT_MAX;
		//未被划线数的最小值
		for (int i = 0; i < Max; ++i) {
			for (int j = 0; j < Max; ++j) {
				if (draw[i][j] == 0 && min > sub[i][j]) {
					min = sub[i][j];
				}
			}
		}

		for (int i = 0; i < Max; ++i) {
			for (int j = 0; j < Max; ++j) {
				if (draw[i][j] == 0) {	//将所有未被划线的数减去最小值
					sub[i][j] -= min;
				} else if (draw[i][j] == 2) {	//2次划线数加上最小值
					sub[i][j] += min;
				}
			}
		}
	}
}

int main() {
	init();
	subRowAndCol();
	findDependent0();

	int result = 0;
	for (int i = 0; i < Max; ++i) {
		for (int j = 0; j < Max; ++j) {
			//独立0位置对应值之和为解
			if (dep0[i][j] == 2) {
				cout << orgin[i][j] << " ";
				result += orgin[i][j];
			}
		}
	}
	cout << " = " << result << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值