Chapter 1: Data Structures and Algorithms

🎯 Learning Objectives

By the end of this study session, you’ll be able to:

  • Explain abstraction clearly - Define what abstraction means in programming and list its key benefits for both users and developers
  • Distinguish between ADTs and data structures - Understand the conceptual difference and how they relate to each other
  • Master the three fundamental linear ADTs:
    • Lists: Describe their unrestricted access pattern and identify when to use them
    • Stacks: Explain LIFO (Last In, First Out) principle and recognize stack-appropriate problems
    • Queues: Understand FIFO (First In, First Out) principle and identify queue-suitable scenarios
  • Apply ADTs to real-world problems - Choose the most appropriate ADT for different programming challenges
  • Work with Java Collections - Use predefined Java interfaces and classes to implement these ADTs
  • Solve classic problems - Handle tasks like string reversal, bracket matching, postfix evaluation, and profit calculations

🌟 The Big Picture

Think of Abstract Data Types as different ways of organizing and accessing your data, just like how you might organize items in your room differently depending on what you need to do with them.

The core insight: Different access patterns make different types of problems much easier to solve. When you choose the right ADT for your problem, the solution often becomes elegant and straightforward. When you choose poorly, you’ll find yourself fighting against the data structure instead of working with it.

This chapter introduces you to three fundamental “organizing principles” that will become your go-to tools for solving a huge variety of programming problems.

📚 Core Concepts

Understanding Abstraction - The Foundation

What is abstraction? Simply put, abstraction means hiding the complex details and showing only what’s essential. Think of it like driving a car - you don’t need to understand how the engine works internally; you just need to know that pressing the gas pedal makes it go faster.

In programming terms: Abstraction exists in thought but has no concrete existence. When we talk about “integers” or “strings,” we’re thinking about abstract concepts that get implemented differently by different systems.

Visual Reference (Slide 5):

The slide shows a cloud labeled “ABSTRACTION” with INTEGER concept above four boxes containing the numbers 1, 39, 65, and 15. This illustrates how the abstract concept of “integer” applies to all these concrete values.

Why do we love abstraction? It gives us two major superpowers:

  1. For Users: You can focus on the “big picture” and what you want to accomplish, not getting bogged down in implementation details
  2. For Developers: You can modify how things work internally without breaking existing code, as long as you maintain the same interface

Real-world example: When you use Integer.parseInt() in Java, you don’t need to know exactly how Java converts a string to a number. You just need to know it works reliably.

Programming Paradigms - A Quick Context Check

Before diving into ADTs, let’s quickly recap why abstraction became so important:

Procedural programming came first, where you wrote step-by-step instructions. Object-oriented programming followed, introducing better ways to organize code through:

  • Modularity: Breaking code into separate, functional units
  • Encapsulation: Hiding internal implementation details (information hiding)
  • Abstraction: Distilling complex systems down to their fundamental parts and describing them in simple, precise language

These principles work together to make code more reusable and maintainable - two huge wins for any programmer!

Abstract Data Types (ADTs) Explained

An ADT consists of:

  • Data with certain characteristics
  • A set of operations that can manipulate that data

Key insight: An ADT is a TYPE conceived apart from concrete reality. It’s the “what” (the concept), while a data structure is the “how” (the actual implementation using programming constructs).

Examples of ADTs you already know:

  • Integer, Double, String, Fraction, Counter

Collection ADTs - Organizing Groups of Objects

A collection ADT contains a group of objects. Think of collections as containers with different rules:

  • Some allow duplicate items, others don’t
  • Some maintain a specific order, others don’t care about order

Linear collections store entries in a linear sequence - imagine items lined up in a row. The three linear collections we’ll master are Lists, Stacks, and Queues.

The key difference between these three is the restrictions they place on how entries may be added, removed, or accessed.

Lists - The Flexible All-Rounder

What Makes a List Special?

A List gives you maximum flexibility - it’s like having a notebook where you can:

  • Add entries anywhere you want
  • Remove entries from any position
  • Look up any entry by its position
  • Search through all entries without restrictions

Formal definition: A linear collection of entries where you can add, remove, and search for entries without restriction.

Essential List Operations

Visual Reference (Slides 24-25):

The Java List interface documentation shows these core methods:

Operation        What It Does
add(e)          Insert element e at the end of the list
remove(i)       Remove element at position i and return it
isEmpty()       Return true if the list is empty  
get(i)          Return element at position i without removing it
clear()         Remove all elements from the list
size()          Return the number of elements in the list

When Do You Use Lists?

Perfect for:

  • Task lists, shopping lists, playlists - anything where you need flexible access
  • High-precision arithmetic - representing very long numbers that don’t fit in standard integer types
  • Any scenario where you need to access, insert, or remove items at arbitrary positions

Real-World List Applications

Example 1: Marathon Registration System

Visual Reference (Slide 28):

Shows a group of marathon runners with numbers, illustrating the registration scenario.

Algorithm:

  1. Create an empty list
  2. While there are runners to register:
    • Input runner’s details
    • Add runner to the list
  3. Display list of registered runners

This works perfectly because you need to maintain the order of registration while allowing flexible access to the runner information.

Note about List Types:

  • Unordered lists: Entries aren’t arranged in any particular order (this is the default)
  • Sorted lists: Entries are maintained in sorted order (covered in Chapter 7)

Stacks - The LIFO Champion

Understanding the Stack Mindset

Key principle: LIFO (Last In, First Out). The last item you put in is the first one that comes out.

Visual Reference (Slide 34):

Shows a spring-loaded plate dispenser at a cafeteria - the perfect metaphor! You can only add plates to the top, and when someone takes a plate, they get the one that was added most recently.

Formal definition: A collection where objects are inserted and removed according to the LIFO principle. Typically, you can’t search through a stack - you can only work with the top element.

Essential Stack Operations

Operation    What It Does
push(e)      Insert element e to be the top of the stack
pop()        Remove and return the element at the top
isEmpty()    Return true if the stack is empty
peek()       Return the top element without removing it
clear()      Remove all elements from the stack

Stack Applications - Where LIFO Shines

1. Undo Operations Think about Microsoft Word’s undo feature - when you press Ctrl+Z, you undo the most recent action first.

Visual Reference (Slides 35-36):

Shows browser history and undo operations, demonstrating how the most recent actions are processed first.

2. String Reversal

Visual Reference (Slide 39):

Illustrates pushing each character of “IBMRD” onto a stack, then popping them to get “DRMBI”.

Algorithm:

  1. Create an empty stack
  2. Push every character of the string onto the stack
  3. While the stack is not empty:
    • Pop a character
    • Display the character

3. Bracket Matching - A Classic Problem

This is one of the most elegant uses of stacks! You need to check if brackets (), [], and {} are properly balanced in expressions.

Visual Reference (Slides 44-47):

Multiple diagrams showing how the stack contents change as we process different bracket expressions:

For balanced expression {[()]}:

Process: {  [  (  )  ]  }
         ↓  ↓  ↓  ↓  ↓  ↓
Stack:   {  {  {  {  {  empty
            [  [  [  
               (     

Result: Balanced! âś“

Algorithm:

  1. Create an empty stack
  2. For each character in the expression:
    • If it’s an opening bracket (, [, or {: push it onto the stack
    • If it’s a closing bracket ), ], or }:
      • Pop a bracket from the stack
      • Check if the popped bracket matches the closing bracket
      • If they don’t match, the expression is unbalanced
  3. After processing all characters, the stack should be empty for a balanced expression

4. Postfix Expression Evaluation

Background: We normally write expressions in infix form (like a + b), but computers often work better with postfix form (like a b +).

Expression types:

  • Infix: a + b (operator between operands)
  • Prefix: + a b (operator before operands)
  • Postfix: a b + (operator after operands)

Why postfix? No need for parentheses or precedence rules - much easier to evaluate!

Visual Reference (Slides 53-54):

Shows step-by-step stack contents while evaluating postfix expressions.

Algorithm for postfix evaluation:

  1. Create an empty stack
  2. For each character in the postfix expression:
    • If it’s an operand (number): push it onto the stack
    • If it’s an operator (+, -, *, /):
      • Pop two operands (first pop = right operand, second pop = left operand)
      • Perform the operation
      • Push the result back onto the stack
  3. The final result is the only item left in the stack

5. The Program Stack - How Your Code Actually Runs

Visual Reference (Slide 57):

Shows the program stack at three different points in time during method execution.

What happens when you call a method:

  1. The system creates an activation record containing:
    • Method arguments
    • Local variables
    • Program counter (which instruction to execute next)
  2. This record gets pushed onto the program stack
  3. When the method finishes, its record gets popped off

This is why recursive functions work - each recursive call gets its own space on the stack!

Queues - The FIFO Organizer

Understanding the Queue Mindset

Key principle: FIFO (First In, First Out). The first item you put in is the first one that comes out - just like a line of people waiting for service.

Formal definition: A collection where the element that has been in the queue the longest will be the next one to be removed. Elements enter at the rear/back, and elements are removed from the front.

Essential Queue Operations

Operation     What It Does
enqueue(e)    Add an entry to the rear of the queue
dequeue()     Remove the entry at the front of the queue  
isEmpty()     Check if the queue is empty
getFront()    Retrieve (but don't remove) the front object
size()        Return the number of entries in the queue

Java Implementation Note

Visual Reference (Slide 61-62):

Shows the Java Queue interface methods and mentions ArrayBlockingQueue as a concrete implementation.

Java provides java.util.Queue interface with several implementations like ArrayBlockingQueue that follow the FIFO principle.

Queue Applications - Where FIFO Excels

1. Customer Service Systems Perfect for handling calls to a restaurant reservation center - first caller gets served first.

2. Print Queues
When multiple people send documents to a printer, they get printed in the order they were submitted.

3. Round-Robin Schedulers

Visual Reference (Slide 64):

Shows a circular diagram illustrating how a round-robin scheduler works:

  1. Dequeue the next process/task
  2. Give it some CPU time
  3. Enqueue it back to the end of the queue

This ensures fair sharing of resources like CPU time among different programs.

4. Service Counter Simulation Single queue, multiple counters - like at the post office where people take a number and wait to be called.

5. Computing Stock Profits - A Practical Example

This is a brilliant real-world application! When you sell stocks, tax law requires you to sell them in the order you bought them (FIFO).

Problem scenario:

  • Last year: bought 20 shares at RM45 each
  • Last month: bought 20 shares at RM75 each
  • Today: sold 30 shares at RM65 each
  • Question: What’s your profit?

Visual Reference (Slide 69):

Shows two queue representations: (a) Individual shares: Each share as a separate queue entry (b) Grouped shares: Batches of shares with same price as single entries

Solution using a queue:

  1. Buying: Enqueue each purchase (shares + price)
  2. Selling: Dequeue shares in purchase order, calculating profit for each batch

Profit calculation:

  • First 20 shares sold: bought at RM45, sold at RM65 → profit = 20 Ă— (65-45) = RM400
  • Next 10 shares sold: bought at RM75, sold at RM65 → loss = 10 Ă— (65-75) = -RM100
  • Total profit: RM400 - RM100 = RM300

🔄 Choosing the Right ADT - Decision Framework

Here’s your decision-making guide:

Use a List when:

  • You need flexible access to any element at any time
  • You’re implementing task lists, playlists, or similar collections
  • You need to insert/remove elements at arbitrary positions
  • Order matters, but you don’t have strict access restrictions

Use a Stack when:

  • You need to reverse the order of processing (LIFO)
  • You’re implementing undo operations
  • You’re processing nested structures (like brackets or method calls)
  • You’re evaluating expressions or handling recursion
  • The most recent item is the most important

Use a Queue when:

  • You need fair, first-come-first-served processing (FIFO)
  • You’re simulating real-world waiting lines
  • You’re implementing schedulers or managing shared resources
  • You’re handling tasks that must be processed in submission order
  • You need to maintain chronological order

🔄 Connections and Review

The Big Takeaway: ADTs are about choosing the right access pattern for your problem. Each ADT enforces different rules about how you can interact with your data:

  • Lists: No restrictions → Maximum flexibility
  • Stacks: LIFO restrictions → Perfect for reversing and nesting
  • Queues: FIFO restrictions → Perfect for fair processing and chronological order

Benefits of Abstraction Summary:

  1. Reusability - Write once, use many times
  2. Maintainability - Change implementation without breaking existing code
  3. Focus - Users concentrate on what they want to accomplish, not how it’s implemented
  4. Flexibility - Developers can optimize implementations behind the scenes

Remember: The “Abstract” in ADT means you’re thinking about the concept and behavior, not the implementation details. That’s what makes these tools so powerful and widely applicable!

Study Tip: Practice identifying which ADT fits different scenarios. The more you recognize these patterns, the more naturally you’ll choose the right tool for each programming challenge.