离散数学实验三则(关系元算,集合运算与操作,最短路)

前言

前段时间才做了离散实验的实验报告,为防止在本地遗失,所以上传到CSDN上一份,欢迎大家一起学习

实验1 关系元算

实验报告内容

一、实验目的

熟悉掌握命题逻辑中的联结词、真值表、主范式等,进一步能用他们来解决实际问题。

二、实验内容

1、从键盘输入两个命题变元P和Q的真值,求它们的合取、析取、条件和双条件的真值。(A)

2、求任意一个命题公式的真值表(B,并根据真值表求主范式(C))

三、实验环境

C或C++语言编程环境实现。
DEVC++ 5.4.0
MinGW GCC 4.7.2 32-bit
-std=c++17

四、实验原理和过程(算法)

A:直接if……else……判断,没啥好说的。

B:没有考虑括号和时间效率就简单多了。考虑括号的话最优解应该是用树+栈(貌似是中值表达式的模板题),考虑到只有五个优先级,就直接用map存下五个优先级,然后从高到低暴力遍历,每次把结果算出来化简字符串,最后输出字符创就是真值表最终的值。

C:相比于B来说,C就简单多了,考虑一下一共有多少个不同的命题,然后以此为位数,直接枚举+位运算,感觉没啥要注意的点。

五、程序清单

A:

#include 
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e6 + 3;
int dp[500][500];
int s[500][500];
int a[N];
bool judge1(int p, int q)
{
	if (p == 1)
	{
		if (q == 1)
			return 1;
	}
	return 0;
} //合取
bool judge2(int p, int q)
{
	if (p == 0)
	{
		if (q == 0)
			return 0;
	}
	return 1;
} //析取
bool judge3(int p, int q)
{
	if (p == 1)
	{
		if (q == 0)
			return 0;
	}
	return 1;
} //条件
bool judge4(int p, int q)
{
	if (judge3(p, q))
	{
		if (judge3(q, p))
			return 1;
	}
	return 0;
} //双条件
bool judge(int p, int q)
{
	if (p == 1)
	{
		if (q == 1)
			return 1;
		if (q == 0)
			return 1;
		return 0;
	}
	if (p == 0)
	{
		if (q == 1)
			return 1;
		if (q == 0)
			return 1;
		return 0;
	}
	return 0;
}
signed main()
{
	int p, q;
	cout << "P:";
	cin >> p;
	cout << "Q:";
	cin >> q;
	if (judge(p, q) == 0)
	{
		cout << "error\n";
		return 0;
	}
	cout << "析取:" << judge2(p, q) << endl;
	cout << "合取:" << judge1(p, q) << endl;
	cout << "条件:" << judge3(p, q) << endl;
	cout << "双条件:" << judge4(p, q) << endl;
}

B1暴力做法:(只能计算三个命题之间的真值表,无法判断括号)

