-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQ7.cpp
More file actions
205 lines (182 loc) · 7.2 KB
/
Q7.cpp
File metadata and controls
205 lines (182 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
* @file Q7.cpp
* @brief Implementation file for validating and analyzing a tree in Conjunctive Normal Form (CNF).
*
* This file contains functions to check if a given expression tree conforms to the
* structure of CNF. It also counts the number of valid (satisfiable) clauses
* and invalid or tautological clauses within the expression.
*/
#include "Q2.h" // For TreeNode definition
#include "Q7.h"
#include <set>
#include <string>
#include <utility> // For std::pair and std::make_pair
using std::pair;
using std::string;
using std::make_pair;
/**
* @brief Checks if a given TreeNode represents a literal.
*
* A literal is defined as either an atomic proposition (e.g., "p", "q") or its
* negation (e.g., "~p"). This function identifies atoms by checking if they are not operators,
* and negated atoms by checking for a '~' node whose right child is an atom.
*
* @param node A pointer to the TreeNode to check.
* @return true if the node is a literal.
* @return false otherwise.
*/
bool isLiteral(TreeNode* node) {
if (!node) return false;
// Check if it's an atom (a variable, not an operator)
if (node->data != "~" && node->data != "*" && node->data != "+" && node->data != ">") {
return true; // Atom
}
// Check if it's a negated atom (~A), where A is not an operator
if (node->data == "~" && node->right &&
(node->right->data != "~" && node->right->data != "*" &&
node->right->data != "+" && node->right->data != ">")) {
return true; // Negated atom
}
return false;
}
/**
* @brief Recursively collects all literals from a clause subtree.
*
* This function traverses a tree representing a single clause (a disjunction of literals)
* and separates the atomic variables into two sets: one for positive literals (e.g., "p")
* and one for negative literals (e.g., the "p" from "~p").
*
* @param node A pointer to the root of the clause subtree.
* @param positiveAtoms A reference to a set where the names of positive atoms will be stored.
* @param negativeAtoms A reference to a set where the names of negated atoms will be stored.
*/
void collectLiterals(TreeNode* node, std::set<string>& positiveAtoms, std::set<string>& negativeAtoms) {
if (!node) return;
// If it's a negated atom (~A)
if (node->data == "~" && node->right && isLiteral(node)) {
negativeAtoms.insert(node->right->data);
return;
}
// If it's a positive atom (A, B, C, etc.)
if (isLiteral(node) && node->data != "~") {
positiveAtoms.insert(node->data);
return;
}
// If it's a disjunction (+), recursively collect from both children.
if (node->data == "+") {
collectLiterals(node->left, positiveAtoms, negativeAtoms);
collectLiterals(node->right, positiveAtoms, negativeAtoms);
}
}
/**
* @brief Determines if a clause is satisfiable by checking for tautology.
*
* A clause is a tautology (and thus trivially satisfiable, but often considered invalid in CNF contexts)
* if it contains complementary literals, i.e., both an atom and its negation (e.g., `p + ~p`).
* This function returns `false` if the clause is a tautology and `true` otherwise.
*
* @param clause A pointer to the root of the clause subtree.
* @return true if the clause is satisfiable (not a tautology).
* @return false if the clause is a tautology (contains complementary literals).
*/
bool isSatisfiableClause(TreeNode* clause) {
std::set<string> positiveAtoms;
std::set<string> negativeAtoms;
collectLiterals(clause, positiveAtoms, negativeAtoms);
// Check if any atom appears in both sets.
// If so, the clause contains (A + ~A), which is a tautology.
for (const string& atom : positiveAtoms) {
if (negativeAtoms.count(atom) > 0) {
return false; // Tautology found
}
}
return true; // Satisfiable
}
/**
* @brief Recursively checks if a subtree has the structure of a valid clause.
*
* A valid clause is defined as a literal or a disjunction ('+') of other valid clauses.
* It must not contain conjunctions ('*') or implications ('>').
*
* @param node A pointer to the root of the subtree to check.
* @return true if the subtree has a valid clause structure.
* @return false otherwise.
*/
bool isClause(TreeNode* node) {
if (!node) {
return true; // An empty child is acceptable.
}
if (isLiteral(node)) {
return true; // Base case: a literal is a valid clause.
}
// Recursive step: an OR node is a clause if its children are clauses.
if (node->data == "+") {
return isClause(node->left) && isClause(node->right);
}
// Any other operator (like '*') is not allowed within a clause.
return false;
}
/**
* @brief Recursively traverses a CNF tree to count valid and invalid clauses.
*
* This function walks the tree. When it finds a conjunction ('*'), it continues down
* both branches. When it encounters a potential clause (a literal or a disjunction root),
* it validates its structure and checks if it's a tautology, then increments the
* appropriate counter.
*
* @param root A pointer to the root of the CNF expression tree.
* @param validNum A reference to an integer to count valid, satisfiable clauses.
* @param invalidNum A reference to an integer to count invalid/tautological clauses.
*/
void countClauses(TreeNode* root, int &validNum, int &invalidNum) {
if (!root) return;
// If it's a conjunction, recurse on both sides (the conjuncts).
if (root->data == "*") {
countClauses(root->left, validNum, invalidNum);
countClauses(root->right, validNum, invalidNum);
return;
}
// At this point, the root of the subtree should be a clause.
// We check if it has the correct structure (is a disjunction of literals).
if (isClause(root)) {
// If the structure is correct, check if it's a tautology.
if (isSatisfiableClause(root)) {
validNum++; // Valid, satisfiable clause.
} else {
invalidNum++; // Tautology (e.g., p + ~p).
}
} else {
// If the structure is not a valid clause (e.g., contains '*').
invalidNum++;
}
}
/**
* @brief Checks if a tree is in CNF and counts its valid and invalid clauses.
*
* This function serves as the entry point for CNF validation. It initializes counters
* and calls a recursive helper function to traverse the tree and perform the counts.
*
* @param root A pointer to the root of the expression tree to be checked.
* @return pair<int, int> A pair where the first element is the number of invalid/tautological
* clauses and the second element is the number of valid, satisfiable clauses.
*/
pair<int, int> isCNF(TreeNode* root) {
int validNum = 0;
int invalidNum = 0;
// A single node that is not a conjunction is treated as a single clause.
if (root && root->data != "*") {
if (isClause(root)) {
if (isSatisfiableClause(root)) {
validNum++;
} else {
invalidNum++;
}
} else {
invalidNum++;
}
} else {
// If the root is a conjunction or null, start the recursive counting.
countClauses(root, validNum, invalidNum);
}
return make_pair(invalidNum, validNum);
}