Linear Data Structure
A linear data structure is a type of data structure where data elements are arranged in a sequential manner, with each element having at most one predecessor and one successor.
Main Types
1. Array
- Contiguous memory space
- Fixed size
- Direct access through index
- Time complexity:
- Access: O(1)
- Insert/Delete: O(n)
Visualization:
Memory Layout:
[0] [1] [2] [3] [4] <- Indices
5 2 8 1 9 <- Values
2. Linked List
- Non-contiguous memory space
- Dynamic size
- Connected through pointers
- Time complexity:
- Access: O(n)
- Insert/Delete: O(1)
Types of Linked Lists
- Singly Linked List:
[5] -> [2] -> [8] -> [1] -> [9] -> null - Doubly Linked List:
null <- [5] <-> [2] <-> [8] <-> [1] <-> [9] -> null - Circular Linked List:
[5] -> [2] -> [8] -> [1] -> [9] -┐ ^ | └────────────────────────────────┘
3. Stack
- Last In First Out (LIFO)
- Main operations:
- Push
- Pop
- Peek
- Time complexity: O(1)
Visualization:
[9] <- Top (Last In)
[1]
[8]
[2]
[5] <- Bottom (First In)
4. Queue
- First In First Out (FIFO)
- Main operations:
- Enqueue
- Dequeue
- Front
- Time complexity: O(1)
Visualization:
Front -> [5] [2] [8] [1] [9] <- Rear
Applications
- Array:
- Random access required
- Fixed data size
- Memory efficiency needed
- Linked List:
- Frequent insertions and deletions
- Unknown data size
- No random access needed
- Stack:
- Function calls
- Expression evaluation
- Parenthesis matching
- Queue:
- Task scheduling
- Message queues
- Breadth-first search
Implementation Examples
Array Implementation
// Array example
int[] array = new int[]{1, 2, 3, 4, 5};
System.out.println(array[0]); // Access element
// Dynamic array (ArrayList)
ArrayList<Integer> dynamicArray = new ArrayList<>();
dynamicArray.add(1);
dynamicArray.add(2);
dynamicArray.add(3);
Linked List Implementation
// Node definition
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
this.next = null;
}
}
// Linked List implementation
class LinkedList {
private ListNode head;
public LinkedList() {
head = null;
}
public void add(int value) {
ListNode newNode = new ListNode(value);
if (head == null) {
head = newNode;
return;
}
ListNode current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
}
Stack Implementation
class Stack {
private ArrayList<Integer> elements;
public Stack() {
elements = new ArrayList<>();
}
public void push(int value) {
elements.add(value);
}
public int pop() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
return elements.remove(elements.size() - 1);
}
public int peek() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
return elements.get(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
}
Queue Implementation
class Queue {
private ArrayList<Integer> elements;
public Queue() {
elements = new ArrayList<>();
}
public void enqueue(int value) {
elements.add(value);
}
public int dequeue() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
return elements.remove(0);
}
public int front() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
return elements.get(0);
}
public boolean isEmpty() {
return elements.isEmpty();
}
}
// Using Java's built-in Queue interface
Queue<Integer> builtInQueue = new LinkedList<>();
builtInQueue.offer(1); // Enqueue
builtInQueue.poll(); // Dequeue
builtInQueue.peek(); // Front
Summary
Linear data structures are fundamental in computer science, each with its own characteristics and use cases. Choosing the appropriate linear data structure is crucial for program efficiency and performance. In Java, we can use built-in collection frameworks (such as ArrayList, LinkedList) or implement these data structures ourselves.
Array vs Linked List: Key Differences
Memory Allocation
Array: Contiguous Memory Space
[0x1000][0x1004][0x1008][0x1012][0x1016] <- Contiguous Memory Addresses
5 2 8 1 9
Linked List: Non-contiguous Memory Space
[0x2000] -> [0x3000] -> [0x4000] -> [0x5000] -> [0x6000]
5 2 8 1 9
Key Advantages of Linked Lists
- Dynamic Size
- Array: Fixed size at creation, requires reallocation for expansion
- Linked List: Can grow dynamically, no need for pre-allocation
- Insertion and Deletion Efficiency
- Array: Requires shifting elements for middle insertions/deletions
Array Insertion Example: [5][2][8][1][9] -> Insert 7 at position 2 [5][2][7][8][1][9] -> Need to shift 8,1,9 - Linked List: Only needs to change pointers
Linked List Insertion Example: [5] -> [2] -> [8] -> [1] -> [9] [5] -> [2] -> [7] -> [8] -> [1] -> [9] -> Only two pointer changes needed
- Array: Requires shifting elements for middle insertions/deletions
- Memory Utilization
- Array: Must allocate full size even if partially used
- Linked List: Allocates only what’s needed, no memory waste
Real-world Applications
- Text Editor
- Uses linked list to store text for easy character insertion/deletion
- Each character as a node allows easy editing at any position
- Music Player
- Playlist implementation using linked list
- Easy to add, remove, and reorder songs
- Supports loop playback (using circular linked list)
- Undo Functionality
- Implements undo stack using linked list
- Each operation as a node
- Easy to backtrack to previous states
- Memory Management
- Operating systems use linked lists to manage free memory blocks
- Efficient memory allocation and deallocation
- Graph Algorithms
- Adjacency lists use linked lists to store graph edges
- Space-efficient for sparse graphs
When to Choose Array?
- Random Access Needed
- Array: O(1) time access to any element
- Linked List: O(n) time access to any element
- Fixed Size Data
- Array: More memory efficient
- Linked List: Has additional pointer overhead
- Cache-friendly
- Array: Contiguous memory, high cache hit rate
- Linked List: Non-contiguous memory, low cache hit rate
Practical Java Example
public class ArrayVsLinkedList {
public static void main(String[] args) {
// 1. Array Example
System.out.println("Array Operations:");
int[] array = new int[5];
array[0] = 5;
array[1] = 2;
array[2] = 8;
array[3] = 1;
array[4] = 9;
// Inserting in the middle of array (inefficient)
int[] newArray = new int[6];
System.arraycopy(array, 0, newArray, 0, 2); // Copy first 2 elements
newArray[2] = 7; // Insert new element
System.arraycopy(array, 2, newArray, 3, 3); // Copy remaining elements
array = newArray;
// 2. LinkedList Example
System.out.println("\nLinkedList Operations:");
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(5);
linkedList.add(2);
linkedList.add(8);
linkedList.add(1);
linkedList.add(9);
// Inserting in the middle of linked list (efficient)
linkedList.add(2, 7); // Insert 7 at index 2
// 3. Performance Comparison
System.out.println("\nPerformance Comparison:");
// Array access time
long startTime = System.nanoTime();
int element = array[3]; // Random access
long endTime = System.nanoTime();
System.out.println("Array random access time: " + (endTime - startTime) + " ns");
// LinkedList access time
startTime = System.nanoTime();
element = linkedList.get(3); // Sequential access
endTime = System.nanoTime();
System.out.println("LinkedList access time: " + (endTime - startTime) + " ns");
// 4. Memory Usage
System.out.println("\nMemory Usage:");
System.out.println("Array size: " + array.length + " elements");
System.out.println("LinkedList size: " + linkedList.size() + " elements");
// 5. Real-world Example: Text Editor
System.out.println("\nText Editor Example:");
LinkedList<Character> textBuffer = new LinkedList<>();
// Simulating text insertion
String text = "Hello";
for (char c : text.toCharArray()) {
textBuffer.add(c);
}
// Inserting text in the middle
textBuffer.add(2, 'X');
// Displaying the result
System.out.print("Text after insertion: ");
for (char c : textBuffer) {
System.out.print(c);
}
System.out.println(); // Output: HeXllo
}
}
This example demonstrates:
- Array Operations:
- Fixed size initialization
- Inefficient middle insertion (requires array copying)
- Fast random access
- LinkedList Operations:
- Dynamic size
- Efficient middle insertion
- Sequential access
- Performance Comparison:
- Measures access time for both structures
- Shows the trade-off between random and sequential access
- Memory Usage:
- Shows the size difference between array and linked list
- Real-world Application:
- Simulates a simple text editor using linked list
- Demonstrates efficient text insertion
The output will show the performance differences and practical usage of both data structures. This helps understand when to use each structure in real applications.