#include 
#define int long long
#define endl '\n'
using namespace std;
map mp;
bool judge1(int p)
{
	return (p + 1) % 2;
} //非
bool judge2(int p, int q)
{
	if (p == 1)
	{
		if (q == 1)
		{
			return 1;
		}
	}
	return 0;
} //且
bool judge3(int p, int q)
{
	if (p == 0)
	{
		if (q == 0)
		{
			return 0;
		}
	}
	return 1;
} //并
bool judge4(int p, int q)
{
	if (p == 1)
	{
		if (q == 0)
		{
			return 0;
		}
	}
	return 1;
} //条件
bool judge5(int p, int q)
{
	if (p == 1)
	{
		if (q == 1)
		{
			return 1;
		}
		return 0;
	}
	if (p == 0)
	{
		if (q == 0)
		{
			return 1;
		}
		return 0;
	}
} //双条件
void solve(int i, int j, int k, string x)
{
	while (x.find('i') != string::npos)
	{
		x[x.find('i')] = i + '0';
	}
	while (x.find('j') != string::npos)
	{
		x[x.find('j')] = j + '0';
	}
	while (x.find('k') != string::npos)
	{
		x[x.find('k')] = k + '0';
	} //替换未知数为值
	for (int l = 5; l > 0; l--)
	{
		for (int r = 0; r < x.length(); r++)
		{
			if (x[r] == mp[l])
			{
				if (l == 5)
				{
					cout << judge1(x[r + 1] - '0') << " ";
					x[r] = judge1(x[r + 1] - '0') + '0';
					x.erase(r + 1, 1);
				} //判断‘!’,并将计算结果替换原始表达式
				if (l == 4)
				{
					cout << judge2(x[r - 1] - '0', x[r + 1] - '0') << " ";
					x[r - 1] = judge2(x[r - 1] - '0', x[r + 1] - '0') + '0';
					x.erase(r, 2);

				} //判断‘^’,并将计算结果替换原始表达式
				if (l == 3)
				{
					cout << judge3(x[r - 1] - '0', x[r + 1] - '0') << " ";
					x[r - 1] = judge3(x[r - 1] - '0', x[r + 1] - '0') + '0';
					x.erase(r, 2);
				} //判断‘v’,并将计算结果替换原始表达式
				if (l == 2)
				{
					cout << judge4(x[r - 1] - '0', x[r + 1] - '0') << " ";
					x[r - 1] = judge4(x[r - 1] - '0', x[r + 1] - '0') + '0';
					x.erase(r, 2);
				} //判断‘->’,并将计算结果替换原始表达式
				if (l == 1)
				{
					cout << judge5(x[r - 1] - '0', x[r + 1] - '0') << " ";
					x[r - 1] = judge5(x[r - 1] - '0', x[r + 1] - '0') + '0';
					x.erase(r, 2);
				} //判断‘<->’,并将计算结果替换原始表达式
				r--;
			}
		}
	}
	cout << x << endl; //化简到最后只剩一个字符,即输出一个数字
}
bool judge_ele(string y, int i)
{
	if (y[i] != 'P')
	{
		if (y[i] != 'Q')
		{
			if (y[i] != 'R')
			{
				return 0;
			}
		}
	}
	return 1;
} //判断字母
signed main()
{
	string x, y;
	cin >> x;
	mp[5] = '!';
	mp[4] = '^';
	mp[3] = 'v';
	mp[2] = '#';
	mp[1] = '@';	// mp记录五种运算的优先级
	y = "!^v#@PQR"; //合法字符串模板
	while (x.find("<->") != string::npos)
	{
		int p = x.find("<->");
		x[p] = '@';
		x.erase(p + 1, 2);
	}
	while (x.find("->") != string::npos)
	{
		int p = x.find("->");
		x[x.find("->")] = '#';
		x.erase(p + 1, 1);
	} //将多字节运算符替换为单字节

	for (int i = 0; x[i]; i++)
	{
		if (count(y.begin(), y.end(), x[i]) == 0)
		{
			cout << "error!\n";
			return 0;
		}
	} //判断输入符号是否合法

	string temp = x;
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; temp[j]; j++)
		{
			if (i == 0)
			{
				if (temp[j] == '!')
				{
					if (judge_ele(temp, j + 1) == 0)
					{
						cout << "error!\n";
						return 0;
					}
				}
			} // i==0时优先判断!是否合法(即观察!后是否为合法字母)
			else
			{
				if (judge_ele(temp, j) == 0)
				{
					if (j - 1 < 0 && judge_ele(temp, j - 1) == 0)
					{
						cout << "error!\n";
						return 0;
					}
					else
					{
						if (j + 1 >= temp.length() && judge_ele(temp, j + 1) == 0)
						{
							cout << "error!\n";
							return 0;
						}
					}
				} //当前位置不是字母时,判断该运算符前后是否为合法字母,若不是合法字母,则输出error
				else
				{
					if (j - 1 < 0 && judge_ele(temp, j - 1))
					{
						cout << "error!\n";
						return 0;
					}
					else
					{
						if (j + 1 >= temp.length() && judge_ele(temp, j + 1))
						{
							cout << "error!\n";
							return 0;
						}
					}
				} //当前位置为合法字母时,判断字母前后是否为运算符 ,若不是合法运算符,则输出error
			}
		}
		while (temp.find("!") != string::npos)
		{
			int p = temp.find("!");
			temp.erase(p, 1);
		} //当‘!’判断完后,删除所有的‘!’以防影响下一次判断
	}	  //判断表达式是否合法

	while (x.find('P') != string::npos)
	{
		x[x.find('P')] = 'i';
	}
	while (x.find('Q') != string::npos)
	{
		x[x.find('Q')] = 'j';
	}
	while (x.find('R') != string::npos)
	{
		x[x.find('R')] = 'k';
	} //将'P','Q','R'替换成'i','j','k'与枚举变量名字一致,方便表示

	for (int i = 0; i <= 1; i++)
	{
		for (int j = 0; j <= 1; j++)
		{
			for (int k = 0; k <= 1; k++)
			{
				cout << i << " " << j << " " << k << " ";
				solve(i, j, k, x); //枚举每一种取值情况
			}
		}
	}
}

