前段时间才做了离散实验的实验报告,为防止在本地遗失,所以上传到CSDN上一份,欢迎大家一起学习
二、实验内容熟悉掌握命题逻辑中的联结词、真值表、主范式等,进一步能用他们来解决实际问题。
三、实验环境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 << "errorn"; 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; set operator_set = { '(', ')', '=', '^', '!', '&', '|' };//操作集 map level; //优先级 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()); }//未到底端继续递归往下查找子树计算结果 } }; stack num_stk; stack op_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();//清空栈 vector v; 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; } } //判断输入符号是否合法 set st; map mp; 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()) { vector temp; 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; }
二、实验内容集合论是一切数学的基础,也是计算机科学不可或缺的,在数据结构、数据库理论、开关理论、自动机理论和可计算理论等领域都有广泛的应用。集合的运算规则是集合论中的重要内容。通过该组实验,目的是让学生更加深刻地理解集合的概念和性质,并掌握集合的运算规则等。
三、实验环境(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全排序 }
二、实验内容图论是一个应用十分广泛而又极其有趣的数学分支,以离散对象的二元关系结构为研究
对象。图论在网络理论、信息论、控制论和计算机科学等领域都有着广泛的应用。通过该组
实验,目的是让学生更加深刻地理解图论中的一些基本概念,并熟悉图在计算机上的表示和
运算方法,且能够解决实际图论问题。
三、实验环境在简单图中求源点 a 到其它各点(b,c,…,j)的所有最短路径。
四、实验原理和过程(算法)C或C++语言编程环境实现。
DEVC++ 5.4.0
MinGW GCC 4.7.2 32-bit
-std=c++17
五、程序清单
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 }



