← Math|80 of 100
Discrete Mathematics

Applications in Computer Science

See how discrete math powers algorithms, cryptography, coding theory, and scheduling.

📂 Applications📖 Lesson 80 of 100🎓 Free Course

Advertisement

Applications in Computer Science

â„šī¸ Why It Matters

Discrete mathematics is the language of computer science. Every algorithm, data structure, and computational system relies on discrete mathematical foundations. This module connects abstract concepts to real-world applications: cryptography secures communications, coding theory prevents data corruption, scheduling optimizes resource allocation, and networking protocols ensure reliable delivery. Understanding these applications is essential for software engineers, security professionals, and AI practitioners.


Algorithm Analysis

DfTime Complexity

Time complexity measures how the number of operations grows with input size nn. It is expressed using Big-O notation: f(n)=O(g(n))f(n) = O(g(n)) means ff grows no faster than gg asymptotically.

DfSpace Complexity

Space complexity measures the total memory space required by an algorithm as a function of input size.

ThMaster Theorem for Divide-and-Conquer

For T(n)=aT(n/b)+O(nd)T(n) = aT(n/b) + O(n^d): if d<log⁥bad < \log_b a then T(n)=O(nlog⁥ba)T(n) = O(n^{\log_b a}); if d=log⁥bad = \log_b a then T(n)=O(ndlog⁥n)T(n) = O(n^d \log n); if d>log⁥bad > \log_b a then T(n)=O(nd)T(n) = O(n^d).

import time
import random

def measure_time(func, *args, runs=100):
    total = 0
    for _ in range(runs):
        start = time.time()
        func(*args)
        total += time.time() - start
    return total / runs

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
    return arr

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

sizes = [100, 1000, 5000]
for n in sizes:
    arr = random.sample(range(n * 10), n)
    t_bubble = measure_time(bubble_sort, arr.copy(), runs=10)
    t_merge = measure_time(merge_sort, arr.copy(), runs=10)
    print(f"n={n}: Bubble={t_bubble:.4f}s, Merge={t_merge:.4f}s")

Cryptography

DfRSA Encryption

RSA uses the difficulty of factoring n=pqn = pq (product of two large primes). Public key: (n,e)(n, e). Private key: d=e−1(modĪ•(n))d = e^{-1} \pmod{\phi(n)}. Encrypt: c=me(modn)c = m^e \pmod{n}. Decrypt: m=cd(modn)m = c^d \pmod{n}.

DfDiffie-Hellman Key Exchange

Two parties agree on public prime pp and generator gg. Alice sends ga(modp)g^a \pmod{p}, Bob sends gb(modp)g^b \pmod{p}. Shared secret: gab(modp)g^{ab} \pmod{p}.

DfHash Function