B2语法树做法:(可以判断括号,对命题数量无限制)

#include
#define OP 0
#define NUM 1//宏定义确定该位置为操作符还是数字 
using namespace std;
setoperator_set = { '(', ')', '=', '^', '!', '&', '|' };//操作集
maplevel; //优先级
bool judge1(int p) {
	return (p + 1) % 2;
} //非
bool judge2(int p, int q) {
	if (p == 1) {
		if (q == 1) {
			return 1;
		}
	}
	return 0;
} //且
bool judge3(int p, int q) {
	if (p == 0) {
		if (q == 0) {
			return 0;
		}
	}
	return 1;
} //并
bool judge4(int p, int q) {
	if (p == 1) {
		if (q == 0) {
			return 0;
		}
	}
	return 1;
} //条件
bool judge5(int p, int q) {
	if (p == 1) {
		if (q == 1) {
			return 1;
		}
		return 0;
	}
	if (p == 0) {
		if (q == 0) {
			return 1;
		}
		return 0;
	}
} //双条件
class Token {
	public:
		int num;//数字(常量)
		char op;//操作符
		int type;
		Token(char ch) {
			type = OP;
			op = ch;
		}
		Token(int x) {
			type = NUM;
			num = x;
		}
};
class TreeNode {
	public:
		int x;
		char op;
		int type;//记录读入类型
		TreeNode *L;
		TreeNode *R;//左右子节点
		TreeNode() {};
		TreeNode(TreeNode *_L, char _op, TreeNode *_R) {
			type = OP;
			op = _op;
			L = _L, R = _R;
		}
		TreeNode(int _x) {
			type = NUM;
			x = _x;
		}//构造函数
		int eval() {//递归计算
			if (type == NUM)return x;//当检查类型为数时,已到节点底端,直接返回值
			switch (op) {
				case '|':
					return judge3(L->eval(), R->eval());
				case '&':
					return judge2(L->eval(), R->eval());
				case '!':
					return judge1(R->eval());
				case '@':
					return judge5(L->eval(), R->eval());
				case '#':
					return judge4(L->eval(), R->eval());
			}//未到底端继续递归往下查找子树计算结果
		}
};
stacknum_stk;
stackop_stk;
void addnode() {
	TreeNode *R = num_stk.top();//右节点为栈顶元素
	num_stk.pop();
	TreeNode *L = new TreeNode(0);//定义新节点为0
	char op = op_stk.top();
	op_stk.pop();
	if (op == '!') {
		num_stk.push(new TreeNode(L, op, R));//若为!直接入栈
	} else {
		if (num_stk.size())L = num_stk.top(), num_stk.pop();
		num_stk.push(new TreeNode(L, op, R));//其他则再取左节点为另一栈顶元素,入栈
	}
}
int Expression(string s) {
	level['!'] = 5;
	level['&'] = 4;
	level['|'] = 3;
	level['#'] = 2;
	level['@'] = 1;
	level['('] = level[')'] = 0;//定义优先级
	while (num_stk.size()) num_stk.pop();
	while (op_stk.size()) op_stk.pop();//清空栈
	vectorv;
	for (auto it : s) {
		if (it == '1' || it == '0') {
			v.push_back(Token(it - '0'));
		} else {
			v.push_back(Token(it));
		}
	}//开辟数组,并将读入字符串转化为数字与操作符的组合
	for (auto t : v) {
		if (t.type == NUM) {
			num_stk.push(new TreeNode(t.num));//建立语法树
		} else {
			switch (t.op) {
				case '(':
					op_stk.push('(');
					break;
				case ')':
					while (op_stk.size() && op_stk.top() != '(')addnode();
					op_stk.pop();//当有后括号时查找前括号
					break;
				default:
					while (op_stk.size() && level[op_stk.top()] >= level[t.op])addnode();
					op_stk.push(t.op);//先判断待插入操作与栈顶优先级,再加点并插入
			}
		}
	}
	while (op_stk.size())addnode();//查看操作栈是否为空,若不为空则运算未结束,继续加点
	return num_stk.top()->eval();
}

