title: Algorithms and Data Structures —
Overview
Capstone Enhancement: Algorithms & Data Structures
Inventory Management Application
Artifact Overview
The Inventory Management Application is an Android mobile application that allows users to manage inventory items through a grid-based interface. The app provides user authentication with SHA-256 password hashing, full CRUD operations, SMS low-stock alerts, and a normalized Room database with six related tables.
This capstone enhancement focuses on Category Two: Algorithms and Data Structures, transforming the application from a system with zero sorting capability into one powered by multiple industry-standard sorting algorithms with intelligent, automatic algorithm selection.
Enhancement Summary
The Problem
The original application had no sorting algorithm implementation. All data ordering relied on a single hardcoded SQL query:
// InventoryDao.java - The ONLY sort option available
@Query("SELECT * FROM inventory ORDER BY id DESC")
List<InventoryItem> getAll();
This meant:
- Users could never sort items by name, quantity, price, or date
- Every sort change required a full database query (slow, ~60ms for 1000 items)
- There was no in-memory sorting capability
- The app was essentially unusable for large inventories where finding items matters
- Users with 100+ items had no way to efficiently locate what they needed
The Solution
The enhancement introduced a complete sorting engine with four sorting algorithms, nine sort criteria, smart algorithm selection, and real-time search filtering - all operating in-memory for maximum performance.
What Was Built
New Files Created
| File | Purpose | Lines |
|---|---|---|
InventorySortManager.java |
Core sorting engine with four algorithm implementations | ~460 |
SortCriteria.java |
Enum defining nine sort options with display names and direction | ~66 |
Files Modified
| File | Changes Made |
|---|---|
InventoryActivity.java |
Integrated sorting UI, sort dialog, search filtering, sort preference persistence |
InventoryAdapter.java |
Added HashMap caching for category/supplier/location lookups |
InventoryDao.java |
Added sorting, filtering, search, aggregation, pagination, and statistics queries |
InventoryItem.java |
Extended entity with price, SKU, timestamps, minStockLevel, foreign keys, utility methods |
Algorithms Implemented
1. QuickSort
Purpose: General-purpose sorting for most criteria (name, price, date).
How It Works: QuickSort uses the divide-and-conquer paradigm. It selects a pivot element, partitions the array so all elements less than the pivot are on the left and all elements greater are on the right, then recursively sorts each partition.
Implementation Details:
- Uses the middle element as pivot to avoid O(n^2) worst-case on already-sorted data
- Partitions in-place using
Collections.swap()for memory efficiency - Recursive calls process the left and right partitions independently
// InventorySortManager.java - QuickSort with middle-element pivot
private static void quickSort(List<InventoryItem> items,
int low, int high,
SortCriteria criteria) {
if (low < high) {
int pivotIndex = partition(items, low, high, criteria);
quickSort(items, low, pivotIndex - 1, criteria); // Left partition
quickSort(items, pivotIndex + 1, high, criteria); // Right partition
}
}
private static int partition(List<InventoryItem> items,
int low, int high,
SortCriteria criteria) {
int middle = low + (high - low) / 2;
InventoryItem pivot = items.get(middle);
Collections.swap(items, middle, high); // Move pivot to end
int i = low - 1;
for (int j = low; j < high; j++) {
if (compare(items.get(j), pivot, criteria) <= 0) {
i++;
Collections.swap(items, i, j);
}
}
Collections.swap(items, i + 1, high); // Place pivot in final position
return i + 1;
}
Complexity: | Case | Time | Space | |——|——|——-| | Best | O(n log n) | O(log n) | | Average | O(n log n) | O(log n) | | Worst | O(n^2) | O(n) |
Why QuickSort: It has the best average-case performance of comparison-based sorts and operates in-place, making it ideal as the default algorithm for string and floating-point comparisons where Counting Sort cannot apply.
2. MergeSort
Purpose: Stable sort with guaranteed O(n log n) performance. Available when consistent, predictable sort times are needed.
How It Works: MergeSort also uses divide-and-conquer. It splits the list in half, recursively sorts each half, then merges the two sorted halves back together by comparing elements one at a time.
Implementation Details:
- Creates new
ArrayListsublists for left and right halves - Merges back into the original list using three-pointer technique
- Stable sort: items with equal keys maintain their original relative order
// InventorySortManager.java - MergeSort with stable merge
private static void mergeSortRecursive(List<InventoryItem> items,
SortCriteria criteria) {
if (items.size() <= 1) return;
int mid = items.size() / 2;
List<InventoryItem> left = new ArrayList<>(items.subList(0, mid));
List<InventoryItem> right = new ArrayList<>(items.subList(mid, items.size()));
mergeSortRecursive(left, criteria);
mergeSortRecursive(right, criteria);
merge(items, left, right, criteria);
}
private static void merge(List<InventoryItem> result,
List<InventoryItem> left,
List<InventoryItem> right,
SortCriteria criteria) {
int i = 0, j = 0, k = 0;
while (i < left.size() && j < right.size()) {
if (compare(left.get(i), right.get(j), criteria) <= 0) {
result.set(k++, left.get(i++));
} else {
result.set(k++, right.get(j++));
}
}
while (i < left.size()) result.set(k++, left.get(i++));
while (j < right.size()) result.set(k++, right.get(j++));
}
Complexity: | Case | Time | Space | |——|——|——-| | Best | O(n log n) | O(n) | | Average | O(n log n) | O(n) | | Worst | O(n log n) | O(n) |
Why MergeSort: Guarantees O(n log n) in all cases unlike QuickSort’s O(n^2) worst case. Its stability preserves secondary sort order, which is valuable when users sort by one criterion and then another.
3. Counting Sort
Purpose: Linear-time sorting optimized specifically for quantity-based sorting.
How It Works: Counting Sort is a non-comparison algorithm. Instead of comparing elements, it counts the occurrences of each value and uses those counts to place elements in sorted order. It creates “buckets” for every possible quantity value (0 through max), places each item in its corresponding bucket, then reads the buckets in order.
Implementation Details:
- Finds the maximum quantity to determine bucket count
- Creates
ArrayListbuckets for each quantity value (0 to max) - Distributes items into buckets, then rebuilds the list from buckets in ascending or descending order
- Handles negative quantities defensively (maps to bucket 0)
// InventorySortManager.java - Counting Sort with bucket lists
private static void countingSort(List<InventoryItem> items,
SortCriteria criteria) {
if (items.isEmpty()) return;
int maxQuantity = findMaxQuantity(items);
// Create buckets for each possible quantity (0 to max)
List<List<InventoryItem>> buckets = new ArrayList<>(maxQuantity + 1);
for (int i = 0; i <= maxQuantity; i++) {
buckets.add(new ArrayList<>());
}
// Place each item in its bucket
for (InventoryItem item : items) {
int quantity = Math.max(item.getQuantity(), 0);
buckets.get(quantity).add(item);
}
// Rebuild list from buckets
items.clear();
if (criteria == SortCriteria.QUANTITY_ASC) {
for (int i = 0; i <= maxQuantity; i++)
items.addAll(buckets.get(i));
} else {
for (int i = maxQuantity; i >= 0; i--)
items.addAll(buckets.get(i));
}
}
Complexity: | Case | Time | Space | |——|——|——-| | All Cases | O(n + k) | O(k) |
Where k is the range of quantity values (0 to max quantity).
Why Counting Sort: For integer-based sorting where the range k is reasonable (< 100,000), Counting Sort runs in linear time - significantly faster than any comparison-based O(n log n) algorithm. Sorting 1000 items by quantity takes ~4ms vs ~12ms with QuickSort.
4. Insertion Sort
Purpose: Efficient sorting for small datasets (fewer than 50 items).
How It Works: Insertion Sort builds the sorted array one element at a time. For each element, it scans backward through the already-sorted portion to find the correct insertion position, shifting elements right as needed.
// InventorySortManager.java - Insertion Sort for small datasets
private static void insertionSort(List<InventoryItem> items,
SortCriteria criteria) {
for (int i = 1; i < items.size(); i++) {
InventoryItem key = items.get(i);
int j = i - 1;
while (j >= 0 && compare(items.get(j), key, criteria) > 0) {
items.set(j + 1, items.get(j));
j--;
}
items.set(j + 1, key);
}
}
Complexity: | Case | Time | Space | |——|——|——-| | Best | O(n) | O(1) | | Average | O(n^2) | O(1) | | Worst | O(n^2) | O(1) |
Why Insertion Sort: Despite O(n^2) worst case, Insertion Sort outperforms QuickSort and MergeSort on small datasets due to minimal overhead - no recursion, no auxiliary arrays, and excellent cache locality. For n < 50, the simplicity wins.
Smart Algorithm Selection
The InventorySortManager.sort() method automatically selects the optimal algorithm based on the dataset size and sort criteria. This is the core decision logic:
public static void sort(List<InventoryItem> items, SortCriteria criteria) {
if (items == null || items.size() <= 1) return;
int size = items.size();
// Decision 1: Small dataset → Insertion Sort (low overhead)
if (size < INSERTION_SORT_THRESHOLD) { // threshold = 50
insertionSort(items, criteria);
return;
}
// Decision 2: Quantity sorting → Counting Sort (linear time)
if (criteria == SortCriteria.QUANTITY_ASC ||
criteria == SortCriteria.QUANTITY_DESC) {
int maxQuantity = findMaxQuantity(items);
if (maxQuantity < COUNTING_SORT_MAX_RANGE) { // max range = 100,000
countingSort(items, criteria);
return;
}
}
// Decision 3: Everything else → QuickSort (best average case)
quickSort(items, 0, items.size() - 1, criteria);
}
Decision Tree:
┌─────────────────┐
│ sort() called │
└────────┬────────┘
│
┌────────▼────────┐
│ size < 50? │
└───┬─────────┬───┘
YES │ │ NO
┌────────▼──┐ ┌──▼────────────┐
│ Insertion │ │ Quantity sort? │
│ Sort │ └──┬─────────┬──┘
└────────────┘ YES│ │NO
┌───────▼──┐ ┌───▼───────┐
│ range < │ │ QuickSort │
│ 100K? │ │ O(n log n) │
└──┬────┬──┘ └────────────┘
YES│ │NO
┌───────▼┐ ┌▼──────────┐
│Counting│ │ QuickSort │
│Sort │ │ O(n log n) │
│O(n+k) │ └────────────┘
└────────┘
Why This Matters: The user never has to think about which algorithm is best. The system analyzes the data and picks the optimal one. Sorting by “Name (A-Z)” on 200 items uses QuickSort. Sorting by “Quantity (Low to High)” on the same data uses Counting Sort. Sorting 30 items by anything uses Insertion Sort. The selection is transparent and automatic.
Sort Criteria
The SortCriteria enum defines nine sorting options exposed to the user:
| Criterion | Display Name | Direction | Algorithm Used (n > 50) |
|---|---|---|---|
NAME_ASC |
Name (A-Z) | Ascending | QuickSort |
NAME_DESC |
Name (Z-A) | Descending | QuickSort |
QUANTITY_ASC |
Quantity (Low to High) | Ascending | Counting Sort |
QUANTITY_DESC |
Quantity (High to Low) | Descending | Counting Sort |
PRICE_ASC |
Price (Low to High) | Ascending | QuickSort |
PRICE_DESC |
Price (High to Low) | Descending | QuickSort |
DATE_ADDED_ASC |
Oldest First | Ascending | QuickSort |
DATE_ADDED_DESC |
Newest First | Descending | QuickSort |
LOW_STOCK_FIRST |
Low Stock Priority | Ascending | QuickSort |
The comparison logic handles all criteria through a single compare() method with null-safe string comparison, case-insensitive name sorting, and automatic ascending/descending reversal:
private static int compare(InventoryItem a, InventoryItem b, SortCriteria criteria) {
int result;
switch (criteria) {
case NAME_ASC:
case NAME_DESC:
String nameA = a.getName() != null ? a.getName() : "";
String nameB = b.getName() != null ? b.getName() : "";
result = nameA.compareToIgnoreCase(nameB);
break;
case QUANTITY_ASC:
case QUANTITY_DESC:
result = Integer.compare(a.getQuantity(), b.getQuantity());
break;
case PRICE_ASC:
case PRICE_DESC:
result = Double.compare(a.getPrice(), b.getPrice());
break;
case DATE_ADDED_ASC:
case DATE_ADDED_DESC:
result = Long.compare(a.getCreatedAt(), b.getCreatedAt());
break;
case LOW_STOCK_FIRST:
int deficitA = a.getMinStockLevel() - a.getQuantity();
int deficitB = b.getMinStockLevel() - b.getQuantity();
result = Integer.compare(deficitB, deficitA); // Higher deficit first
break;
default:
result = 0;
}
if (!criteria.isAscending()) result = -result;
return result;
}
UI Integration
Sort Dialog
Users access sorting through a toolbar menu button that opens an AlertDialog with single-choice radio buttons for all nine criteria. The currently active sort is pre-selected.
// InventoryActivity.java - Sort dialog with all options
private void showSortDialog() {
String[] options = SortCriteria.getAllDisplayNames();
int currentIndex = currentSortCriteria.ordinal();
new AlertDialog.Builder(this)
.setTitle(R.string.sort_by)
.setSingleChoiceItems(options, currentIndex, (dialog, which) -> {
SortCriteria selected = SortCriteria.values()[which];
applySortCriteria(selected);
dialog.dismiss();
})
.setNegativeButton(R.string.cancel, null)
.show();
}
Performance Feedback
After every sort, a Toast message tells the user what happened:
private void applySortCriteria(SortCriteria criteria) {
long startTime = System.nanoTime();
InventorySortManager.sort(filteredItems, criteria);
adapter.notifyDataSetChanged();
long durationMs = (System.nanoTime() - startTime) / 1_000_000;
String algorithm = InventorySortManager.getLastAlgorithmUsed();
String message = String.format("Sorted by %s in %dms (%s)",
criteria.getDisplayName(), durationMs, algorithm);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
Example toast: “Sorted by Quantity (Low to High) in 4ms (CountingSort)”
Sort Preference Persistence
The user’s selected sort criterion is saved to SharedPreferences and restored on app launch, so the preferred sort order persists across sessions.
Real-Time Search Filtering
A SearchView in the toolbar enables live filtering by item name or description. Search results maintain the current sort order - the search filter runs first, then InventorySortManager.sort() is applied to the filtered results:
private void applySearchFilter() {
filteredItems.clear();
if (currentSearchQuery == null || currentSearchQuery.trim().isEmpty()) {
filteredItems.addAll(items);
} else {
String query = currentSearchQuery.toLowerCase().trim();
for (InventoryItem item : items) {
boolean matchesName = item.getName() != null &&
item.getName().toLowerCase().contains(query);
boolean matchesDesc = item.getDescription() != null &&
item.getDescription().toLowerCase().contains(query);
if (matchesName || matchesDesc) filteredItems.add(item);
}
}
InventorySortManager.sort(filteredItems, currentSortCriteria);
adapter.notifyDataSetChanged();
}
Performance: Before vs After
Sort Operation (1000 items)
| Operation | Before (Database) | After (In-Memory) | Improvement |
|---|---|---|---|
| Sort by Name | ~60ms (new SQL query + transfer + UI) | ~14ms (QuickSort + UI) | 4.3x faster |
| Sort by Quantity | ~60ms (new SQL query + transfer + UI) | ~6ms (CountingSort + UI) | 10x faster |
| Re-sort (change criteria) | ~60ms (full database round-trip) | ~12ms (in-memory only) | 5x faster |
Scalability
| Dataset Size | In-Memory Sort Time | Algorithm Selected |
|---|---|---|
| 10 items | < 1ms | Insertion Sort |
| 50 items | < 3ms | QuickSort / Counting Sort |
| 100 items | < 5ms | QuickSort / Counting Sort |
| 500 items | < 10ms | QuickSort / Counting Sort |
| 1,000 items | < 20ms | QuickSort / Counting Sort |
| 5,000 items | < 80ms | QuickSort / Counting Sort |
Key Architectural Improvement
Before: Every sort change required a round-trip to the SQLite database - executing a new query, deserializing results, rebuilding the in-memory list, and refreshing the entire UI.
After: Data is loaded from the database once. All subsequent sorting happens in-memory on the existing ArrayList<InventoryItem>, eliminating database overhead entirely. The sort operation modifies the list in-place (QuickSort, Insertion Sort) or rebuilds it from buckets (Counting Sort), then a single adapter.notifyDataSetChanged() updates the grid.
Data Structures Used
ArrayList<InventoryItem>
The primary data structure for the inventory list. Chosen for:
- O(1) random access by index (critical for RecyclerView adapter’s
onBindViewHolder) - In-place modification support for QuickSort and Insertion Sort via
list.set()andCollections.swap() - Dynamic sizing for Counting Sort’s rebuild phase using
clear()andaddAll()
ArrayList<List<InventoryItem>> (Counting Sort Buckets)
Used in Counting Sort as the bucket array. Each index represents a quantity value, and the bucket at that index holds all items with that quantity. This enables O(n + k) sorting by avoiding comparisons entirely.
HashMap<Long, String> (Adapter Caches)
The InventoryAdapter uses three HashMap caches for category, supplier, and location names:
private final Map<Long, String> categoryCache = new HashMap<>();
private final Map<Long, String> supplierCache = new HashMap<>();
private final Map<Long, String> locationCache = new HashMap<>();
These provide O(1) lookup by ID when binding each grid card, avoiding repeated database queries during scrolling. Pre-loaded once in the constructor, refreshed only when reference data changes.
Skills Demonstrated
1. Algorithm Design & Implementation
- Implemented four distinct sorting algorithms from scratch (not using
Collections.sort()) - Applied divide-and-conquer paradigm (QuickSort, MergeSort)
- Applied non-comparison sorting paradigm (Counting Sort)
- Designed recursive algorithms with proper base cases
2. Complexity Analysis
- Analyzed time and space complexity for all four algorithms using Big-O notation
- Understood best/average/worst case distinctions (QuickSort O(n log n) average vs O(n^2) worst)
- Recognized when O(n + k) linear-time sorting outperforms O(n log n) comparison sorts
- Applied threshold-based selection (Insertion Sort for n < 50 despite O(n^2))
3. Data Structure Selection
- Chose
ArrayListfor O(1) indexed access required by RecyclerView - Chose
HashMapfor O(1) cached lookups in the adapter - Used bucket lists in Counting Sort for O(n + k) distribution sorting
- Understood trade-offs: ArrayList’s O(n) removal vs O(1) access
4. Software Engineering Practices
- Separation of concerns:
InventorySortManageris a standalone static utility class, completely independent from UI code - Single Responsibility Principle: Sorting logic lives in one class, sort criteria definitions in another, UI integration in the activity
- Encapsulation: All algorithm implementations are
private; onlysort()andmergeSort()are public - Defensive programming: Null checks, empty list handling, negative quantity guards, range validation for Counting Sort
5. Problem-Solving & Optimization
- Identified the performance bottleneck (unnecessary database round-trips for re-sorting)
- Moved sorting from database layer to application layer for ~5x improvement
- Implemented smart algorithm selection that adapts to data characteristics automatically
- Added
sortWithAlgorithm()for benchmarking and testing individual algorithms
Course Outcomes Alignment
Outcome 3: Design and Evaluate Computing Solutions Using Algorithmic Principles
This enhancement is fundamentally about algorithmic principles:
- Designed four sorting algorithms tailored to inventory management
- Evaluated their complexity to determine when each is optimal
- Implemented a decision system that selects algorithms based on data characteristics
- Measured empirical performance against theoretical complexity predictions
Outcome 4: Demonstrate an Ability to Use Well-Founded Techniques
- QuickSort, MergeSort, and Counting Sort are industry-standard algorithms used in production systems worldwide
- The modular
SortManager+SortCriteriaarchitecture follows established design patterns - Algorithm selection thresholds (n < 50, k < 100,000) are based on well-documented performance characteristics
Outcome 5: Develop a Security Mindset
- Input validation on all public methods (null checks, empty list guards)
- Bounded memory usage (Counting Sort validates range before allocating buckets)
- Defensive handling of edge cases (negative quantities, missing names)
- No external/user input reaches algorithm internals without validation
Outcome 2: Design, Develop, and Deliver Professional-Quality Communications
- Comprehensive JavaDoc on all public methods with parameter descriptions and complexity annotations
- This document itself demonstrates technical communication of algorithmic concepts
- Performance feedback in the UI communicates algorithm behavior to end users
Conclusion
This enhancement transformed the inventory management application from a system with zero sorting capability into one with a professional-grade sorting engine. The original single-order database query was replaced by four sorting algorithms operating across nine criteria with automatic optimization.
Before: 1 sort option, database-dependent, ~60ms per sort, no user control.
After: 9 sort options, in-memory sorting, ~6-14ms per sort, persistent user preferences, real-time search with sorted results.
The implementation demonstrates proficiency in algorithm design (divide-and-conquer, non-comparison sorting), complexity analysis (Big-O for time and space), data structure selection (ArrayList, HashMap, bucket arrays), and software engineering best practices (modularity, encapsulation, defensive programming).