Bachelors Program in Computer Applications
Fundamentals of Programming (C/C++)
•
Bachelor
Algorithmic Problem Solving
Introduction
Programming is not primarily the act of writing statements in C or C++. It is the disciplined process of transforming a problem into a precise, executable procedure. A program can be syntactically correct yet solve the wrong problem, fail on boundary cases, or use excessive resources. Algorithmic problem solving addresses these issues before and during implementation.
An algorithm is a finite, ordered, and unambiguous set of instructions for solving a problem. Programming languages provide the notation for expressing algorithms, whereas problem solving provides the reasoning that determines what the program should do. This distinction is fundamental: a well-designed algorithm can be implemented in C, C++, Java, Python, or another language, but poorly understood requirements cannot be repaired merely by changing syntax.
This chapter develops a systematic approach based on input–process–output analysis, abstraction, pseudocode, flowcharts, desk checking, modular decomposition, and introductory complexity analysis. These techniques form the foundation for later study of data structures, object-oriented programming, databases, and software engineering.
Learning Objectives
By the end of this chapter, you should be able to:
formulate a problem using an input–process–output model;
identify assumptions, constraints, and required output;
use abstraction to represent only the details relevant to a solution;
write clear pseudocode and construct suitable flowcharts;
trace an algorithm using desk checking and test cases;
decompose a problem into coherent modules;
distinguish correctness, termination, efficiency, and usability;
describe basic time and space complexity using asymptotic notation;
translate an algorithmic design into reliable C or C++ code.
1. From Problem Statement to Algorithm
A problem statement usually describes a goal in natural language. Natural language is expressive but may be incomplete or ambiguous. Algorithmic analysis converts it into a precise specification.
A useful first step is to ask five questions:
What information is available?
What information must be produced?
What processing connects the input to the output?
What assumptions and constraints apply?
What constitutes a correct result?
For example, “Calculate a student’s final result” is incomplete. A precise version must specify the assessment components, their weights, valid mark ranges, rounding policy, and grading rules.
The Input–Process–Output Model
The input–process–output (IPO) model separates a problem into three related parts:
Component
Meaning
Example: average of three marks
Input
Data supplied to the algorithm
Three numeric marks
Process
Operations performed on the data
Add marks and divide by 3
Output
Information produced
Average mark
The IPO model is not itself an algorithm. It is a requirements model that helps prevent missing data or unnecessary processing. A complete specification may also include validation and error handling.
For the average example, let the marks be m₁, m₂, and m₃. Assuming each mark is numeric and valid, the average is:
average = (m₁ + m₂ + m₃) ÷ 3
The divisor is 3 because there are three equally weighted observations. If the marks have different weights, the formula must change; using an unweighted average would solve a different problem.
Constraints and Assumptions
Constraints describe limits within which an algorithm must operate. Examples include:
an age must be between 0 and 120;
a denominator must not be zero;
an array contains at least one element;
an identifier must be unique;
a monetary value must be represented with an appropriate precision policy.
Assumptions are conditions accepted as true for a particular design. They should be stated rather than hidden. For example, an algorithm that finds the largest value in a list may assume that the list is nonempty. If an empty list is possible, the algorithm needs an explicit policy, such as returning an error status.
2. Abstraction and Representation
Abstraction is the process of focusing on relevant properties while omitting irrelevant detail. It allows a programmer to reason about a problem without being overwhelmed by its implementation or real-world complexity.
Suppose a library system must determine whether a book is overdue. Relevant properties may include the due date, return date, and loan status. The book’s cover colour and physical weight are irrelevant to this decision. A useful abstraction might be:
Loan
dueDate
returnDate
status
Abstraction occurs at several levels:
Problem abstraction: describing the goal without committing to a programming language.
Data abstraction: representing entities through relevant data fields.
Procedural abstraction: naming a task, such as calculateTotal, without initially exposing its internal steps.
Control abstraction: representing repetition or selection conceptually before choosing exact syntax.
Good abstraction has two properties. It is sufficiently simple to support reasoning, but sufficiently detailed to preserve information needed for correctness. Excessive detail creates cognitive load; insufficient detail produces an ambiguous design.
A useful design question is: “If this detail changed, would the solution’s main logic change?” If not, it may belong below the current abstraction level.
3. Algorithm Properties and Correctness
A well-formed algorithm normally has the following properties:
Finiteness: it terminates after a finite number of steps for every permitted input.
Definiteness: each step has a clear meaning.
Input: it accepts zero or more well-defined inputs.
Output: it produces at least one result or observable effect.
Effectiveness: each operation can be carried out in finite time using available resources.
Generality: it solves all inputs within its specified domain, not merely one example.
Correctness has two dimensions. Partial correctness means that if the algorithm terminates, its result is correct. Total correctness means that it is partially correct and terminates for all permitted inputs.
For a loop, programmers often use a loop invariant: a statement that remains true before and after each iteration. Consider finding a maximum value in a nonempty list. Before processing position i, the invariant can be stated as: “the current maximum equals the largest value among the elements already examined.” Initially, the first element establishes the invariant. Each later value either preserves the current maximum or replaces it. When all elements are examined, the invariant proves that the result is the list maximum.
4. Pseudocode
Pseudocode is an informal, language-independent notation for expressing algorithms. It should be precise enough to implement but not tied to C or C++ punctuation.
Common pseudocode conventions include:
BEGIN
READ value
IF condition THEN
statements
ELSE
statements
END IF
FOR each item in collection DO
statements
END FOR
DISPLAY result
END
Pseudocode should use meaningful names, consistent indentation, and one logical action per line. It should make selection, repetition, input, output, and termination visible.
Worked Example: Largest of Three Values
Problem: Read three numbers and display the largest.
IPO analysis
Input: a, b, and c;
Process: compare the values;
Output: the largest value;
Assumption: all values are comparable numeric values.
BEGIN
READ a, b, c
largest ← a
IF b > largest THEN
largest ← b
END IF
IF c > largest THEN
largest ← c
END IF
DISPLAY largest
END
This algorithm handles ties correctly because equal values do not need to replace the current largest value. It uses two comparisons regardless of the input values.
Selection, Repetition, and Sentinel Values
Selection chooses among alternatives. Repetition executes a block multiple times. A sentinel is a special input value used to indicate termination, such as -1 for a sequence of nonnegative marks. A sentinel must not be confused with valid data; if −1 is a legitimate mark, it is unsuitable as a sentinel.
Example: calculating the sum of nonnegative numbers until −1 is entered:
BEGIN
total ← 0
READ value
WHILE value ≠ -1 DO
total ← total + value
READ value
END WHILE
DISPLAY total
END
The initial read occurs before the loop, and the second read occurs inside the loop. Omitting the second read would cause an infinite loop whenever the first value is not −1.
5. Flowcharts
A flowchart represents control flow graphically. Standard symbols include:
Symbol
Meaning
Oval
Start or end
Parallelogram
Input or output
Rectangle
Processing step
Diamond
Decision or condition
Arrow
Direction of control flow
Flowcharts are especially useful when a problem contains several decisions or loops. They make alternative paths visible to reviewers and beginners. However, very large flowcharts can become difficult to maintain; modular pseudocode or structured design is usually preferable for complex systems.
Flowchart for determining whether a number is even
Figure: Flowchart for determining whether a number is even
Figure: A flowchart for classifying an integer as even or odd.
A flowchart and pseudocode should describe the same logic. If they disagree, neither should be treated as authoritative until the discrepancy is resolved.
6. Desk Checking and Testing
Desk checking is a manual simulation of an algorithm using a trace table. It is a reasoning technique, not a substitute for executing compiled code. A trace table records important variables after each significant step.
Worked Trace
Consider the maximum algorithm with input 7, 12, and 9.
Step
a
b
c
largest
Explanation
Initial read
7
12
9
—
Inputs received
Assignment
7
12
9
7
Start with a
Compare b
7
12
9
12
12 > 7
Compare c
7
12
9
12
9 is not greater than 12
Output
7
12
9
12
Correct maximum
Effective test data includes:
normal values, representing common use;
boundary values, at the limits of valid input;
minimum and maximum permitted values;
equal values;
invalid values;
empty or single-item collections, where applicable;
values that exercise every branch.
For the even–odd algorithm, useful tests include 8, 7, 0, −4, and −5. The remainder operation must be understood according to the chosen language and specification, especially for negative operands. Tests should be selected from the requirements, not merely invented after implementation.
7. Modular Decomposition
Modular decomposition divides a large problem into smaller, coherent units. Each module should have a clear responsibility and a defined interface: its inputs, outputs, assumptions, and side effects.
A student-result system might be decomposed as follows:
high cohesion: its operations belong to one focused responsibility;
low coupling: it depends on as few details of other modules as practical;
information hiding: internal implementation details are not unnecessarily exposed;
single responsibility: one reason to change.
Modularity improves testing, reuse, maintenance, and collaboration. It also makes complexity analysis more meaningful because each component can be examined separately.
In C, modules may be organized through functions and header/source files. In C++, functions, classes, namespaces, and interfaces provide additional mechanisms. The design principle is language-independent, although syntax differs.
Example of Decomposition
FUNCTION calculateWeightedAverage(mark1, mark2, mark3, w1, w2, w3)
totalWeight ← w1 + w2 + w3
RETURN (mark1×w1 + mark2×w2 + mark3×w3) ÷ totalWeight
END FUNCTION
The function requires an assumption that totalWeight is positive. A robust implementation validates this precondition or reports an error. The formula is applicable when weights represent relative contributions and their sum is nonzero.
8. Basic Time and Space Complexity
Complexity analysis estimates how resource requirements grow as input size grows. It is more informative than measuring one execution on one computer.
Let n denote the number of input items. Time complexity describes the growth of the number of fundamental operations. Space complexity describes the growth of additional memory used by the algorithm, excluding the input storage when discussing auxiliary space.
Big-O notation expresses an asymptotic upper bound. If an algorithm performs at most a constant multiple of f(n), plus lower-order terms, its time complexity is written O(f(n)). Constants and lower-order terms are ignored when describing growth.
Common classes include:
Complexity
Typical example
Growth interpretation
O(1)
Accessing an array element by index
Does not grow with n
O(log n)
Binary search on sorted data
Grows slowly by repeated division
O(n)
Scanning a list once
Proportional to n
O(n log n)
Efficient comparison sorting
Common in scalable sorting
O(n²)
Comparing many pairs
Can become costly for large n
O(2ⁿ)
Exhaustive subset generation
Rapidly becomes impractical
Complexity notation is an abstraction. An O(n) algorithm may be slower than an O(n²) algorithm for very small n because constants, memory access, and implementation overhead matter. Nevertheless, asymptotic growth is crucial for scalability.
Counting Simple Algorithms
The maximum-of-three algorithm performs two comparisons, so its time complexity is O(1): the number of inputs is fixed. A maximum algorithm for n values scans the list once and performs n − 1 comparisons, giving O(n) time and O(1) auxiliary space.
A nested loop such as the following performs n × n iterations when both loops run n times:
FOR i ← 1 TO n
FOR j ← 1 TO n
process(i, j)
END FOR
END FOR
Thus its time complexity is O(n²), assuming process takes constant time. If the inner loop runs from 1 to i, the total number of iterations is:
1 + 2 + … + n = n(n + 1) ÷ 2
This is still O(n²), because the leading term is proportional to n².
Best, Average, and Worst Cases
The best case is the most favourable permitted input, the worst case is the least favourable, and the average case depends on an assumed input distribution. For a linear search of n items:
best case: the target is first, O(1);
worst case: the target is last or absent, O(n);
average case: approximately (n + 1) ÷ 2 comparisons if the target is equally likely to occur at any position, which is O(n).
The average-case statement requires the stated probability assumption. Without such an assumption, “average” is not well-defined.
Space complexity must also be considered. A loop using a fixed number of variables has O(1) auxiliary space. Creating a second array of n elements requires O(n) additional space. Recursive algorithms also consume call-stack space; recursion depth may therefore affect memory use even when no explicit array is created.
9. From Algorithm to C/C++ Implementation
Once an algorithm has been checked, implementation becomes a translation task rather than an act of guesswork. The programmer must still address language-specific issues:
choose suitable types and ranges;
validate input and handle failed input operations;
avoid division by zero and invalid indexing;
understand integer division and floating-point precision;
use braces and indentation consistently;
initialize variables before use;
keep functions small and purposeful;
compile with warnings enabled;
test using the planned cases.
For example, in C or C++, the expression 5 / 2 with integer operands produces integer division, typically 2. To obtain a fractional result, at least one operand must use a floating-point type, such as 5.0 / 2. This is an implementation detail that must be reconciled with the abstract algorithm.
Input validation is part of correctness. If a specification requires a positive denominator, a program should reject zero and negative values rather than allowing undefined or meaningless processing. Error messages should help users recover without revealing sensitive system information.
A university wants a small application to calculate parking charges. The fee depends on the number of hours parked and whether the driver is a registered student. The system must be understandable, testable, and easy to modify if rates change.
Problem Specification
Assume the following rules:
hours must be greater than 0 and no more than 24;
the first 2 hours cost 20 currency units in total;
each additional hour costs 8 units;
registered students receive a 25% discount;
the result is displayed to two decimal places.
The rates and discount are business rules and should be isolated from input and output logic.
IPO Analysis
Component
Specification
Input
Parking hours and student status
Process
Validate; calculate base fee; apply discount
Output
Final fee
Constraints
0 < hours ≤ 24; status is valid
Assumptions
Hours are billed as whole hours; currency uses two decimal places
Algorithm Design
For h hours, the base fee is:
baseFee = 20, if 1 ≤ h ≤ 2 baseFee = 20 + 8 × (h − 2), if 2 < h ≤ 24
If d is 0 for a nonstudent and 0.25 for a student, the final fee is:
finalFee = baseFee × (1 − d)
The discount formula is valid because d is expressed as a decimal proportion, not as the number 25. The units of finalFee are currency units.
FUNCTION calculateFee(hours, isStudent)
IF hours ≤ 0 OR hours > 24 THEN
RETURN error
END IF
IF hours ≤ 2 THEN
baseFee ← 20
ELSE
baseFee ← 20 + 8 × (hours − 2)
END IF
IF isStudent THEN
finalFee ← baseFee × 0.75
ELSE
finalFee ← baseFee
END IF
RETURN finalFee
END FUNCTION
Desk Check
For 5 hours and a student:
base fee = 20 + 8 × (5 − 2) = 20 + 24 = 44;
student fee = 44 × 0.75 = 33 currency units.
For 2 hours and a nonstudent:
base fee = 20;
final fee = 20 currency units.
Boundary tests should include 0, 1, 2, 3, 24, and 25 hours. The values 0 and 25 must be rejected. Testing 2 and 3 verifies the change between pricing branches.
Modular Design
A maintainable implementation could contain:
readParkingInput for input;
validateHours for constraint checking;
calculateBaseFee for the rate policy;
applyStudentDiscount for the discount rule;
displayFee for formatted output.
This decomposition permits a policy change, such as a new hourly rate, without rewriting validation or display code.
Complexity and Outcome
The calculation performs a fixed number of operations, so its time complexity is O(1) and auxiliary space complexity is O(1). The design makes assumptions explicit, separates policy from interaction, and provides clear locations for tests. In a production system, additional concerns would include currency representation, localization, audit records, authorization, and protection of personal or vehicle data.
11. Challenges, Limitations, and Professional Practice
Algorithmic models simplify reality. A model may omit exceptional conditions, ambiguous requirements, concurrency, hardware failures, or human factors. Consequently, algorithm design should include communication with stakeholders and review of assumptions.
Common design errors include:
coding before clarifying the required output;
using a sentinel that is also valid data;
failing to initialize an accumulator;
confusing assignment with comparison in source code;
ignoring empty collections;
writing a loop whose control variable never changes;
testing only typical values;
using floating-point values for money without a considered rounding policy;
optimizing prematurely before measuring actual constraints.
Ethical and governance issues are also relevant. An algorithm that assigns grades, loan eligibility, or access privileges can affect people’s lives. Designers should document rules, test for unintended discrimination, protect personal data, and provide an avenue for correction or appeal. “The computer produced the result” is not an adequate accountability explanation.
AI-assisted programming tools can generate pseudocode or code, but generated output must be reviewed. The programmer remains responsible for requirements, security, correctness, licensing implications, and testing. AI tools may reproduce subtle errors, invent unsupported assumptions, or expose confidential input. They are aids to reasoning, not replacements for it.
Best practice is to maintain traceability: connect each requirement to an algorithm step, each algorithm branch to test cases, and each module to a clear responsibility. Version control, code review, automated tests, and compiler warnings reinforce this discipline.
Chapter Summary
Algorithmic problem solving is the bridge between an informal problem and a reliable program. The IPO model identifies data entering, processing within, and results leaving a system. Abstraction reduces unnecessary detail while preserving information essential to correctness. Pseudocode and flowcharts express control logic before language-specific implementation.
Desk checking exposes incorrect state changes and supports systematic test design. Modular decomposition divides complex work into focused units with clear interfaces, high cohesion, and low coupling. Correctness requires attention to assumptions, constraints, termination, and boundary cases. Basic complexity analysis describes how time and memory requirements grow with input size; O(1), O(log n), O(n), O(n log n), and O(n²) are important introductory classes.
A disciplined process does not eliminate all defects, but it makes errors easier to detect, explain, test, and maintain. These foundations support effective C and C++ programming and provide professional habits applicable to larger software systems.
Key Terms
Algorithm: A finite, ordered, unambiguous procedure for solving a problem.
Abstraction: Representing relevant features while omitting unnecessary detail.
Assumption: A condition accepted as true for a particular design.
Constraint: A limit or rule restricting valid input or operation.
Input–process–output model: A framework identifying supplied data, transformations, and results.
Pseudocode: Language-independent notation used to express algorithmic logic.
Flowchart: A graphical representation of control flow.
Selection: Choosing a path according to a condition.
Iteration: Repeating a group of instructions.
Sentinel: A special value indicating termination of input.
Desk checking: Manual simulation of an algorithm using selected data.
Trace table: A table recording variable states during execution.
Module: A cohesive unit of a larger solution with a defined interface.
Cohesion: The degree to which elements of a module belong together.
Coupling: The degree of dependency between modules.
Correctness: The property of producing specified results for permitted inputs.
Time complexity: The growth of computational work as input size increases.
Space complexity: The growth of memory requirements as input size increases.
Big-O notation: Asymptotic notation describing an upper growth bound.
Loop invariant: A condition that remains true across loop iterations.
Review Questions
Short Answer Questions
What are the three components of the input–process–output model?
Define abstraction and give one example from a library system.
What is the purpose of a sentinel value?
Distinguish time complexity from auxiliary space complexity.
Why are boundary test cases important?
Long Answer Questions
Explain how an informal problem statement can be transformed into a precise algorithmic specification. Include inputs, outputs, assumptions, and constraints.
Compare pseudocode and flowcharts. Discuss the strengths and limitations of each representation.
Explain modular decomposition, cohesion, coupling, and information hiding in the design of a C++ program.
Describe best-case, average-case, and worst-case complexity using linear search as an example.
Explain how desk checking, loop invariants, and systematic testing contribute to algorithm correctness.
Analytical / Application Questions
Design an IPO model and pseudocode algorithm for calculating an electricity bill using a fixed connection charge and different rates for successive consumption bands. State all assumptions and boundary cases.
A program scans an array of n values and then compares every pair of values. Determine the combined asymptotic time complexity and justify your answer.
Review the campus parking case. Propose a modular design that supports different vehicle types and explain how your design would reduce the impact of future pricing changes.
Multiple Choice Questions with Answers
Question 1
Which component of the IPO model describes transformations applied to input data?
A. Input
B. Process
C. Output
D. Constraint
Correct Answer: B. Process
Question 2
What is the main purpose of abstraction in algorithmic problem solving?
A. To hide all requirements from the programmer
B. To focus on relevant details while omitting unnecessary detail
C. To guarantee constant time complexity
D. To replace testing with diagrams
Correct Answer: B. To focus on relevant details while omitting unnecessary detail
Question 3
An algorithm scans each of n elements once and uses only a fixed number of extra variables. What are its time and auxiliary space complexities?
A. O(1) time and O(n) space
B. O(log n) time and O(n) space
C. O(n) time and O(1) space
D. O(n²) time and O(n²) space
Correct Answer: C. O(n) time and O(1) space
Question 4
Which flowchart symbol normally represents a decision?
A. Oval
B. Rectangle
C. Parallelogram
D. Diamond
Correct Answer: D. Diamond
Question 5
What is desk checking?
A. Compiling a program without executing it
B. Manually simulating an algorithm with selected data
C. Measuring processor temperature during execution
D. Replacing modules with library functions
Correct Answer: B. Manually simulating an algorithm with selected data
Question 6
For a linear search, under the usual assumption that a present target is equally likely to occur at any position, the average number of comparisons is:
A. 1
B. n
C. (n + 1) ÷ 2
D. n²
Correct Answer: C. (n + 1) ÷ 2
Question 7
Which property means that a module has one focused responsibility?
A. High cohesion
B. High coupling
C. Infinite recursion
D. Data duplication
Correct Answer: A. High cohesion
Question 8
In the parking-fee case, what is the base fee for 5 hours?
A. 20 currency units
B. 28 currency units
C. 40 currency units
D. 44 currency units
Correct Answer: D. 44 currency units
Question 9
Which condition is required for total correctness?
A. The algorithm has a flowchart only
B. The algorithm is partially correct and terminates
C. The algorithm uses recursion
D. The algorithm has O(1) complexity
Correct Answer: B. The algorithm is partially correct and terminates
Question 10
Which complexity class grows most rapidly as n increases among the following choices?
A. O(log n)
B. O(n)
C. O(n log n)
D. O(n²)
Correct Answer: D. O(n²)
Fundamentals of Programming (C/C++)
Chapter 2
of 15