signed main() {
	string s, y;
	y = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#@!&|()";
	int idx = 0;
	cin >> s;
	while (s.find("<->") != string::npos) {
		int p = s.find("<->");
		s[p] = '@';
		s.erase(p + 1, 2);
	}
	while (s.find("->") != string::npos) {
		int p = s.find("->");
		s[s.find("->")] = '#';
		s.erase(p + 1, 1);
	} //将多字节运算符替换为单字节
	while (s.find("^") != string::npos) {
		int p = s.find("^");
		s[s.find("^")] = '&';
	}//替换v为&
	while (s.find("v") != string::npos) {
		int p = s.find("v");
		s[s.find("v")] = '|';
	}//替换^为|
	for (int i = 0; s[i]; i++) {
		if (count(y.begin(), y.end(), s[i]) == 0) {
			cout << "error!\n";
			return 0;
		}
	} //判断输入符号是否合法
	setst;
	mapmp;
	for (int i = 0; s[i]; i++) {
		if (s[i] <= 'Z' && s[i] >= 'A') {
			st.insert(s[i]);
			mp[s[i]] = 1;
		}
	}//将输入命题按从小到大排序
	for (auto it = mp.begin() ; it != mp.end(); it++) it->second = idx++;//给命题编号
	int num1 = 0, num0 = st.size();//枚举,从全0无1开始
	for (auto it : mp) cout << it.first << ' ';//输出表头
	cout << "ans" << endl;//输出表头最后一列
	while (num1 <= st.size()) {
		vectortemp;
		for (int i = 1; i <= num0; i++) temp.push_back(0);
		for (int i = 1; i <= num1; i++) temp.push_back(1);//按枚举数量分别放入相应数量0,1
		do {//全排列枚举该数量下0,1位置
			string x;
			x = s;
			for (int it = 0; x[it]; it++) {
				if (x[it] <= 'Z' && x[it] >= 'A') {
					x[it] = temp[mp[x[it]]] + '0';
				}
			}//将对应位置命题符号转化为01
			for (auto it : temp) cout << it << ' ';//输出命题取值
			cout << Expression(x) << endl;//输出真值
		} while (next_permutation(temp.begin(), temp.end()));
		num0--;
		num1++;
	}
}

C:

#include 
#define int long long
using namespace std;
const int N = 1e5 + 100;
int cnt[N];
signed main()
{
	int m;
	bool f = 0;
	cin >> m;
	int s = 0;
	for (int i = 0; i < pow(2, m); i++)
	{
		cin >> cnt[i]; // cnt存储真值表最后一列
		if (cnt[i] != 1)
		{
			if (cnt[i] != 0)
				f = 1; //判断输入是否合法(是否有除0,1外的数)
		}
	}
	if (f)
	{
		cout << "error!\n";
		return 0;
	}
	cout << "主析取范式:";
	for (int i = 0; i < pow(2, m); i++)
	{
		int t = m;	//获取位数
		if (cnt[i]) //当命题为真时进入
		{
			int k = i;
			if (s != 0)
			{
				if (i != pow(2, m))
					cout << "v"; //若不是首元,输出‘v’
			}
			s++;
			cout << "(";
			while (t)
			{

				int j = (k >> (t - 1)) & 1; //位运算判断当前为情况该位置命题为‘1’还是为‘0’
				if (j == 0)
					cout << "!"; //若为0输出!
				cout << (char)('P' + m - t);
				if (t != 1)
					cout << "^";
				t--;
			}
			cout << ")";
		}
	} //主析取范式
	cout << endl;
	s = 0;
	cout << "主合取范式:";
	for (int i = 0; i < pow(2, m); i++)
	{
		int t = m;	 //获取位数
		if (!cnt[i]) //当命题为假时进入
		{

			int k = i;
			if (s)
			{
				if (i != pow(2, m))
					cout << "^"; //若不是首元,输出‘^’
			}
			s++;
			cout << "(";
			while (t)
			{

				int j = (k >> (t - 1)) & 1; //位运算判断当前为情况该位置命题为‘1’还是为‘0’
				if (j == 1)
					cout << "!"; //若为1输出!
				cout << (char)('P' + m - t);
				if (t != 1)
					cout << "v";
				t--;
			}
			cout << ")";
		}
	} //主合取范式
	cout << endl;
}

