Unit - 7
String Matching & NP-Completeness
1. Introduction to String Matching
1.1 Problem Definition and Formal Model
The string-matching problem formalizes the task of locating all valid occurrences of a given pattern string within a larger body of text.
- Text: Modeled as an array of length .
- Pattern: Modeled as an array of length , where .
- Both and are composed of character elements chosen from a finite alphabet .
- Valid Shift: We say that pattern occurs with shift in text if: If occurs with shift in , then is called a valid shift; otherwise, it is an invalid shift.
1.2 Applications in Modern Computing
- Text Editing & Word Processors: Find and replace tools, syntax highlighting, spelling correctors.
- Search Engines & Information Retrieval: Indexing and fast retrieval of queries across internet documents.
- Bioinformatics & Computational Genomics: Locating specific nucleotide (DNA/RNA: ) or amino acid sequence motifs.
- Cybersecurity & Network Intrusion Detection: Inspecting packet payloads against known malicious signatures (e.g., Snort rules).
2. Naive String Matching Algorithm
2.1 Working Principle and Shift Exploration
The Naive String Matching algorithm finds all valid shifts by systematically testing every possible shift :
- For each shift , it performs a character-by-character comparison between and the text window .
- As soon as a mismatch is detected, it shifts the pattern forward by exactly 1 position () and restarts comparison from the first character of .
2.2 Algorithm Pseudocode
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Think of holding a small cardboard cutout with your search word (the pattern ) and sliding it along a printed sentence (the text ): You start at the beginning (). You check character by character. If every letter matches, you found a match! Then you simply slide the cutout 1 character to the right () and check again. Repeat until you reach the end of the sentence.
👣 3 Easy Steps to Remember
- Slide 1 Step at a Time: Test every starting shift from to .
- Compare Window: Check if pattern equals text slice .
- Report Match: If all characters match, announce valid shift .
🧠 Memory Trick: Slide window by 1 Compare all letters Repeat! Zero preprocessing, worst-case time , extra space .
NAIVE-STRING-MATCHER(T, P)
n = T.length
m = P.length
for s = 0 to n - m do
if P[1..m] == T[s + 1 .. s + m] then
print "Pattern occurs with shift " s
2.3 Step-by-Step Trace Example
Given:
- Text: ()
- Pattern: ()
- Possible shifts: ()
| Shift () | Text Window | Pattern | Character Comparisons | Outcome |
|---|---|---|---|---|
a c a | a a b | ('a' == 'a'), but ('c' 'a') | Mismatch at index 2 | |
c a a | a a b | ('c' 'a') | Mismatch at index 1 | |
a a b | a a b | , , ('a'=='a', 'a'=='a', 'b'=='b') | Valid Match! () | |
a b c | a a b | ('a' == 'a'), but ('b' 'a') | Mismatch at index 2 |
2.4 Complexity Analysis
- Preprocessing Time: None ().
- Worst-Case Time Complexity:
- Occurs when text and pattern consist of repeating identical characters with a mismatch at the end (e.g., , ).
- Best-Case Time Complexity: (when the very first character mismatches at every shift).
- Space Complexity: auxiliary space.
3. Rabin-Karp Algorithm
3.1 Hash-Based String Matching Concept
The Rabin-Karp algorithm speeds up string matching by comparing numerical hash values of the pattern and text windows before performing character-by-character verification:
- Treat characters as digits in a radix- positional notation (where ).
- Compute the hash value for the pattern .
- Compute the hash value for each sliding text window .
- If , perform a character verification to guard against spurious hits (hash collisions).
3.2 Radix Notation and Rolling Hash Formula
To prevent large integers from causing arithmetic overflow, calculations are performed modulo a large prime :
- Pattern hash:
- Initial text window ():
- Precomputed constant:
- Rolling Hash Formula (compute from in time):
3.3 Spurious Hits vs. Valid Matches
- Valid Match: When AND .
- Spurious Hit: When , but (a hash collision due to the modulo operation).
3.4 Algorithm Pseudocode
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Instead of comparing every single character at every step, Rabin-Karp treats strings like numbers and compares numeric fingerprints (hashes)!
Imagine looking for "26" inside "31415926535":
- Compute the hash fingerprint of
"26"(say, ). - Slide across the text: as the window moves, compute each 2-digit window's hash in time using a rolling hash (subtract the old left digit, add the new right digit).
- If the window's hash matches , you check the characters to make sure it's a genuine match and not a false alarm (spurious hit)!
👣 3 Easy Steps to Remember
- Hash the Pattern & First Window: Calculate hash for pattern , and hash for the first characters of (modulo prime ).
- Check Hash Match:
- If : do a character-by-character check. If all letters match, report shift !
- Roll to Next Window in :
- Drop the old outgoing left character, multiply by base , add the new incoming right character, and apply modulo .
- Repeat until the end of text!
🧠 Memory Trick: Fingerprint comparison Hashes match? Verify letters! Slide window: roll hash in ! Average time is , auxiliary space is .
RABIN-KARP-MATCHER(T, P, d, q)
n = T.length
m = P.length
h = d^(m - 1) mod q
p = 0
t0 = 0
// Preprocessing
for i = 1 to m do
p = (d * p + P[i]) mod q
t0 = (d * t0 + T[i]) mod q
// Matching
for s = 0 to n - m do
if p == ts then
if P[1..m] == T[s + 1 .. s + m] then
print "Pattern occurs with shift " s
if s < n - m then
ts+1 = (d * (ts - T[s + 1] * h) + T[s + m + 1]) mod q
3.5 Step-by-Step Trace Example
Given:
- Text: ()
- Pattern: ()
- Base , Prime
- Pattern hash:
Sliding Window Hash Calculations:
| Shift () | Text Substring | Value | Value () | Status () | Verification Check | Result |
|---|---|---|---|---|---|---|
| 0 | 3 1 | 31 | Skipped | No match | ||
| 1 | 1 4 | 14 | Skipped | No match | ||
| 2 | 4 1 | 41 | Skipped | No match | ||
| 3 | 1 5 | 15 | "15" "26" | Spurious Hit | ||
| 4 | 5 9 | 59 | "59" "26" | Spurious Hit | ||
| 5 | 9 2 | 92 | "92" "26" | Spurious Hit | ||
| 6 | 2 6 | 26 | "26" "26" | Valid Match! () | ||
| 7 | 6 5 | 65 | Skipped | No match | ||
| 8 | 5 3 | 53 | Skipped | No match | ||
| 9 | 3 5 | 35 | Skipped | No match |
3.6 Complexity and Performance Characteristics
- Preprocessing Time:
- Worst-Case Time Complexity:
- Occurs when all windows produce hash matches and require full string comparisons (e.g., , ).
- Average / Expected Running Time: (with suitable choice of prime ).
- Space Complexity: auxiliary memory.
4. String Matching with Finite Automata (FA)
4.1 5-Tuple Automaton Model
A string-matching automaton is a deterministic finite state machine defined by the 5-tuple:
- : Set of states, where state denotes that a prefix of length of has been matched.
- : Initial state (no characters matched yet).
- : Accepting state (indicating a complete match of ).
- : Finite input alphabet.
- : State transition function.
4.2 Suffixes and Prefix-Suffix Matching Concept
- Suffix Notation: indicates that string is a suffix of string .
- Prefix Function : The length of the longest prefix of that is also a suffix of :
- The state transition function is defined as:
4.3 Transition Function Computation ()
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
A Finite Automaton works like a board game with numbered circles (states ): State means: "We have matched the first letters of the pattern so far." The transition function is the rulebook: "If we are currently at state and the next character in the book is '', which state should we jump to?"
- If '' is the expected next letter, we jump forward ().
- If '' is a mismatch, we don't start from scratch! We jump back to the longest prefix of the pattern that still matches what we just read.
👣 3 Easy Steps to Remember
- Loop Through All States & Letters: For every state and every alphabet letter .
- Find the Longest Common Overlap: Start at candidate length and count down until prefix matches the end of string .
- Save Next State: Set .
🧠 Memory Trick: State = letters matched For next char , jump to longest prefix that fits suffix of ! Preprocessing time: .
COMPUTE-TRANSITION-FUNCTION(P, Sigma)
m = P.length
for q = 0 to m do
for each character a in Sigma do
k = min(m + 1, q + 2)
repeat
k = k - 1
until P_k is a suffix of P_q a
delta(q, a) = k
return delta
4.4 Transition Table Walkthrough: Pattern ababaca
Given () over :
| State () | Match String | Input 'a' | Input 'b' | Input 'c' |
|---|---|---|---|---|
| 0 | 1 () | 0 | 0 | |
| 1 | a | 1 () | 2 () | 0 |
| 2 | ab | 3 () | 0 | 0 |
| 3 | aba | 1 () | 4 () | 0 |
| 4 | abab | 5 () | 0 | 0 |
| 5 | ababa | 1 () | 4 () | 6 () |
| 6 | ababac | 7 () | 0 | 0 |
| 7 | ababaca | 1 () | 2 () | 0 |
4.5 Finite Automaton Matcher Algorithm and Trace
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Once the transition table has been built, scanning through text is lightning-fast:
You start at State .
You read the text character by character from left to right, and make exactly one table lookup per character:
q = delta(q, text[i])
You never step backward in the text!
Whenever your current state reaches (the length of the pattern), it means you've successfully read all characters of the pattern in order! You shout: "Match found at position !"
👣 3 Easy Steps to Remember
- Initialize State: Set current state .
- Scan Text Linearly:
- For each character from to :
- Look up the new state: .
- Check for Match:
- If : pattern found with shift !
🧠 Memory Trick: Start at 0 One state transition per letter: Reach state ? Pattern matched! Strictly linear matching time: .
FINITE-AUTOMATON-MATCHER(T, delta, m)
n = T.length
q = 0
for i = 1 to n do
q = delta(q, T[i])
if q == m then
print "Pattern occurs with shift " (i - m)
Trace on Text :
| Step () | Text Char | State Transition () | New State () | Condition () |
|---|---|---|---|---|
| 1 | 'a' | 1 | - | |
| 2 | 'b' | 2 | - | |
| 3 | 'a' | 3 | - | |
| 4 | 'b' | 4 | - | |
| 5 | 'a' | 5 | - | |
| 6 | 'b' | 4 | (Reverts to state 4) | |
| 7 | 'a' | 5 | - | |
| 8 | 'c' | 6 | - | |
| 9 | 'a' | 7 | Match at shift ! | |
| 10 | 'b' | 2 | - | |
| 11 | 'a' | 3 | - |
4.6 Complexity Analysis
- Preprocessing Time: (can be optimized to ).
- Matching Time: (strictly linear; exactly one state transition per character).
- Space Complexity: to store the transition table .
5. Knuth-Morris-Pratt (KMP) Algorithm
5.1 Motivation: Eliminating Text Backtracking
The Knuth-Morris-Pratt (KMP) algorithm eliminates the need to backtrack the text pointer :
- When a mismatch occurs after matching characters, the pattern itself contains information about where the next possible match could begin.
- It precomputes a Prefix Function (or LPS Array - Longest Proper Prefix which is also a Suffix).
5.2 Proper Prefixes, Proper Suffixes, and the Function
- Proper Prefix: A prefix of string that is strictly shorter than ().
- For
"Snape":{"S", "Sn", "Sna", "Snap"}.
- For
- Proper Suffix: A suffix of string that is strictly shorter than ().
- For
"Hagrid":{"agrid", "grid", "rid", "id", "d"}.
- For
- Prefix Function : stores the length of the longest proper prefix of that is also a proper suffix of .
5.3 Prefix Function Computation Algorithm
5.3 Prefix Function Computation Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
The secret behind KMP is the Prefix Function (also called the LPS Array — Longest Prefix which is also a Suffix).
It asks a very simple question for every slice of the pattern:
"Does the beginning of this slice match its end?"
For example, in "ABA":
- The prefix
"A"is identical to the suffix"A"(length 1!). If you match"ABA"and the next character fails, you don't start over from scratch! You know the tail ends with"A", so you can immediately resume comparing from the second letter!
👣 3 Easy Steps to Remember
- Start at Zero: Set (a 1-character string has no proper prefix). Track current matched length .
- Loop from 2 to :
- Mismatch? Fall back using previous knowledge: while and , jump back .
- Match! If , expand the matching border: .
- Record Length: Save the longest match length for position : .
🧠 Memory Trick: LPS Array Match? Mismatch? Fall back Save ! Preprocessing takes time.
COMPUTE-PREFIX-FUNCTION(P)
m = P.length
pi[1] = 0
k = 0
for q = 2 to m do
while k > 0 and P[k + 1] != P[q] do
k = pi[k]
if P[k + 1] == P[q] then
k = k + 1
pi[q] = k
return pi
5.4 Step-by-Step Prefix Function Traces
5.4.1 Example 1: ababaca
| Sub-pattern | Proper Prefixes | Proper Suffixes | Longest Common | ||
|---|---|---|---|---|---|
| 1 | a | None | 0 | ||
| 2 | ab | a | b | None | 0 |
| 3 | aba | a, ab | a, ba | a | 1 |
| 4 | abab | a, ab, aba | b, ab, bab | ab | 2 |
| 5 | ababa | a, ab, aba, abab | a, ba, aba, baba | aba | 3 |
| 6 | ababac | ... | ... | None | 0 |
| 7 | ababaca | ... | ... | a | 1 |
5.4.2 Example 2: acacabacacabacacac (Length 18)
| Index | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Pattern | a | c | a | c | a | b | a | c | a | c | a | b | a | c | a | c | a | c |
| 0 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 4 |
5.4.3 Example 3: ACACAGT
| Index | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Pattern | A | C | A | C | A | G | T |
| 0 | 0 | 1 | 2 | 3 | 0 | 0 |
5.5 KMP Matcher Algorithm and Text Search Trace
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
In naive string matching, every time a mismatch happens, you yank the pointer back and re-read characters you already verified. That's a massive waste of time! KMP never moves backward in the text ( only moves strictly forward )! If you have matched characters and the next character clashes: You look up to see how many characters from the start of your pattern are already aligned with what you just read. You slide the pattern forward instantly without backtracking, and resume checking!
👣 3 Easy Steps to Remember
- Precompute the Table: Compute the prefix table . Start match count .
- Scan Text Linearly ():
- Mismatch? Slide pattern forward: while and , fall back .
- Match! If , advance .
- Full Pattern Matched ():
- Report valid shift at position !
- Reset to keep searching for overlapping matches.
🧠 Memory Trick: Text pointer never backs up! Mismatch? Fall back . Match? Advance . Found all ? Print shift! Strictly linear total time: .
KMP-MATCHER(T, P)
n = T.length
m = P.length
pi = COMPUTE-PREFIX-FUNCTION(P)
q = 0
for i = 1 to n do
while q > 0 and P[q + 1] != T[i] do
q = pi[q]
if P[q + 1] == T[i] then
q = q + 1
if q == m then
print "Pattern occurs with shift " (i - m)
q = pi[q]
Walkthrough:
- Text: , Pattern: ().
- Match reaches (
"aca"), next char in text is't'while . - Fallback: . It resumes comparing from against text char
't', avoiding re-examining matched prefix'a'. - Shift continues smoothly until valid match is reported at shift .
5.6 Complexity Analysis
- Preprocessing Time: to compute .
- Matching Time: (inner
whileloop amortizes to because increases at most times). - Total Time Complexity:
- Space Complexity: for table .
6. String Matching Algorithms (Formal Procedures)
6.1 Naive String Matching Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Line up the pattern against the text starting at index . Use an inner loop to check letter-by-letter: if character matches, advance to character . If your inner pointer makes it all the way past the end of the pattern (), every single letter matched! Print shift . Then move the starting shift forward by 1 and repeat!
👣 3 Easy Steps to Remember
- Slide Shift by Shift: Outer loop tests each starting position .
- Inner Character Walk: Start . While characters match () and , advance .
- Verify Match: If , you matched the whole pattern! Output shift .
🧠 Memory Trick: Outer loop slides Inner while-loop matches characters If , pattern found!
Algorithm NaiveStringMatcher(T, P)
// Finds all occurrences of pattern P in text T by brute-force character checking
// Input: Text string T of length n, pattern string P of length m (m <= n)
// Output: Prints all valid shift positions s where P matches T[s+1 .. s+m]
{
n <- length(T);
m <- length(P);
for s <- 0 to n - m do
{
j <- 1;
while (j <= m and T[s + j] = P[j]) do
j <- j + 1;
if (j > m) then
print "Pattern occurs with shift", s;
}
}
6.2 Rabin-Karp Matching Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
This is the complete, formal procedure for the Rabin-Karp algorithm:
- It turns pattern and the first text window into numerical hash values ( and ).
- It slides the window across the text:
- When hashes match (), it loops through characters to filter out false alarms (spurious hits).
- It computes the next window's hash in time by subtracting the outgoing left digit and adding the incoming right digit (adding if negative).
👣 3 Easy Steps to Remember
- Preprocessing (Calculate Fingerprints):
- Calculate constant .
- Compute initial hash (pattern) and (first window of ).
- Match Check:
- For each shift :
- If : compare characters. If identical, print valid shift !
- Roll the Hash to Next Window:
- .
- If , wrap around: .
🧠 Memory Trick: Hashes equal? Check letters! Next window: roll hash in (subtract left, multiply base , add right)! Average time: .
Algorithm RabinKarpMatcher(T, P, d, q)
// Finds occurrences of pattern P in text T using rolling hash fingerprints
// Input: Text T of length n, pattern P of length m, radix d, prime modulus q
// Output: Prints all valid shift positions where P occurs in T
{
n <- length(T);
m <- length(P);
h <- (d^(m - 1)) mod q;
p <- 0;
t <- 0;
// Preprocessing: compute hash value of P and first window of T
for i <- 1 to m do
{
p <- (d * p + P[i]) mod q;
t <- (d * t + T[i]) mod q;
}
// Matching: slide pattern over text
for s <- 0 to n - m do
{
if (p = t) then
{
// Verify character by character to eliminate spurious hits
match <- true;
for j <- 1 to m do
{
if (P[j] != T[s + j]) then
{
match <- false;
break;
}
}
if match then
print "Pattern occurs with shift", s;
}
// Compute rolling hash for next window T[s+1 .. s+m]
if (s < n - m) then
{
t <- (d * (t - T[s + 1] * h) + T[s + m + 1]) mod q;
if (t < 0) then
t <- t + q;
}
}
}
6.3 Knuth-Morris-Pratt (KMP) Matching Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
This is the complete, formal procedure for the Knuth-Morris-Pratt (KMP) matcher:
ComputePrefixFunction(P)builds the auxiliary table (the LPS array), which figures out if the beginning of any pattern prefix matches its own tail.KMPMatcher(T, P)reads text strictly from left to right with pointer :- On a mismatch: It looks up and shifts the pattern forward without rewinding .
- On a match: It advances .
- On a full match (): It announces the match at shift and uses to seamlessly hunt for overlapping occurrences!
👣 3 Easy Steps to Remember
- Compute Table :
- Loop from to . Fall back along on mismatch (). Advance on match (). Store .
- Scan Text Forward ():
- If mismatch while : fall back .
- If characters match: increment .
- Record Shifts:
- When : report valid shift , then reset to find overlapping matches.
🧠 Memory Trick: Text pointer never retreats! Mismatch? . Match? . Reach ? Shift reported! Total time: .
Algorithm KMPMatcher(T, P)
// Finds all occurrences of pattern P in text T in linear time without backtracking
// Input: Text string T of length n, pattern string P of length m
// Output: Prints all valid shift positions where P occurs in T
{
n <- length(T);
m <- length(P);
pi <- ComputePrefixFunction(P);
q <- 0; // Number of characters matched
for i <- 1 to n do
{
while (q > 0 and P[q + 1] != T[i]) do
q <- pi[q]; // Next character does not match, backtrack along pi
if (P[q + 1] = T[i]) then
q <- q + 1; // Next character matches
if (q = m) then
{
print "Pattern occurs with shift", i - m;
q <- pi[q]; // Look for next match
}
}
}
Algorithm ComputePrefixFunction(P)
// Computes auxiliary prefix function pi for pattern P
// Input: Pattern string P of length m
// Output: Array pi[1..m] storing length of longest proper prefix of P[1..q] that is also a suffix
{
m <- length(P);
pi[1] <- 0;
k <- 0;
for q <- 2 to m do
{
while (k > 0 and P[k + 1] != P[q]) do
k <- pi[k];
if (P[k + 1] = P[q]) then
k <- k + 1;
pi[q] <- k;
}
return pi;
}
7. Asymptotic Notation & Complexity Foundations
7.1 Performance Analysis Definitions
Asymptotic notations describe the behavior of algorithm running time as the input size tends toward infinity.
7.2 Big-O (), Big- (), and Big- ()
| Notation | Formal Definition | Mathematical Meaning | Nature of Bound |
|---|---|---|---|
| Big-O () | Asymptotic Upper Bound | ||
| Big- () | Asymptotic Lower Bound | ||
| Big- () | Asymptotically Tight Bound |
7.3 Exponential Complexity and Infeasibility
Algorithms running in or time become computationally intractable even for moderate input sizes:
- Example: Naive recursive calculation of Fibonacci numbers ().
- A computer computing
fib(50)recursively would require over operations, stalling execution for days, whereas dynamic programming computes it in operations within microseconds.
8. Complexity Classes: P, NP, NP-Complete, and NP-Hard
8.1 Class P (Deterministic Polynomial Time)
- Definition: The class of decision problems solvable in polynomial time by a deterministic Turing Machine (deterministic algorithm).
- Running time is bounded by for some fixed constant .
- Examples:
- Sorting algorithms ()
- Minimum Spanning Trees ()
- Shortest Path / Dijkstra ( or )
- Fractional Knapsack ()
8.2 Class NP (Non-Deterministic Polynomial Time)
- Definition: The class of decision problems whose candidate solution (certificate/witness) can be verified in polynomial time by a deterministic algorithm.
- Problems in NP are verifiable quickly, even if finding the solution requires exponential time via exhaustive search.
- Key distinction: Finding a solution vs. verifying a proposed solution.
8.3 Class NP-Complete
A decision problem is NP-Complete if:
- (solutions are verifiable in polynomial time).
- Every problem is polynomial-time reducible to ().
- Significance: NP-Complete problems are the hardest problems in NP. If any single NP-Complete problem is solved in polynomial time, then .
- No polynomial-time algorithm is known for any NP-Complete problem.
- Examples: Circuit-SAT, 3-SAT, Vertex Cover, Hamiltonian Cycle, 0/1 Knapsack (Decision).
8.4 Class NP-Hard
A problem is NP-Hard if every problem can be reduced to in polynomial time ():
- NP-Hard problems are at least as hard as NP-Complete problems.
- Key distinction: NP-Hard problems do not need to be in NP (they do not need to be decision problems, nor verifiable in polynomial time).
- Examples:
- Traveling Salesperson Problem (Optimization)
- Turing Halting Problem (Undecidable, hence not in NP, but NP-Hard)
8.5 P vs. NP Classification and Euler Diagram
- Fundamental Theorem:
- If : and are disjoint subsets of .
9. Polynomial-Time Reductions
9.1 Concept of Problem Reduction ()
Let and be two decision problems. A polynomial-time reduction () is a deterministic algorithm that transforms any instance of problem into an instance of problem such that:
- The transformation executes in polynomial time ().
- The outputs are identical: .
9.2 Theoretical Implications
- Proving Tractability ("Easiness"): If and , then . We use the efficiency of to solve .
- Proving Intractability ("Hardness"): If is known to be NP-Hard and , then is also NP-Hard.
10. Classic NP Problems & Hamiltonian Cycles
10.1 Hamiltonian Cycle Problem
- Instance: A connected directed or undirected graph with vertices.
- Hamiltonian Cycle: A round-trip closed loop along edges that visits every vertex in exactly once and returns to the initial vertex.
- Decision Formulation: "Does graph contain a Hamiltonian cycle?"
10.2 Polynomial Verification
Given a proposed sequence of vertices :
- Verify .
- Verify all intermediate vertices are distinct.
- Verify that edge for all .
- Total verification time: operations Hamiltonian Cycle .
10.3 Backtracking Procedure for Finding Hamiltonian Cycles
- Define a solution vector where represents the -th visited vertex.
- Initialize (without loss of generality, start at vertex 1).
- For position from to :
- Check if an edge exists from to .
- Check if has not been visited earlier.
- When , check if an edge exists from back to . If so, a valid cycle is found; otherwise, backtrack.
10.4 Example Graph Trace
Valid Hamiltonian Cycles in :
10.5 Traveling Salesperson Problem (TSP)
- Problem: Given a complete weighted graph , find a Hamiltonian cycle with minimum total edge weight.
- NP-Hard Classification: Because finding whether a cycle exists is NP-Complete, finding the minimum-weight cycle across all permutations is NP-Hard.
11. Decision Problems vs. Optimization Problems
11.1 Fundamental Differences
| Dimension | Decision Problem | Optimization Problem |
|---|---|---|
| Output Type | Binary answer: "YES" or "NO" | An optimal structure or minimum/maximum value |
| Formulation | Determines whether a solution satisfies a bound | Finds a solution that achieves the optimal bound |
| Complexity Class | Belongs directly to , , or | Belongs to (if corresponding decision problem is NP-Complete) |
11.2 Converting Optimization Problems to Decision Problems
Every optimization problem can be cast as a decision problem by introducing a numerical threshold parameter :
| Problem | Optimization Version | Decision Version |
|---|---|---|
| Minimum Spanning Tree | Find a spanning tree of with minimum total weight. | Does there exist a spanning tree of with total weight ? |
| Longest Common Subsequence | Find the maximum length of a common subsequence. | Does there exist a common subsequence of length ? |
| Traveling Salesperson | Find a tour visiting every city once with minimum total length. | Does there exist a tour of length ? |
| 0/1 Knapsack | Maximize subject to . | Does there exist a subset of items with weight and value ? |
11.3 Hierarchy of Computational Hardness
- An optimization problem is at least as difficult as its decision counterpart.
- If the decision version is computationally hard (NP-Complete), the optimization version is automatically hard (NP-Hard).
- An optimization problem can be solved by repeatedly querying its decision version using Binary Search over the bound .
12. Unit 7 Summary and Quick Revision Cheat Sheet
12.1 String Matching Algorithms Master Table
| Algorithm | Preprocessing Time | Matching Time | Total Time | Auxiliary Space | Key Advantage / Mechanism |
|---|---|---|---|---|---|
| Naive | Simple implementation, no extra memory | ||||
| Rabin-Karp | (avg) | Modular rolling hash, ideal for multi-pattern search | |||
| Finite Automata | Strictly linear matching, no text backtracking | ||||
| KMP | Prefix function , zero text backtracking |
12.2 Complexity Classes Reference Card
| Class | Definition | Verification Time | Solution Finding Time | Example Problems |
|---|---|---|---|---|
| P | Solvable in polynomial time deterministically | Polynomial () | Polynomial () | Merge Sort, Dijkstra, Prim, Kruskal |
| NP | Verifiable in polynomial time | Polynomial () | Exponential () | Hamiltonian Cycle, Subset Sum, 3-SAT |
| NP-Complete | Hardest problems in NP; all NP reduce to them | Polynomial () | Unknown () | Circuit-SAT, 3-SAT, Vertex Cover |
| NP-Hard | At least as hard as NP-Complete; not necessarily in NP | Not necessarily poly | Exponential / Undecidable | TSP Optimization, Halting Problem |
12.3 Key Formulas Cheat Sheet
- Rabin-Karp Rolling Hash:
- KMP Prefix Function:
- Asymptotic Inclusion:
- Class Inclusion: