C# - Recursive Data Structures

Recursive data structures are data structures that contain references to instances of the same type within themselves. These structures create a kind of tree-like arrangement or hierarchical relationship, where each individual part can have smaller parts of the same kind, making a repeating pattern. Linked lists and trees are the two most common examples of recursive data structures.

Recursive Data Structure: Linked List

A linked list in C# is a data structure used to store a collection of elements, where each element (node) contains both the data and a reference (link) to the next element in the list. Linked lists are dynamic in size and allow for efficient insertion and removal of elements at any position within the list. The concept of a linked list exhibits inherent recursion, as it involves nodes, each of which holds a reference to another node within the linked list structure.


public class Node
{
    public int data;
    public Node next;

    public Node(int data)
    {
        this.data = data;
        this.next = null;
    }
}

Recursive Data Structure: Trees

Trees are hierarchical data structures and they are consist of nodes. These nodes connected by edges. Each node may or may not have child nodes, forming a recursive tree structure. One common type of tree is the binary tree, where each node has at most two child nodes: left and right.


public class TreeNode
{
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int val)
    {
        this.val = val;
        this.left = this.right = null;
    }
}

Recursive Operations on Data Structures:

Because recursive data structures use references to the same type of thing, people often use recursive actions to do things with these structures. These actions can include going through them, looking for something, putting something in, taking something out, and other similar tasks. Below are examples of recursive operations on linked lists and binary trees:

Recursive Traversal of a Linked List:


public void PrintLinkedList(Node head)
{
    if (head == null)
        return;

    Console.Write(head.data + " ");
    PrintLinkedList(head.next);
}

Recursive Traversal of a Binary Tree (In-order Traversal):


public void InOrderTraversal(TreeNode root)
{
    if (root == null)
        return;

    InOrderTraversal(root.left);
    Console.Write(root.val + " ");
    InOrderTraversal(root.right);
}

Recursive Search in a Linked List:


public Node Search(Node head, int key)
{
    if (head == null || head.data == key)
        return head;

    return Search(head.next, key);
}

Recursive Search in a Binary Tree:


public TreeNode Search(TreeNode root, int key)
{
    if (root == null || root.val == key)
        return root;

    if (key < root.val)
        return Search(root.left, key);
    else
        return Search(root.right, key);
}

Recursive Insertion in a Binary Search Tree:


public TreeNode Insert(TreeNode root, int key)
{
    if (root == null)
        return new TreeNode(key);

    if (key < root.val)
        root.left = Insert(root.left, key);
    else
        root.right = Insert(root.right, key);

    return root;
}

In recursive operations, the function calls itself with a modified version of the data structure (e.g., head.next for linked lists or root.left or root.right for trees) to solve the problem for a smaller instance of the structure. Using recursive actions is a smart approach to handle recursive data structures, and they are frequently used in algorithms and when creating data structures.