实验2 集合运算与操作

实验报告内容

一、实验目的

集合论是一切数学的基础,也是计算机科学不可或缺的,在数据结构、数据库理论、开关理论、自动机理论和可计算理论等领域都有广泛的应用。集合的运算规则是集合论中的重要内容。通过该组实验,目的是让学生更加深刻地理解集合的概念和性质,并掌握集合的运算规则等。

二、实验内容

(1)求任意两个集合的交集、并集、差集。
(2)求任意一个集合的幂集。
(3)求任意一个集合的所有 m 元子集。
(4)求任意个元素的全排列。

三、实验环境

C或C++语言编程环境实现。
DEVC++ 5.4.0
MinGW GCC 4.7.2 32-bit
-std=c++17

四、实验原理和过程(算法)

关键词
DFS 位运算
集合的表示采用列举法,如 A={a,b,c,d}。
(1)求任意两个集合的交集、并集、差集。
A∩B={x|x ∈ \in A∧x ∈ \in B}
A∪B={x|x ∈ \in A∨x ∈ \in B}
A-B={x|x ∈ \in A∧x ∉ \notin / B}
(2)求任意一个集合的幂集。
P(A)={Ai|i ∈ \in J},其中 J={i|i 是二进制数且000 ⋯ \cdots 0 ≤ \leq i ≤ \leq 111 ⋯ \cdots 1}。
(3)求任意一个集合的所有 m 元子集。
按照(2)求出子集并判断是否符合要求。
(4)求任意个元素的全排列。
设 S={1,2,3,…,n},(a1,a2, ⋯ \cdots ,an)和(b1,b2, ⋯ \cdots ,bn)是 S 的两个全排列,若存在 i ∈ \in {1,2, ⋯ \cdots ,n},使得对一切 j=1,2, ⋯ \cdots ,i 有 aj=bj且 ai+1i+1,则称排列(a1,a2, ⋯ \cdots ,an)字典序的小于(b1,b2, ⋯ \cdots ,bn)。记为(a1,a2, ⋯ \cdots ,an)<(b1,b2, ⋯ \cdots ,bn)。若(a1,a2, ⋯ \cdots ,an)<(b1,b2, ⋯ \cdots ,bn),且不存在(c1,c2, ⋯ \cdots ,cn)使得(a1,a2, ⋯ \cdots ,an)< (c1,c2, ⋯ \cdots ,cn)<(b1,b2, ⋯ \cdots ,bn),则称(b1,b2, ⋯ \cdots ,bn)为(a1,a2, ⋯ \cdots ,an)的下一个排列。
求一个排列(a1,a2, ⋯ \cdots ,an)的下一个排列的算法如下:
(1)求满足关系式 aj-1j的 j 的最大值,设为 i,即 i=max{j|aj-1j}
(2)求满足关系式 ai-1k的 k 的最大值,设为 j,即 j=max{k|ai-1k}
(3)ai-1与 aj互换得序列(b1,b2, ⋯ \cdots ,bn)
(4)将(b1,b2, ⋯ \cdots ,bn)中部分 bi,bi+1,…,bn 的顺序逆转,得到(b1,b2, ⋯ \cdots ,bi-1,bn, ⋯ \cdots ,bi)便是所求得下一个排列

五、程序清单

(1)求任意两个集合的交集、并集、差集。

代码

#include 
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
int qpow(int a, int b)
{
	int ans = 1, base = a;
	while (b)
	{
		if (b & 1)
			ans = ans * base;
		base = base * base;
		b >>= 1;
	}
	return ans;
}

