This is a straightforward O(n²) implementation of the unit propagation algorithm, focusing on clarity and simplicity.
O(n²) where n is the number of clauses
The algorithm uses a simple iterative approach:
- Search Phase: Scan through all clauses to find a unit clause
- Propagation Phase: When a unit clause is found:
- Store the unit clause
- Remove all clauses containing this literal
- Remove the negated literal from remaining clauses
- Check if an empty clause is produced (contradiction)
- Repeat: Continue until no more unit clauses exist
do:
unitClauseFound = false
for each clause in clauses:
if clause has only 1 literal:
unitClauseFound = true
unitClause = that literal
store unitClause
remove clause from clauses
propagate(unitClause)
if empty clause produced:
return CONTRADICTION
break
while unitClauseFound
StorageStructure: Dynamic array that grows as needed usingrealloc()- Stores either strings (literals) or lists (clauses)
- Initial capacity: defined by
STARTING_ARRAY_SIZE
List: Singly-linked list representing literals in a clause- Each node contains a literal string
- Supports insertion at start and removal by index
Clauses: Container managing all input clauses- Uses
StorageStructureto store array ofList* - Provides
getUnitClauses()method to run the algorithm
- Uses
Literals: Container storing discovered unit clauses- Uses
StorageStructureto store array of strings - Handles output formatting in lexicographical order
- Uses
main.c # Entry point, input parsing
UnitPropagationAlgorithm.c # Core propagation logic
Clauses.c/h # Clause container and algorithm controller
Literals.c/h # Unit clause storage and output
List.c/h # Singly-linked list implementation
ListNode.c/h # List node structure
StorageStructure.c/h # Dynamic array implementation
// Read clauses line by line
while getline() succeeds:
parse line into tokens
create new List for this clause
for each token:
insert literal at start of list
add list to clauses arrayThe unitPropagation() function handles a single unit clause:
unitPropagation(clauses, unitClause):
negatedUnitClause = findNegated(unitClause)
for each clause:
for each literal in clause:
if literal == unitClause:
remove entire clause
break
else if literal == negatedUnitClause:
if clause has only 1 literal:
return EMPTY_CLAUSE // Contradiction!
else:
remove literal from clauseThe getUnitClauses() function coordinates the overall process:
getUnitClauses():
do:
unitClauseFound = false
for i = 0 to numberOfClauses:
if clause[i].numberOfLiterals == 1:
unitClauseFound = true
unitClause = copy(clause[i].head.literal)
store unitClause in literals
remove clause[i] from clauses
result = unitPropagation(clauses, unitClause)
if result == EMPTY_CLAUSE:
return EMPTY_CLAUSE
break
while unitClauseFound
return SUCCESS- Outer loop: Runs O(n) times (worst case: each clause becomes unit)
- Unit clause search: O(n) - must scan all remaining clauses
- Propagation per unit: O(n·m) where m is average literals per clause
- Visit each clause: O(n)
- Check each literal: O(m)
- Total: O(n) × O(n) = O(n²)
- Store n clauses with m literals each
- Dynamic arrays grow by reallocation
- Singly-linked lists for each clause
All dynamic memory is properly managed:
strdup()creates copies of string literals- Each structure has a
freeMemory()function pointer - Lists are freed recursively (node by node)
- Storage structures free their arrays and contents
The findNegatedUnitClause() function handles two cases:
- If literal starts with
-: remove it (e.g.,-a→a) - Otherwise: prepend
-(e.g.,a→-a)
StorageStructure provides a generic dynamic array:
- Can store either strings or lists (via
Typeenum) - Automatically grows using
realloc()when capacity reached removeFromArray()shifts elements to maintain contiguous storage
make./dpll-polynomial < input_file.inmake cleanInput:
a b
-a c
-c d
a
Execution:
-
Initial state:
- Clause 0:
{a, b} - Clause 1:
{-a, c} - Clause 2:
{-c, d} - Clause 3:
{a}← unit clause!
- Clause 0:
-
Process unit clause
a:- Store
ain literals - Remove clause 0 (contains
a) - Remove clause 3 (contains
a) - Remove
-afrom clause 1 → produces{c}(new unit!) - Remaining clauses:
{c},{-c, d}
- Store
-
Process unit clause
c:- Store
cin literals - Remove clause with
{c} - Remove
-cfrom remaining clause → produces{d}(new unit!) - Remaining clauses:
{d}
- Store
-
Process unit clause
d:- Store
din literals - Remove clause with
{d} - Remaining clauses: none
- Store
-
Output:
a c d
- Simple to understand: Clear, straightforward logic
- Easy to debug: Direct correspondence between code and algorithm
- Low memory overhead: Only essential data structures
- Correct: Handles all edge cases properly
- Slower for large inputs: O(n²) complexity becomes problematic
- Redundant scanning: Examines clauses that haven't changed
- Physical removal: Array shifting on every deletion
This implementation prioritizes clarity over performance. For large-scale SAT solving, the linear implementation (using watched literals) is significantly faster, but this version is excellent for:
- Learning the unit propagation algorithm
- Small to medium input sizes
- Situations where code simplicity matters more than performance