Non_Linear_DSA.
md 2024-08-20
🌳 Non-linear Data Structures in Java
1. Introduction to Trees
1.1 What is a Tree?
A tree is a hierarchical data structure that consists of nodes connected by edges. It is a collection of nodes
where each node has a value, and possibly children, which are other nodes. The topmost node is called the
root, and the nodes at the bottom without children are called leaves.
1.2 Types of Trees
Binary Trees: A tree where each node has at most two children, referred to as the left child and the
right child.
Binary Search Trees (BST): A binary tree with the additional property that for any node, all elements in
the left subtree are less than the node, and all elements in the right subtree are greater.
AVL Trees: A self-balancing binary search tree where the difference in heights between the left and
right subtrees cannot be more than one.
Red-Black Trees: A balanced binary search tree where each node has an extra bit for denoting the
color of the node, either red or black, which helps in balancing the tree during insertions and deletions.
2. Binary Trees
2.1 Structure of a Binary Tree
A binary tree is a tree in which each node has at most two children. These children are referred to as the left
child and the right child. A binary tree is defined by:
class Node {
int data;
Node left, right;
public Node(int item) {
data = item;
left = right = null;
}
}
2.2 Traversal Methods
Traversal of a tree means visiting each node exactly once. There are three types of traversal:
Inorder Traversal: Visit left subtree, node, right subtree.
Preorder Traversal: Visit node, left subtree, right subtree.
Postorder Traversal: Visit left subtree, right subtree, node.
1 / 14
Non_Linear_DSA.md 2024-08-20
Example: Inorder Traversal
void inorder(Node node) {
if (node == null)
return;
inorder(node.left);
System.out.print(node.data + " ");
inorder(node.right);
}
Output:
Given the tree:
1
/ \
2 3
/ \
4 5
The Inorder traversal is: 4 2 5 1 3
3. Binary Search Trees (BST)
3.1 Properties of BST
Left Subtree: Contains nodes with values less than the root.
Right Subtree: Contains nodes with values greater than the root.
No Duplicate Nodes: All nodes are distinct.
3.2 Insertion in BST
To insert a node in BST, we start at the root and compare the node's value with the root's value. If the value is
less, we go to the left subtree; if it's more, we go to the right subtree.
Example: Insert in BST
Node insert(Node root, int key) {
if (root == null) {
root = new Node(key);
return root;
}
if (key < root.data)
root.left = insert(root.left, key);
else if (key > root.data)
root.right = insert(root.right, key);
2 / 14
Non_Linear_DSA.md 2024-08-20
return root;
}
Output:
Insert 6 into the BST:
5
/ \
3 7
After insertion:
5
/ \
3 7
/
6
4. AVL Trees
4.1 Introduction to Balanced Trees
AVL trees are self-balancing binary search trees. The height of two child subtrees of any node differs by at
most one.
4.2 Rotations in AVL Trees
Rotations are used to balance the tree when nodes are inserted or deleted. There are four types of rotations:
Left Rotation
Right Rotation
Left-Right Rotation
Right-Left Rotation
4.3 Insertion in AVL Trees
Insertion may cause the tree to become unbalanced, requiring rotations to balance it.
Example: Right Rotation
Node rightRotate(Node y) {
Node x = y.left;
Node T2 = x.right;
x.right = y;
y.left = T2;
return x;
}
3 / 14
Non_Linear_DSA.md 2024-08-20
Output:
Before Rotation:
30
/
20
/
10
After Right Rotation:
20
/ \
10 30
5. Red-Black Trees
5.1 Properties of Red-Black Trees
Each node is either red or black.
The root is always black.
Red nodes cannot have red children (no two red nodes can be adjacent).
Every path from a node to its descendant leaves must have the same number of black nodes.
5.2 Insertion and Balancing in Red-Black Trees
During insertion, we follow the properties of the Red-Black tree and adjust colors and perform rotations if
necessary to maintain the balance.
6. Graphs
6.1 What is a Graph?
A graph is a data structure that consists of a set of vertices (nodes) and a set of edges connecting pairs of
vertices. Graphs can represent many real-world problems like social networks, maps, etc.
6.2 Types of Graphs
Directed Graph: Edges have a direction.
Undirected Graph: Edges have no direction.
Weighted Graph: Edges have weights (or costs).
Unweighted Graph: Edges have no weights.
6.3 Graph Representation
Adjacency Matrix Adjacency List
4 / 14
Non_Linear_DSA.md 2024-08-20
Adjacency Matrix Adjacency List
Uses a 2D array to represent the graph Uses an array of lists
Suitable for dense graphs Suitable for sparse graphs
Easy to check if two nodes are connected Efficient in terms of space
Takes up more memory Takes up less memory
Example: Adjacency Matrix
int graph[][] = new int[][] { { 0, 1, 0, 0 },
{ 1, 0, 1, 0 },
{ 0, 1, 0, 1 },
{ 0, 0, 1, 0 } };
Example: Adjacency List
class Graph {
private LinkedList<Integer> adjLists[];
Graph(int vertices) {
adjLists = new LinkedList[vertices];
for (int i = 0; i < vertices; i++)
adjLists[i] = new LinkedList();
}
void addEdge(int src, int dest) {
adjLists[src].add(dest);
adjLists[dest].add(src);
}
}
6.4 Graph Traversal
6.4.1 Breadth-First Search (BFS)
BFS starts at a selected node and explores all its neighbors before moving to the next level.
Example: BFS Implementation
void BFS(int start) {
boolean visited[] = new boolean[V];
LinkedList<Integer> queue = new LinkedList<Integer>();
visited[start] = true;
queue.add(start);
5 / 14
Non_Linear_DSA.md 2024-08-20
while (queue.size() != 0) {
start = queue.poll();
System.out.print(start + " ");
Iterator<Integer> i = adjLists[start].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
Output:
Given Graph:
0-1-2
| |
3---4
BFS starting from node 0: 0 1 3 2 4
6.4.2 Depth-First Search (DFS)
DFS explores as far as possible along each branch before backtracking.
Example: DFS Implementation
void DFS(int v) {
boolean visited[] = new boolean[V];
DFSUtil(v, visited);
}
void DFSUtil(int v, boolean visited[]) {
visited[v] = true;
System.out.print(v + " ");
Iterator<Integer> i = adjLists[v].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n])
DFSUtil(n, visited);
}
}
Output:
6 / 14
Non_Linear_DSA.md 2024-08-20
Given Graph:
0-1-2
| |
3---4
DFS starting from node 0: 0 1 2 4 3
7. Hash Tables
7.1 Introduction to Hash Tables
A hash table is a data structure that stores key-value pairs. It uses a hash function to compute an index into an
array of buckets or slots, from which the desired value can be found.
7.2 Implementation of a Hash Table
A basic hash table implementation involves storing data in an array, with each index corresponding to a key.
Example: Simple Hash Table
class HashTable {
private int arr[];
private int size;
public HashTable(int size) {
this.size = size;
arr = new int[size];
Arrays.fill(arr, -1); // -1 indicates an empty slot
}
int hashFunction(int key) {
return key % size;
}
void insert(int key) {
int index = hashFunction(key);
while (arr[index] != -1) {
index = (index + 1) % size; // Linear probing for collision resolution
}
arr[index] = key;
}
void display() {
for (int i = 0; i < size; i++) {
System.out.println(i + " -> " + arr[i]);
}
}
}
7 / 14
Non_Linear_DSA.md 2024-08-20
Output:
Insert keys: 12, 22, 42 into a hash table of size 10
Hash Table:
0 -> -1
1 -> -1
2 -> 12
3 -> 22
4 -> 42
5 -> -1
6 -> -1
7 -> -1
8 -> -1
9 -> -1
7.3 Collision Handling
When two keys hash to the same index, a collision occurs. There are several ways to handle collisions:
7.3.1 Chaining
In chaining, each bucket points to a linked list of entries that map to the same bucket. When a collision
occurs, the new entry is simply added to the list.
Example: Chaining
class HashTableChaining {
private LinkedList<Integer>[] table;
public HashTableChaining(int size) {
table = new LinkedList[size];
for (int i = 0; i < size; i++) {
table[i] = new LinkedList<>();
}
}
int hashFunction(int key) {
return key % table.length;
}
void insert(int key) {
int index = hashFunction(key);
table[index].add(key);
}
void display() {
for (int i = 0; i < table.length; i++) {
System.out.print(i + " -> ");
for (int key : table[i]) {
8 / 14
Non_Linear_DSA.md 2024-08-20
System.out.print(key + " ");
}
System.out.println();
}
}
}
Output:
Insert keys: 12, 22, 32 into a hash table of size 10
Hash Table with Chaining:
0 ->
1 ->
2 -> 12 22 32
3 ->
4 ->
5 ->
6 ->
7 ->
8 ->
9 ->
7.3.2 Open Addressing
Open addressing is another method to handle collisions by finding another empty slot within the hash table
array itself. There are several strategies for open addressing:
Linear Probing: If a collision occurs, check the next slot in sequence.
Quadratic Probing: If a collision occurs, check the slots in a quadratic sequence.
Double Hashing: Use a second hash function to determine the step size when a collision occurs.
Difference Between Linear Probing, Quadratic Probing, and Double Hashing
Linear Probing Quadratic Probing Double Hashing
Searches for the next Searches for the next available slot Uses a second hash function to
available slot sequentially. using a quadratic function. determine the next slot.
Simple and easy to Reduces clustering compared to Further reduces clustering and
implement. linear probing. offers better performance.
May cause primary
May cause secondary clustering. Minimizes clustering.
clustering.
Example: index = (hash + Example: index = (hash + i^2) % Example: index = (hash + i *
i) % size size hash2(key)) % size
8. Practice
9 / 14
Non_Linear_DSA.md 2024-08-20
8.1 Implement Tree Traversal Algorithms
Inorder Traversal
void inorder(Node node) {
if (node == null)
return;
inorder(node.left);
System.out.print(node.data + " ");
inorder(node.right);
}
Example Output:
Inorder Traversal of the tree:
1
/ \
2 3
/ \
4 5
Output: 4 2 5 1 3
Preorder Traversal
void preorder(Node node) {
if (node == null)
return;
System.out.print(node.data + " ");
preorder(node.left);
preorder(node.right);
}
Example Output:
Preorder Traversal of the tree:
1
/ \
2 3
/ \
4 5
Output: 1 2 4 5 3
10 / 14
Non_Linear_DSA.md 2024-08-20
Postorder Traversal
void postorder(Node node) {
if (node == null)
return;
postorder(node.left);
postorder(node.right);
System.out.print(node.data + " ");
}
Example Output:
Postorder Traversal of the tree:
1
/ \
2 3
/ \
4 5
Output: 4 5 2 3 1
8.2 Implement Graph Traversal Algorithms
Breadth-First Search (BFS)
void BFS(int start) {
boolean visited[] = new boolean[V];
LinkedList<Integer> queue = new LinkedList<Integer>();
visited[start] = true;
queue.add(start);
while (queue.size() != 0) {
start = queue.poll();
System.out.print(start + " ");
Iterator<Integer> i = adjLists[start].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
11 / 14
Non_Linear_DSA.md 2024-08-20
}
}
Example Output:
BFS traversal starting from vertex 0:
Given Graph:
0-1-2
| |
3---4
Output: 0 1 3 2 4
Depth-First Search (DFS)
void DFS(int v) {
boolean visited[] = new boolean[V];
DFSUtil(v, visited);
}
void DFSUtil(int v, boolean visited[]) {
visited[v] = true;
System.out.print(v + " ");
Iterator<Integer> i = adjLists[v].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n])
DFSUtil(n, visited);
}
}
Example Output:
DFS traversal starting from vertex 0:
Given Graph:
0-1-2
| |
3---4
Output: 0 1 2 4 3
8.3 Solve Problems Using Hash Tables
Problem 1: Detecting Duplicates
12 / 14
Non_Linear_DSA.md 2024-08-20
Given an array of integers, determine if the array contains any duplicates.
Example Solution:
boolean containsDuplicate(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
if (set.contains(num))
return true;
set.add(num);
}
return false;
}
Example Output:
Input: [1, 2, 3, 1]
Output: true
Input: [1, 2, 3, 4]
Output: false
Problem 2: Grouping Anagrams
Given an array of strings, group anagrams together.
Example Solution:
List<List<String>> groupAnagrams(String[] strs) {
if (strs.length == 0) return new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
char[] ca = s.toCharArray();
Arrays.sort(ca);
String key = String.valueOf(ca);
if (!map.containsKey(key)) map.put(key, new ArrayList<>());
map.get(key).add(s);
}
return new ArrayList<>(map.values());
}
Example Output:
Input: ["eat","tea","tan","ate","nat","bat"]
Output: [["eat","tea","ate"],["tan","nat"],["bat"]]
13 / 14
Non_Linear_DSA.md 2024-08-20
Conclusion
This detailed guide covers all the essential concepts, differences, and practical implementations of Non-linear
Data Structures in Java. By following these explanations and code examples, beginners will be able to grasp
the fundamentals and start applying them in real-world scenarios.
14 / 14