void or_set(vector a, vector b) //交集
{
	cout << "交集:";
	bool f = 0; //若遍历完A仍为0,则意味着交集为空集
	for (auto it : a)
	{
		if (*lower_bound(b.begin(), b.end(), it) == it)
			cout << it << " ", f = 1; //二分查找该元素是否在b中出现过
	}
	if (!f)
		cout << "Φ";
	cout << endl;
}

void and_set(vector a, vector b) //并集
{
	cout << "并集:";
	vector c;
	bool f = 0; //判断是否为空集
	for (auto it : a)
	{
		if (*lower_bound(b.begin(), b.end(), it) == it)
			b.erase(lower_bound(b.begin(), b.end(), it)); //找到相同元素则删除防止重复
		c.push_back(it);
	}
	for (auto it : b)
	{
		c.push_back(it); // b中剩余元素即为B-A,压入c中
	}
	sort(c.begin(), c.end());
	for (auto it : c)
	{
		cout << it << " ";
		f = 1;
	}
	if (!f)
		cout << "Φ"; //判断是否为空集
	cout << endl;
}

void cha_set(vector a, vector b) //差集
{
	cout << "差集:\n";
	cout << "A-B:"; //先判断A-B
	vector d;
	bool f = 0;
	for (auto it : a)
	{
		if (*lower_bound(b.begin(), b.end(), it) != it)
			d.push_back(it); //若A中含有B中不含有则进入d数组
	}
	sort(d.begin(), d.end()); //排序
	for (auto it : d)
	{
		cout << it << " "; //只要d数组不为空,即为有元素,f变为1,否则输出空集
		f = 1;
	}
	if (!f)
		cout << "Φ";
	cout << endl;

	cout << "B-A:"; //判断B-A
	d.clear();		//清空d数组
	f = 0;			//初始化f
	for (auto it : b)
	{
		if (*lower_bound(a.begin(), a.end(), it) != it)
			d.push_back(it); //若B中含有A中不含有则进入d数组
	}
	sort(d.begin(), d.end()); //排序
	for (auto it : d)
	{
		cout << it << " "; //只要d数组不为空,即为有元素,f变为1,否则输出空集
		f = 1;
	}
	if (!f)
		cout << "Φ";
	cout << endl;
}

signed main()
{
	int temp;
	vector a, b;
	cout << "集合A:\n";
	map mp1, mp2;
	int p, q;
	cout << "集合A元素个数:";
	cin >> p;
	while (p--)
	{
		cin >> temp;
		if (mp1[temp])
			continue; //判断输入元素是否有重复,重复则不再进入数组
		else
		{
			mp1[temp] = 1; //标记该元素已进入数组
			a.push_back(temp);
		}
	}
	cout << "集合B:\n";
	cout << "集合B元素个数:";
	cin >> q;
	while (q--)
	{
		cin >> temp;
		if (mp2[temp])
			continue; //判断输入元素是否有重复,重复则不再进入数组
		else
		{
			mp2[temp] = 1; //标记该元素已进入数组
			b.push_back(temp);
		}
	}
	sort(a.begin(), a.end()); //排序,方便后续查找
	sort(b.begin(), b.end()); //排序,方便后续查找
	and_set(a, b);
	or_set(a, b);
	cha_set(a, b);
}

(2)求任意一个集合的幂集。

代码

