Arrays are fixed-size, contiguous collections of elements. C# also provides List
using System;
using System.Collections.Generic;
public class ArrayExample {
public static void Main() {
// Fixed-size array
int[] arr = new int[5];
try {
arr[0] = 10; // Add
Console.WriteLine($"Array access: {arr[0]}"); // Access
} catch (IndexOutOfRangeException) {
Console.WriteLine("Error: Array index out of bounds.");
}
// Dynamic List
List list = new List { 1, 2, 3 };
list.Add(4); // Add
if (list.Contains(2)) list.Remove(2); // Remove
list[1] = 5; // Modify
Console.WriteLine("List contents: " + string.Join(", ", list));
}
}
Strings in C# are immutable sequences of characters. StringBuilder is used for efficient string manipulation. Time Complexity: Concatenation O(n), Contains O(n), Replace O(n). Space Complexity: O(n) for new strings, O(1) for StringBuilder operations. Use Cases: Text processing, parsing, formatting. Advantages: Rich built-in methods, thread-safe (immutable). Disadvantages: Immutability causes overhead for frequent modifications.
using System;
using System.Text;
public class StringExample {
public static void Main() {
string s = "hello";
if (string.IsNullOrEmpty(s)) throw new ArgumentException("String cannot be null or empty.");
string upper = s.ToUpper();
bool contains = s.Contains("ell");
string replaced = s.Replace("e", "a");
StringBuilder sb = new StringBuilder("hello");
sb.Append(" world");
sb.Replace("world", "C#");
Console.WriteLine($"Upper: {upper}, Contains 'ell': {contains}, Replaced: {replaced}");
Console.WriteLine($"StringBuilder: {sb}");
}
}
A singly linked list is a linear data structure where each node contains data and a reference to the next node. Time Complexity: Access O(n), Add/Remove at head O(1), Add/Remove at end O(n). Space Complexity: O(n) for n nodes. Use Cases: Dynamic data, implementing stacks/queues, memory-efficient insertions. Advantages: Dynamic size, efficient insertions/deletions at head. Disadvantages: Slow access, no random access.
using System;
public class Node {
public int Data { get; set; }
public Node Next { get; set; }
public Node(int data) => Data = data;
}
public class SinglyLinkedList {
private Node head;
public void Add(int data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
} else {
Node temp = head;
while (temp.Next != null) temp = temp.Next;
temp.Next = newNode;
}
}
public bool Remove(int key) {
if (head == null) return false;
if (head.Data == key) {
head = head.Next;
return true;
}
Node temp = head;
while (temp.Next != null && temp.Next.Data != key) {
temp = temp.Next;
}
if (temp.Next == null) return false;
temp.Next = temp.Next.Next;
return true;
}
public void Print() {
Node temp = head;
if (temp == null) Console.WriteLine("List is empty.");
else {
while (temp != null) {
Console.Write(temp.Data + " ");
temp = temp.Next;
}
Console.WriteLine();
}
}
public static void Main() {
SinglyLinkedList list = new SinglyLinkedList();
list.Add(1);
list.Add(2);
list.Add(3);
Console.WriteLine("Original list:");
list.Print();
list.Remove(2);
Console.WriteLine("After removing 2:");
list.Print();
}
}
A doubly linked list has nodes with references to both the next and previous nodes, allowing bidirectional traversal. Time Complexity: Access O(n), Add/Remove at head/tail O(1), Add/Remove elsewhere O(n). Space Complexity: O(n) for n nodes. Use Cases: Browser history, undo/redo functionality. Advantages: Bidirectional traversal, efficient insertions/deletions at both ends. Disadvantages: Extra memory for prev pointers, no random access.
using System;
public class DNode {
public int Data { get; set; }
public DNode Prev { get; set; }
public DNode Next { get; set; }
public DNode(int data) => Data = data;
}
public class DoublyLinkedList {
private DNode head;
public void AddFront(int data) {
DNode newNode = new DNode(data);
newNode.Next = head;
if (head != null) head.Prev = newNode;
head = newNode;
}
public void AddLast(int data) {
DNode newNode = new DNode(data);
if (head == null) {
head = newNode;
} else {
DNode temp = head;
while (temp.Next != null) temp = temp.Next;
temp.Next = newNode;
newNode.Prev = temp;
}
}
public bool Remove(int key) {
if (head == null) return false;
DNode current = head;
while (current != null && current.Data != key) current = current.Next;
if (current == null) return false;
if (current == head) {
head = head.Next;
if (head != null) head.Prev = null;
} else {
current.Prev.Next = current.Next;
if (current.Next != null) current.Next.Prev = current.Prev;
}
return true;
}
public void TraverseForward() {
DNode temp = head;
if (temp == null) Console.WriteLine("List is empty.");
else {
while (temp != null) {
Console.Write(temp.Data + " ");
temp = temp.Next;
}
Console.WriteLine();
}
}
public static void Main() {
DoublyLinkedList list = new DoublyLinkedList();
list.AddFront(1);
list.AddLast(2);
list.AddLast(3);
Console.WriteLine("Original list:");
list.TraverseForward();
list.Remove(2);
Console.WriteLine("After removing 2:");
list.TraverseForward();
}
}
A stack is a LIFO (Last-In-First-Out) data structure. C# provides Stack, but a custom implementation can use an array or linked list. Time Complexity: Push/Pop O(1), Peek O(1). Space Complexity: O(n) for n elements. Use Cases: Undo functionality, expression evaluation, backtracking. Advantages: Simple, fast operations. Disadvantages: Limited access (only top element).
using System;
public class CustomStack {
private T[] items;
private int top;
private int capacity;
public CustomStack(int size = 10) {
capacity = size;
items = new T[capacity];
top = -1;
}
public void Push(T item) {
if (top + 1 == capacity) {
Array.Resize(ref items, capacity * 2);
capacity *= 2;
}
items[++top] = item;
}
public T Pop() {
if (top < 0) throw new InvalidOperationException("Stack is empty.");
T item = items[top--];
return item;
}
public T Peek() {
if (top < 0) throw new InvalidOperationException("Stack is empty.");
return items[top];
}
public bool IsEmpty => top < 0;
public static void Main() {
CustomStack stack = new CustomStack();
stack.Push(1);
stack.Push(2);
Console.WriteLine($"Top: {stack.Peek()}");
Console.WriteLine($"Popped: {stack.Pop()}");
Console.WriteLine($"Popped: {stack.Pop()}");
Console.WriteLine($"Is empty: {stack.IsEmpty}");
}
}
A queue is a FIFO (First-In-First-Out) data structure. A circular queue reuses space by wrapping around the array. Time Complexity: Enqueue/Dequeue O(1), Peek O(1). Space Complexity: O(n) for n elements. Use Cases: Task scheduling, breadth-first search, buffering. Advantages: Efficient for sequential processing, circular queue optimizes space. Disadvantages: Fixed size (circular queue), no random access.
using System;
public class CircularQueue {
private T[] items;
private int front, rear, count, capacity;
public CircularQueue(int size = 10) {
capacity = size;
items = new T[capacity];
front = 0;
rear = -1;
count = 0;
}
public void Enqueue(T item) {
if (count == capacity) throw new InvalidOperationException("Queue is full.");
rear = (rear + 1) % capacity;
items[rear] = item;
count++;
}
public T Dequeue() {
if (count == 0) throw new InvalidOperationException("Queue is empty.");
T item = items[front];
front = (front + 1) % capacity;
count--;
return item;
}
public T Peek() {
if (count == 0) throw new InvalidOperationException("Queue is empty.");
return items[front];
}
public static void Main() {
CircularQueue queue = new CircularQueue(3);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
Console.WriteLine($"Peek: {queue.Peek()}");
Console.WriteLine($"Dequeued: {queue.Dequeue()}");
queue.Enqueue(4);
Console.WriteLine($"Dequeued: {queue.Dequeue()}");
}
}
A binary tree is a hierarchical structure where each node has at most two children. Time Complexity: Insert/Find O(h) average, O(n) worst (h = height). Space Complexity: O(n) for n nodes. Use Cases: Hierarchical data, search trees, expression trees. Advantages: Efficient searching in balanced trees. Disadvantages: Can degenerate to linked list if unbalanced.
using System;
public class TreeNode {
public int Data { get; set; }
public TreeNode Left { get; set; }
public TreeNode Right { get; set; }
public TreeNode(int data) => Data = data;
}
public class BinaryTree {
public TreeNode Root { get; private set; }
public void Insert(int data) {
Root = InsertRec(Root, data);
}
private TreeNode InsertRec(TreeNode node, int data) {
if (node == null) return new TreeNode(data);
if (data < node.Data) node.Left = InsertRec(node.Left, data);
else if (data > node.Data) node.Right = InsertRec(node.Right, data);
return node;
}
public void InOrderTraversal(TreeNode node) {
if (node == null) return;
InOrderTraversal(node.Left);
Console.Write(node.Data + " ");
InOrderTraversal(node.Right);
}
public static void Main() {
BinaryTree tree = new BinaryTree();
tree.Insert(50);
tree.Insert(30);
tree.Insert(70);
Console.WriteLine("In-order traversal:");
tree.InOrderTraversal(tree.Root);
}
}
A BST is a binary tree where left < node < right, enabling fast searches. Time Complexity: Search/Insert O(h) average, O(n) worst. Space Complexity: O(n) for n nodes. Use Cases: Sorted data storage, dictionary implementations. Advantages: Efficient search, insert, delete in balanced trees. Disadvantages: Unbalanced trees degrade performance.
using System;
public class BSTNode {
public int Data { get; set; }
public BSTNode Left { get; set; }
public BSTNode Right { get; set; }
public BSTNode(int data) => Data = data;
}
public class BinarySearchTree {
public BSTNode Root { get; private set; }
public void Insert(int data) {
Root = InsertRec(Root, data);
}
private BSTNode InsertRec(BSTNode node, int data) {
if (node == null) return new BSTNode(data);
if (data < node.Data) node.Left = InsertRec(node.Left, data);
else if (data > node.Data) node.Right = InsertRec(node.Right, data);
return node;
}
public bool Search(int data) {
return SearchRec(Root, data);
}
private bool SearchRec(BSTNode node, int data) {
if (node == null) return false;
if (data == node.Data) return true;
return data < node.Data ? SearchRec(node.Left, data) : SearchRec(node.Right, data);
}
public static void Main() {
BinarySearchTree bst = new BinarySearchTree();
bst.Insert(50);
bst.Insert(30);
bst.Insert(70);
Console.WriteLine($"Search for 30: {bst.Search(30)}");
Console.WriteLine($"Search for 40: {bst.Search(40)}");
}
}
A heap is a complete binary tree satisfying heap property (min or max). Time Complexity: Insert/Extract O(log n), Peek O(1). Space Complexity: O(n) for n elements. Use Cases: Priority queues, graph algorithms (Dijkstra), median maintenance. Advantages: Efficient priority management. Disadvantages: No efficient search.
using System;
using System.Collections.Generic;
public class MinHeap {
private List heap = new List();
public void Insert(int val) {
heap.Add(val);
HeapifyUp(heap.Count - 1);
}
private void HeapifyUp(int index) {
int parent = (index - 1) / 2;
if (index > 0 && heap[index] < heap[parent]) {
Swap(index, parent);
HeapifyUp(parent);
}
}
public int ExtractMin() {
if (heap.Count == 0) throw new InvalidOperationException("Heap is empty.");
int min = heap[0];
heap[0] = heap[heap.Count - 1];
heap.RemoveAt(heap.Count - 1);
HeapifyDown(0);
return min;
}
private void HeapifyDown(int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int smallest = index;
if (left < heap.Count && heap[left] < heap[smallest]) smallest = left;
if (right < heap.Count && heap[right] < heap[smallest]) smallest = right;
if (smallest != index) {
Swap(index, smallest);
HeapifyDown(smallest);
}
}
private void Swap(int i, int j) {
int temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
public static void Main() {
MinHeap minHeap = new MinHeap();
minHeap.Insert(3);
minHeap.Insert(1);
minHeap.Insert(4);
Console.WriteLine($"Extract Min: {minHeap.ExtractMin()}");
Console.WriteLine($"Extract Min: {minHeap.ExtractMin()}");
}
}
A hash table stores key-value pairs with fast lookups using hashing. Time Complexity: Insert/Search/Delete O(1) average, O(n) worst. Space Complexity: O(n) for n elements. Use Cases: Dictionaries, caching, indexing. Advantages: Constant-time operations. Disadvantages: Hash collisions, memory overhead.
using System;
using System.Collections.Generic;
public class CustomHashTable {
private Dictionary dict = new Dictionary();
public void Insert(TKey key, TValue value) {
if (dict.ContainsKey(key)) dict[key] = value;
else dict.Add(key, value);
}
public TValue Search(TKey key) {
return dict.TryGetValue(key, out TValue value) ? value : default;
}
public bool Delete(TKey key) {
return dict.Remove(key);
}
public static void Main() {
CustomHashTable hashTable = new CustomHashTable();
hashTable.Insert("one", 1);
hashTable.Insert("two", 2);
Console.WriteLine($"Search 'one': {hashTable.Search("one")}");
hashTable.Delete("one");
Console.WriteLine($"Search 'one' after delete: {hashTable.Search("one") == default ? "Not found" : "Found"}");
}
}
A graph is a collection of nodes and edges. Representations: adjacency list/matrix. Time Complexity: BFS/DFS O(V+E). Space Complexity: O(V+E). Use Cases: Networks, social graphs, pathfinding. Advantages: Models relationships. Disadvantages: Complex traversal.
using System;
using System.Collections.Generic;
public class Graph {
private Dictionary> adjList = new Dictionary>();
public void AddEdge(int u, int v) {
if (!adjList.ContainsKey(u)) adjList[u] = new List();
adjList[u].Add(v);
}
public void BFS(int start) {
bool[] visited = new bool[adjList.Keys.Count + 1];
Queue queue = new Queue();
queue.Enqueue(start);
visited[start] = true;
while (queue.Count > 0) {
int node = queue.Dequeue();
Console.Write(node + " ");
foreach (var neighbor in adjList.GetValueOrDefault(node, new List())) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.Enqueue(neighbor);
}
}
}
}
public static void Main() {
Graph g = new Graph();
g.AddEdge(1, 2);
g.AddEdge(1, 3);
g.AddEdge(2, 4);
Console.WriteLine("BFS traversal:");
g.BFS(1);
}
}
QuickSort uses divide-and-conquer with pivot. MergeSort divides and merges. Time Complexity: QuickSort O(n log n) average, O(n^2) worst; MergeSort O(n log n). Space Complexity: QuickSort O(log n), MergeSort O(n). Use Cases: General sorting. Advantages: Efficient for large data. Disadvantages: QuickSort unstable worst-case.
using System;
public class Sorting {
public static void QuickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = Partition(arr, low, high);
QuickSort(arr, low, pi - 1);
QuickSort(arr, pi + 1, high);
}
}
private static int Partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
Swap(arr, i, j);
}
}
Swap(arr, i + 1, high);
return i + 1;
}
private static void Swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void MergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
Merge(arr, left, mid, right);
}
}
private static void Merge(int[] arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[] L = new int[n1];
int[] R = new int[n2];
Array.Copy(arr, left, L, 0, n1);
Array.Copy(arr, mid + 1, R, 0, n2);
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
public static void Main() {
int[] arr = { 5, 3, 8, 4, 2 };
QuickSort(arr, 0, arr.Length - 1);
Console.WriteLine("QuickSort: " + string.Join(", ", arr));
arr = new int[] { 5, 3, 8, 4, 2 };
MergeSort(arr, 0, arr.Length - 1);
Console.WriteLine("MergeSort: " + string.Join(", ", arr));
}
}
Binary search finds elements in sorted arrays by halving the search interval. Time Complexity: O(log n). Space Complexity: O(1). Use Cases: Searching sorted data. Advantages: Fast for large datasets. Disadvantages: Requires sorted array.
using System;
public class Searching {
public static int BinarySearch(int[] arr, int target) {
int left = 0;
int right = arr.Length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
public static void Main() {
int[] arr = { 2, 3, 4, 10, 40 };
int index = BinarySearch(arr, 10);
Console.WriteLine($"Index of 10: {index}");
}
}
Dynamic programming solves complex problems by breaking them into overlapping subproblems. Memoized Fibonacci avoids redundant calculations. Time Complexity: O(n) with memoization, vs O(2^n) without. Space Complexity: O(n) for memoization table. Use Cases: Optimization problems, shortest paths, knapsack. Advantages: Efficient for overlapping subproblems. Disadvantages: Extra memory for memoization.
using System;
using System.Collections.Generic;
public class DynamicProgramming {
private static Dictionary memo = new Dictionary();
public static long Fib(int n) {
if (n < 0) throw new ArgumentException("Input must be non-negative.");
if (n <= 1) return n;
if (memo.TryGetValue(n, out long value)) return value;
value = Fib(n - 1) + Fib(n - 2);
memo[n] = value;
return value;
}
public static void Main() {
Console.WriteLine($"Fib(10): {Fib(10)}");
Console.WriteLine($"Fib(20): {Fib(20)}");
}
}
ICollection
using System;
using System.Collections.Generic;
public class ICollectionExample {
public static void Main() {
ICollection fruits = new List { "Apple", "Banana" };
fruits.Add("Cherry");
fruits.Remove("Apple");
Console.WriteLine("Total: " + fruits.Count);
foreach (var fruit in fruits) Console.WriteLine(fruit);
}
}
IDictionary
using System;
using System.Collections.Generic;
public class IDictionaryExample {
public static void Main() {
IDictionary emp = new Dictionary {
{ 101, "Alice" }, { 102, "Bob" }
};
emp.Add(103, "Eve");
emp.Remove(102);
foreach (var kv in emp) {
Console.WriteLine($"ID: {kv.Key}, Name: {kv.Value}");
}
}
}
Queue
using System;
using System.Collections.Generic;
public class QueueExample {
public static void Main() {
Queue tasks = new Queue();
tasks.Enqueue("Task1");
tasks.Enqueue("Task2");
Console.WriteLine("Next: " + tasks.Peek());
while (tasks.Count > 0) {
Console.WriteLine(tasks.Dequeue());
}
}
}
IList
using System;
using System.Collections.Generic;
public class IListExample {
public static void Main() {
IList colors = new List { "Red", "Blue" };
colors.Insert(1, "Green");
colors[0] = "Yellow";
foreach (var color in colors) {
Console.WriteLine(color);
}
}
}
IEnumerable
using System;
using System.Collections.Generic;
public class IEnumerableExample {
public static void Main() {
IEnumerable numbers = new List { 10, 20, 30 };
foreach (int num in numbers) {
Console.WriteLine(num);
}
}
}
HashSet
using System;
using System.Collections.Generic;
public class HashSetExample {
public static void Main() {
HashSet set = new HashSet { 1, 2, 3 };
set.Add(2); // Ignored
set.Add(4);
foreach (var val in set) {
Console.WriteLine(val);
}
}
}
Stack
using System;
using System.Collections.Generic;
public class StackExample {
public static void Main() {
Stack stack = new Stack();
stack.Push(10);
stack.Push(20);
Console.WriteLine("Top: " + stack.Peek());
while (stack.Count > 0) {
Console.WriteLine(stack.Pop());
}
}
}