A hash function H:{0,1}∗→{0,1}nH: \{0,1\}^* \to \{0,1\}^n maps arbitrary input to fixed-size output. Properties: preimage resistance, second preimage resistance, collision resistance.

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x, y = extended_gcd(b % a, a)
    return g, y - (b // a) * x, x

def mod_inverse(a, m):
    g, x, _ = extended_gcd(a % m, m)
    if g != 1:
        raise ValueError("No inverse")
    return x % m

def rsa_demo():
    p, q = 61, 53
    n = p * q
    phi = (p - 1) * (q - 1)
    e = 17
    d = mod_inverse(e, phi)

    message = 65
    encrypted = pow(message, e, n)
    decrypted = pow(encrypted, d, n)

    print(f"Public key: ({n}, {e})")
    print(f"Private key: {d}")
    print(f"Original: {message}")
    print(f"Encrypted: {encrypted}")
    print(f"Decrypted: {decrypted}")

rsa_demo()

Coding Theory

DfError-Detecting Code

A code adds redundancy to data so errors can be detected. The Hamming distance between two codewords is the number of positions where they differ.

DfHamming Code

Hamming code adds parity bits to detect and correct single-bit errors. For rr parity bits, it protects 2r−r−12^r - r - 1 data bits.

def hamming_encode(data_bits):
    m = len(data_bits)
    r = 0
    while (1 << r) < m + r + 1:
        r += 1
    total = m + r
    code = [0] * (total + 1)

    j = 0
    for i in range(1, total + 1):
        if i & (i - 1) != 0:
            code[i] = int(data_bits[j])
            j += 1

    for i in range(r):
        pos = 1 << i
        parity = 0
        for j in range(1, total + 1):
            if j & pos:
                parity ^= code[j]
        code[pos] = parity

    return code[1:]

def hamming_count_errors(received):
    r = 0
    while (1 << r) < len(received):
        r += 1
    error_pos = 0
    for i in range(r):
        pos = 1 << i
        parity = 0
        for j in range(len(received)):
            if (j + 1) & pos:
                parity ^= received[j]
        if parity:
            error_pos += pos
    return error_pos

data = [1, 0, 1, 1]
encoded = hamming_encode(data)
print(f"Data: {data}")
print(f"Encoded: {encoded}")

received = encoded.copy()
received[3] = 1 - received[3]
error = hamming_count_errors(received)
print(f"Error position: {error}")

Scheduling

DfJob Scheduling

Given jobs with durations and deadlines, schedule them to minimize lateness or maximize throughput. Greedy algorithms using sorting by earliest deadline provide optimal solutions for single-machine scheduling.

DfTopological Sort

For tasks with dependencies, topological sort produces a valid execution order. Used in build systems, task managers, and project planning.

from collections import defaultdict, deque

def topological_sort_dfs(n, edges):
    graph = defaultdict(list)
    in_degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1

    order = []
    visited = set()

    def dfs(node):
        visited.add(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                dfs(neighbor)
        order.append(node)

    for i in range(n):
        if i not in visited:
            dfs(i)

    return order[::-1]

def topological_sort_kahn(n, edges):
    graph = defaultdict(list)
    in_degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1

    queue = deque([i for i in range(n) if in_degree[i] == 0])
    order = []

    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    return order if len(order) == n else []

edges = [(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)]
print("DFS order:", topological_sort_dfs(5, edges))
print("Kahn order:", topological_sort_kahn(5, edges))

Networking

DfShortest Path

Dijkstra's algorithm finds shortest paths from a source to all vertices in a weighted graph with non-negative weights. Time complexity: O((V+E)log⁥V)O((V + E) \log V) with a priority queue.

DfNetwork Flow

The max-flow min-cut theorem states the maximum flow equals the minimum cut capacity. Used in transportation, communication, and resource allocation.

import heapq

def dijkstra(graph, start, n):
    dist = [float('inf')] * n
    dist[start] = 0
    prev = [-1] * n
    pq = [(0, start)]

    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                prev[v] = u
                heapq.heappush(pq, (dist[v], v))

    return dist, prev

def get_path(prev, target):
    path = []
    current = target
    while current != -1:
        path.append(current)
        current = prev[current]
    return path[::-1]

graph = defaultdict(list)
edges = [(0, 1, 4), (0, 2, 1), (1, 3, 1), (2, 1, 2), (2, 3, 5)]
for u, v, w in edges:
    graph[u].append((v, w))

dist, prev = dijkstra(graph, 0, 4)
for i in range(4):
    path = get_path(prev, i)
    print(f"Distance to {i}: {dist[i]}, Path: {path}")

Applications in AI/ML

â„šī¸ Applications in AI and Machine Learning

  • Optimization: Graph algorithms solve vehicle routing, supply chain, and logistics problems
  • Cryptography: Secure ML requires encrypted computation (homomorphic encryption, secure multi-party computation)
  • Natural Language Processing: Finite automata for tokenization; graph algorithms for parsing; information theory for compression
  • Computer Vision: Graph cuts for image segmentation; shortest paths for object tracking
  • Reinforcement Learning: Markov Decision Processes are state-transition graphs; shortest paths relate to value iteration
  • Recommendation Systems: Bipartite matching, network flow for resource allocation
  • AutoML: Scheduling and resource allocation for neural architecture search
  • Edge AI: Coding theory enables efficient data transmission over noisy channels

Common Mistakes

MistakeCorrection
Using O(n2)O(n^2) when O(nlog⁥n)O(n \log n) existsAlways check if a better algorithm is available for the problem size
Ignoring constant factors in Big-OBig-O describes asymptotic growth; constants matter for small inputs
Forgetting that hashing can have collisionsHash tables have O(1)O(1) average but O(n)O(n) worst case
Confusing average and worst caseQuick sort: O(nlog⁥n)O(n \log n) average, O(n2)O(n^2) worst
Using greedy without proof of optimalityGreedy works for matroids and certain scheduling problems; not always optimal
Not considering space-time tradeoffsMemoization trades space for time; precomputation trades space for query speed
Assuming all graphs are sparseDense graphs (âˆŖEâˆŖâ‰ˆâˆŖVâˆŖ2|E| \approx |V|^2) need different algorithms than sparse graphs
Ignoring numerical precision in cryptoCryptographic operations require arbitrary-precision arithmetic

Interview Questions

  1. What is the time complexity of common sorting algorithms? Bubble: O(n2)O(n^2), Merge: O(nlog⁥n)O(n \log n), Quick: O(nlog⁥n)O(n \log n) average, Heap: O(nlog⁥n)O(n \log n). Merge and heap are guaranteed; quick is fastest in practice.

  2. How does RSA encryption work? Choose primes p,qp, q, compute n=pqn = pq and Ī•(n)\phi(n). Pick ee coprime to Ī•(n)\phi(n). Private key d=e−1(modĪ•(n))d = e^{-1} \pmod{\phi(n)}. Encrypt: c=me(modn)c = m^e \pmod{n}. Decrypt: m=cd(modn)m = c^d \pmod{n}. Security relies on factoring difficulty.

  3. What is dynamic programming and when should you use it? DP solves problems with overlapping subproblems and optimal substructure. Store subproblem results to avoid recomputation. Use when a naive recursive solution has exponential complexity.

  4. How does Dijkstra's algorithm work? Greedily relax edges from the closest unvisited vertex using a priority queue. Correct only for non-negative weights. For negative weights, use Bellman-Ford.

  5. What is the difference between BFS and DFS for shortest path? BFS finds shortest paths in unweighted graphs. Dijkstra handles weighted graphs. DFS does not guarantee shortest paths but is useful for cycle detection and topological sorting.

  6. What is network flow and where is it used? Maximum flow from source to sink in a capacity-constrained network. Applications: transportation, communication bandwidth, bipartite matching, image segmentation.

  7. How do error-correcting codes work? Add redundancy bits so errors can be detected and corrected. Hamming distance determines error capability: dd distance detects d−1d-1 errors and corrects ⌊(d−1)/2⌋\lfloor(d-1)/2\rfloor errors.

  8. What is the relationship between P and NP? P: problems solvable in polynomial time. NP: problems verifiable in polynomial time. P is a subset of NP. Whether P = NP is the most important open problem in computer science.


Practice Problems

📝Practice: Time Complexity

Problem: What is the time complexity of binary search?

💡Solution: Time Complexity

O(log⁥n)O(\log n). Each comparison eliminates half the remaining elements. After kk comparisons, n2k\frac{n}{2^k} elements remain. We need n2k=1\frac{n}{2^k} = 1, so k=log⁥2nk = \log_2 n.

📝Practice: RSA

Problem: If p=7,q=11p = 7, q = 11, and e=13e = 13, find the private key dd.

💡Solution: RSA

n=77n = 77, Ī•(n)=60\phi(n) = 60. Need dd such that 13d≡1(mod60)13d \equiv 1 \pmod{60}. Using extended Euclidean algorithm: 13×37=481=8×60+113 \times 37 = 481 = 8 \times 60 + 1. So d=37d = 37.

📝Practice: Topological Sort

Problem: Tasks: 0 depends on none, 1 depends on 0, 2 depends on 0, 3 depends on 1 and 2. Find a valid order.

💡Solution: Topological Sort

Dependencies: 0→1,0→2,1→3,2→30 \to 1, 0 \to 2, 1 \to 3, 2 \to 3. Valid orders: [0,1,2,3][0, 1, 2, 3] or [0,2,1,3][0, 2, 1, 3]. Multiple valid topological orderings exist.

📝Practice: Shortest Path

Problem: Find shortest path from vertex 0 in graph: edges (0,1,2), (0,2,5), (1,2,1), (1,3,6), (2,3,2).

💡Solution: Shortest Path

Dijkstra from 0: dist[0]=0, dist[1]=2, dist[2]=3 (via 1), dist[3]=5 (via 2). Shortest path to 3: 0 -> 1 -> 2 -> 3 with total weight 5.


Quick Reference

ApplicationKey ConceptComplexity
Binary SearchDivide and conquer on sorted arrayO(log⁥n)O(\log n)
Merge SortRecursively split and mergeO(nlog⁥n)O(n \log n)
DijkstraShortest path, non-negative weightsO((V+E)log⁥V)O((V+E) \log V)
RSAModular exponentiation, prime factoringO(k3)O(k^3) for kk-bit numbers
Hamming CodeSingle error correctionLinear time
Topological SortDAG orderingO(V+E)O(V + E)
Dynamic ProgrammingOverlapping subproblemsProblem-dependent
Hash TableAverage-case constant lookupO(1)O(1) average

Cross-References

  • Graph Theory → 074-discrete-graphs.mdx: Networks, social graphs, routing algorithms
  • Trees → 075-discrete-trees.mdx: Decision trees, spanning trees, search trees
  • Recurrence Relations → 076-discrete-recurrence.mdx: Algorithm complexity analysis, Master theorem
  • Number Theory → 077-discrete-number-theory.mdx: Cryptography, modular arithmetic, hashing
  • Boolean Algebra → 078-discrete-boolean.mdx: Circuit design, logic optimization, search queries
  • Automata Theory → 079-discrete-automata.mdx: Compilers, pattern matching, protocol verification
Lesson Progress80 / 100