#include 
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
int qpow(int a, int b)
{ //快速幂,保障精度
	int ans = 1, base = a;
	while (b)
	{
		if (b & 1)
			ans = ans * base;
		base = base * base;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	int temp;
	vector a, b;
	cout << "集合A:\n";
	map mp1;
	int p, q;
	cout << "集合A元素个数:";
	cin >> p;
	while (p--)
	{
		cin >> temp;
		if (mp1[temp])
			continue; //判断输入元素是否有重复,重复则不再进入数组
		else
		{
			mp1[temp] = 1; //标记该元素已进入数组
			a.push_back(temp);
		}
	}
	q = a.size(); //由于数组大小有可能因为判重改变,必须重新更新数组大小
	sort(a.begin(), a.end());
	for (int i = 0; i < qpow(2, q); i++) //类似于第一次实验C的真值表写法,用位运算判断当前位数是否该取
	{
		int k = q; //记录位数
		while (k)
		{
			if ((i >> (k - 1)) & 1)
				cout << a[k - 1] << " "; //若当前位为1则输出,否则不输出
			k--;
		}
		if (i == 0)
			cout << "Φ"; // i==0时全为0,即为空集
		cout << endl;
	}
}

(3)求任意一个集合的所有 m 元子集。

代码

#include 
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
int qpow(int a, int b)
{ //快速幂,保障精度
	int ans = 1, base = a;
	while (b)
	{
		if (b & 1)
			ans = ans * base;
		base = base * base;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	int temp;
	vector a, b;
	cout << "集合A:\n";
	map mp1;
	int p, q;
	cout << "集合A元素个数:";
	cin >> p;
	while (p--)
	{
		cin >> temp;
		if (mp1[temp])
			continue; //判断输入元素是否有重复,重复则不再进入数组
		else
		{
			mp1[temp] = 1; //标记该元素已进入数组
			a.push_back(temp);
		}
	}
	q = a.size(); //由于数组大小有可能因为判重改变,必须重新更新数组大小
	int m;
	cout << "m个数:\n";
	cin >> m; //记录m元子集中m的大小
	sort(a.begin(), a.end());
	for (int i = 0; i < qpow(2, q); i++) //几乎和B题一样
	{
		int k = q; //多判断一次子集个数是否为m,若不为m则不输出
		int j = q;
		int t = 0;
		while (k)
		{
			if ((i >> (k - 1)) & 1)
				t++;
			k--;
		}			//判断子集元素个数
		if (t == m) //判断是否为m元子集
		{
			while (j)
			{
				if ((i >> (j - 1)) & 1)
					cout << a[j - 1] << " ";
				j--;
			} //输出
			if (i == 0)
				cout << "Φ"; // i==0时全为0,即为空集
			cout << endl;
		}
	}
}

(4)求任意个元素的全排列。

代码

#include 
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;

void dfs(int start, int end, vector a)
{
	if (start >= end) //判断递归层数,若到底,输出并返回
	{
		for (auto it : a)
		{
			cout << it << " ";
		}
		cout << endl;
		return;
	}
	for (int i = start; i <= end; i++)
	{
		swap(a[start], a[i]);	//交换目标位和末位
		dfs(start + 1, end, a); //按此新顺序递归,固定i,并dfs从i+1层开始
		swap(a[start], a[i]);	//换回,防止排序顺序混乱
	}
}

signed main()
{
	int temp;
	vector a, b;
	cout << "集合A:\n";
	map mp1;
	int p, q;
	cout << "集合A元素个数:";
	cin >> p;
	while (p--)
	{
		cin >> temp;
		if (mp1[temp])
			continue; //判断输入元素是否有重复,重复则不再进入数组
		else
		{
			mp1[temp] = 1; //标记该元素已进入数组
			a.push_back(temp);
		}
	}
	q = a.size(); //由于数组大小有可能因为判重改变,必须重新更新数组大小
	sort(a.begin(), a.end());
	dfs(0, q - 1, a); // dfs全排序
}

实验3 最短路径

实验报告内容

一、实验目的

图论是一个应用十分广泛而又极其有趣的数学分支,以离散对象的二元关系结构为研究
对象。图论在网络理论、信息论、控制论和计算机科学等领域都有着广泛的应用。通过该组
实验,目的是让学生更加深刻地理解图论中的一些基本概念,并熟悉图在计算机上的表示和
运算方法,且能够解决实际图论问题。

二、实验内容

在简单图中求源点 a 到其它各点(b,c,…,j)的所有最短路径。

三、实验环境

C或C++语言编程环境实现。
DEVC++ 5.4.0
MinGW GCC 4.7.2 32-bit
-std=c++17

四、实验原理和过程(算法)

离散数学实验三则(关系元算,集合运算与操作,最短路)_第1张图片

五、程序清单

A1(dijkstra朴素版):

#include
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
const int N = 1e6 + 100;
const int MAXN = 0x3f3f3f3f3f3f3f3f;
int n, m, start;
int mp[1005][1005];
int dist[N];
int st[N];
void dijkstra() {
	memset(dist, 0x3f, sizeof dist);//初始化为无穷大
	memset(st, 0, sizeof st);//初始化
	dist[start] = 0;//将起点设置距离为0
	for (int i = 1; i < n; i++) {//由于最多n个节点,若有路,则最多走n-1次
		int var, d = MAXN;
		for (int j = 1; j <= n; j++) {
			if (!st[j] && d > dist[j]) {
				d = dist[j];
				var = j;
			}
		}//找到当前位置未确定,且距离最短的点
		st[var] = 1;//将该点确定
		for (int j = 1; j <= n; j++) {
			if (!st[j] && mp[var][j]) {
				dist[j] = min(dist[j], dist[var] + mp[var][j]);
			}//用距离最小点去更新所有还未确定点的距离
		}
	}
}
signed main() {
	IOS;
	cin >> n >> m >> start;//输入结点数,边数,以及起点
	while (m--)
	{
		int i, j, w;
		cin >> i >> j >> w;//输入边(起点,终点,权值)
		if (mp[i][j] != 0)//若有重边,根据贪心思想取权值最小的边
		{
			mp[i][j] = min(mp[i][j], w);
			mp[j][i] = min(mp[j][i], w);//无向图等效为双向边,故把起始点和终点倒置即可
		}
		else//若无重边直接赋值
		{
			mp[i][j] = w;
			mp[j][i] = w;
		}
	}
	dijkstra();
	for (int i = 1; i <= n; i++) {
		cout << i << ':';//第i个点
		if (dist[i] == MAXN) cout << -1 << endl;//若无路则输出-1
		else cout << dist[i] << endl;//有路则输出路径
	}
}

A2(dijkstra堆优化版本,复杂度更小):

#include 
#define endl '\n'
#define int long long
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
typedef pair PII;
int mp[1005][1005];
int dist[N];
int st[N];
int n, m, start;
void dijkstra()//经典dijkstra算法复杂度为O(n^2),本代码实现的是其堆优化,复杂度更加优秀为O(mlogn)
{
	memset(dist, 0x3f, sizeof dist);//初始化为无穷大
	memset(st, 0, sizeof st);//初始化
	dist[start] = 0;
	priority_queue, greater> heap;//stl默认为大根堆,这里要用小根堆
	heap.push({0, start});//将起点入堆
	while (heap.size())//当堆为空结束
	{
		auto t = heap.top();//由于小根堆特性,改点一定为路径最小的点
		heap.pop();//堆顶元素赋值后出堆
		int ver = t.second, distance = t.first;
		if (st[ver]) continue;//若改点已经确定,则一定为最短路,不需要再次判断
		st[ver] = 1;//将本次访问的点确定,以此点为起始点入堆查找最短路为下一次访问
		for (int j = 1; j <= n; j++)
		{
			if (mp[ver][j] != 0 && st[j] == 0)//若遍历的该点有路且该点未确定,则进行判断
			{
				if (dist[j] > distance + mp[ver][j])//若未确定的点已得路径小于从新结点到此点的路径,则不改变,反之则改变
				{
					dist[j] = distance + mp[ver][j];
					heap.push({dist[j], j});//路径更新,则需要重新入堆
				}
			}
		}
	}
}
signed main()
{
	cin >> n >> m >> start;//输入结点数,边数,以及起点
	while (m--)
	{
		int i, j, w;
		cin >> i >> j >> w;//输入边(起点,终点,权值)
		if (mp[i][j] != 0)//若有重边,根据贪心思想取权值最小的边
		{
			mp[i][j] = min(mp[i][j], w);
			mp[j][i] = min(mp[j][i], w);//无向图等效为双向边,故把起始点和终点倒置即可
		}
		else//若无重边直接赋值
		{
			mp[i][j] = w;
			mp[j][i] = w;
		}
	}
	dijkstra();
	for (int i = 1; i <= n; i++)//输出起点到所有点的最短路
	{
		if (dist[i] == 0x3f3f3f3f3f3f3f3f)//若为无穷大则无路
			cout << - 1 << " ";
		else
			cout << dist[i] << " ";
	}//这样输出第n行就是起点到第n个节点的最短路,若无路则输出-1
}

你可能感兴趣的:(算法,c++,图论)