Binary Tree :

If there are n nodes, then in total there will be n-1 edges.

  • G, H, I are siblings.
  • Descendants : Set of all those nodes which can be reached from a particular node. For e.g. for B : E F J K M are all descendants.
  • Ancestors : All the nodes along the path of the given node and the root node. For e.g. For M, J F B A are all ancestors.
  • Degree of a node : It is the number of children it is having, direct children and not all descendants. For e.g. degree of A is 3 and degree of B is 2, degree of K is 0.
  • Internal/ Non-Leaf and External/Leaf Nodes : Nodes with degree 0 are called Leaf Nodes. Nodes whose degree > 0 are called Internal/Non-Leaf Nodes.
  • Level : We count the number of nodes in the path :
    Pasted image 20240319132732.png
  • Height : We count the number of edges in the path.
    Pasted image 20240319133048.png
  • Forest : A collection of trees is called a forest.
    Pasted image 20240319150203.png
    Here we can see that in total there are three trees and a collection of trees is called a forest.

A tree of degree 2. Every node can have maximum of 2 children.
Pasted image 20240411125113.png

Number of Binary Trees :

Unlabeled Nodes :

If n nodes are given, then number of Binary Trees are possible :
Pasted image 20240411125953.png

Catalan's Formula : Combinational and Recursive both :

Pasted image 20240411130857.png

Labeled Nodes :

Pasted image 20240411131728.png

Height Vs Nodes :

Max Nodes :

Pasted image 20240411133221.png

Min Nodes:

Pasted image 20240411133521.png

Max Height and Min Height :

Pasted image 20240411140157.png

  • For min and max height we can directly derive the formula from the min and max nodes formulas by moving h to the left side of the = sign.

In Short :

Pasted image 20240411140651.png

Relationship between degree(2) and degree(0) :

Pasted image 20240411141342.png

  • : this is always true for a binary tree.

Strict Binary Tree :

Also called a Strict/Proper/Complete Binary Tree.

  • Each node will have either 0 or 2 nodes only.
    Pasted image 20240411142047.png
  • Pasted image 20240411142236.png

Height Vs Nodes :

Pasted image 20240411142540.png

Internal vs External (Leaf) Nodes :

Pasted image 20240413153307.png

SKIPPED : 274 to 275 : n-ary Trees

Representation of a Binary Tree :

Array Representation :

Pasted image 20240413154045.png

  • Parent of any node at index i will be at index
    Pasted image 20240413154416.png

We can fill the elements in the array level-by-level, make sure to take into account empty nodes

  • ! Note that the array indexing here starts from 1, we can simply ignore the index 0 and start from 1.

Linked Representation :

Pasted image 20240413164819.png

  • Here notice that the total null pointers will be equal to total nodes + 1. This is similar to the case of strict binary tree where the total number of leaf nodes were equal to total number of internal nodes + 1

Full vs Complete Binary Tree : (Doubt : Full and Strict same or not) :

Full / Strict BT : A binary tree of height h having maximum number of nodes.

Info

RKV Sir :
Fill Binary Tree / Strict Binary Tree : 0 or 2 Child Nodes of a parent
Complete Binary Tree : Left Aligned Nodes hone chahiye, i.e. left side se nodes fill/add honge.

Pasted image 20240413181935.png

  • A tree of height h can have maximum of nodes.
  • Here the 1st BT is a full / strict and a complete binary tree.
  • 2nd BT is a complete binary tree : as there are no empty spaces in between the elements of the array.
    Pasted image 20240421225545.png
    Pasted image 20240421225806.png
  • 3rd BT is not a complete BT : as there are some empty spaces in between the array.
    Pasted image 20240421225826.png

Book Definition :

  • A complete binary tree of height h, will be a full binary tree up to h-1 height. Basically nodes should be left aligned, i.e. nodes should start filling from the left side. Also, all the leaf nodes should be at the same level.
  • $ A Full BT is always a complete BT. // WRONG

Strict vs Complete :

Pasted image 20240421230214.png

Tree Traversals : Preorder, Inorder & Postorder

Pasted image 20240413193345.png

Tip

Preorder : NLR
Inorder : LNR
Postorder : LRN

Example :

  • Ex 1 :
    Pasted image 20240413194629.png

Tree Traversal Tricks :

Trick 1 :

Pasted image 20240413205416.png

Trick 2 :

Pasted image 20240413210619.png

Trick 3 :

Pre :

Pasted image 20240413210800.png

  • Note the direction the index finger is pointing to and note down the nodes which are being pointed to.

In :

Pasted image 20240413211021.png

Post :

Pasted image 20240413211252.png

Creating a Binary Tree :

  • -1 as input value will mean that the node is empty / it doesn't exist.
    Pasted image 20240418001513.png
    Pasted image 20240418001900.png
    Abdul Bari's approach to creating a binary tree using a queue and linked list involves the process of level-order traversal. Here's a step-by-step explanation of how to implement this approach:
  1. Create Node Structure: Define a structure to represent each node in the binary tree. Each node should contain data and pointers to its left and right children.

  2. Create Queue Structure: Define a structure to represent the queue. Each node in the queue will contain a pointer to a tree node and a pointer to the next node in the queue.

  3. Enqueue Root: Create the root node of the binary tree and enqueue it into the queue.

  4. Loop Until Queue is Empty:

    • Dequeue a node from the front of the queue.
    • Prompt the user to enter the left child of the dequeued node. If the input is valid, create a node for it and enqueue it.
    • Prompt the user to enter the right child of the dequeued node. If the input is valid, create a node for it and enqueue it.
  5. Repeat: Repeat the above steps until the queue becomes empty.

Pasted image 20240418003746.png

  • Here the red marked portion is for the insertion of left node, we'll have to write a similar code for the right node as well.
#include <bits/stdc++.h>
#include <iostream>
using namespace std;

typedef struct Node Node;
#define newNode (Node *)malloc(sizeof(Node))
/* -------------------- Queue Starts -------------------- */
struct Node
{
    int data;
    Node *lchild;
    Node *rchild;
};

struct Queue
{
    int size;
    int front;
    int rear;
    Node **Q;
};

void createQueue(Queue *q, int size)
{
    q->size = size;
    q->front = 0; // front and rear should not be at -1 but at 0 instead
    q->rear = 0;
    q->Q = (Node **)malloc(size * sizeof(Node *)); // Array of pointers
}

void enqueue(Queue *q, Node *x)
{
    if ((q->rear + 1) % (q->size) == q->front)
        cout << "Queue is Full\n";
    else
    {
        q->rear = (q->rear + 1) % (q->size);
        q->Q[q->rear] = x;
    }
}

Node *dequeue(Queue *q)
{
    Node *x = NULL;
    if (q->front == q->rear)
        cout << "Queue is empty\n";
    else
    {
        q->front = (q->front + 1) % (q->size);
        x = q->Q[q->front];
    }
    return x;
}

void Display(Queue q)
{
    int i = q.front + 1;

    do
    {
        cout << q.Q[i];
        i = (i + 1) % q.size;
    } while (i != (q.rear + 1) % (q.size));
    cout << endl;
}

int isEmpty(struct Queue q)
{
    return q.front == q.rear;
}
/* --------------------- Queue Ends --------------------- */

struct Node *root = NULL;

void create()
{
    struct Node *p, *t;
    int x;
    struct Queue q;
    createQueue(&q, 100);

    printf("Enter Root Value: ");
    scanf("%d", &x);
    root = newNode;
    root->data = x;
    root->lchild = root->rchild = NULL;
    enqueue(&q, root);

    while (!isEmpty(q))
    {
        p = dequeue(&q);
        printf("enter left child of %d: ", p->data);
        scanf("%d", &x);
        if (x != -1)
        {
            t = newNode;
            t->data = x;
            t->lchild = t->rchild = NULL;
            p->lchild = t;
            enqueue(&q, t);
        }
        printf("enter right child of %d: ", p->data);
        scanf("%d", &x);
        if (x != -1)
        {
            t = newNode;
            t->data = x;
            t->lchild = t->rchild = NULL;
            p->rchild = t;
            enqueue(&q, t);
        }
    }
}

void preorder(Node *p)
{
    if (p)
    {
        printf("%d", p->data);
        preorder(p->lchild);
        preorder(p->rchild);
    }
}

void Inorder(struct Node *p)
{
    if (p)
    {
        Inorder(p->lchild);
        printf("%d ", p->data);
        Inorder(p->rchild);
    }
}
void Postorder(struct Node *p)
{
    if (p)
    {
        Postorder(p->lchild);
        Postorder(p->rchild);
        printf("%d ", p->data);
    }
}

int main()
{

    create();
    preorder(root);

    return 0;
}

Pre Order Tree Traversal :

Recursive Approach :

Pasted image 20240420120807.png

Iterative Approach :

  • Stack will be used.
    Pasted image 20240420121615.png

In Order Traversal :

Recursive Approach :

DIY : Easy hai

Iterative Approach

Pasted image 20240420122422.png

Post Order :

Recursive Approach :

Iterative Approach :

This is a little complex.

  • When you are pushing the address of a node for the second time in the stack, we'll have to convert that address to an integer and then store negative of that integer in the stack so as to know if we have to print that node or not. If the address is negative then print the node otherwise continue forward.
    Pasted image 20240420124056.png

Level Order Traversal :

Nodes will be printed level by level from left to right.

  • Queue will be required.
  • First insert the root node into the Queue and print it's data.
  • Then, dequeue the queue and visit it's left child, print it's data and then visit the right child and print it's data and enqueue it to the queue.
    Pasted image 20240420130949.png

Generating Trees from given Order/s :

  • If only one type of order is given then total number of possible tree formations are :
    Pasted image 20240420141330.png

  • If Preorder + Postorder is given then more than one (i.e. 1+) trees are possible.

  • Preorder + Inorder and Postorder + Inorder will give only 1 tree structure.

  • Pasted image 20240420142552.png

  • Pasted image 20240420143308.png

Binary Search Trees :

  • No Duplicates
  • In Order Traversal : Gives sorted list of elements
  • How many BSTs are possible if we are given some elements :
    • We know that InOrder of this binary tree will give elements in sorted order therefore a total of (2nCn)/(n+1) BSTs are possible.
    • Pasted image 20240420151509.png

Searching in a BST :

Time Complexity is where height is the height of the BST.
Pasted image 20240420173056.png

  • We assume the height of a BST with n number of nodes to be minimum.

Recursive Approach :
Pasted image 20240420173242.png

Iterative Approach :
Pasted image 20240420173646.png

Inserting in BST :

Iterative Approach :

Pasted image 20240420174507.png

Recursive Approach :

Pasted image 20240420193559.png

RKV Sir Lab Question Code :

#include <stdio.h>
#include <stdlib.h>

typedef struct Node Node;
#define newNode (struct Node *)malloc(sizeof(struct Node))

// Similar to question 1
struct Node
{
    int data;
    struct Node *lchild;
    struct Node *rchild;
};

struct Node *createNode(int data)
{
    struct Node *p = newNode;
    if (p != NULL)
    {
        p->data = data;
        p->lchild = p->rchild = NULL;
    }
    return p;
}

struct Node *insertNode(struct Node *root, int data)
{
    if (root == NULL)
    {
        root = createNode(data);
    }
    else if (data <= root->data) // Should be < and not<=
    {
        root->lchild = insertNode(root->lchild, data);
    }
    else // shoule be else if (data > root->data)
    {
        root->rchild = insertNode(root->rchild, data);
    }
    return root;
}

void inorderTraversal(struct Node *root)
{
    if (root != NULL)
    {
        inorderTraversal(root->lchild);
        printf("%d ", root->data);
        inorderTraversal(root->rchild);
    }
}

int main()
{
    struct Node *root = NULL;

    int elements[] = {13, 3, 4, 12, 14, 10, 5, 1, 8, 2, 7, 9, 11, 6, 18};
    int n = sizeof(elements) / sizeof(elements[0]);

    for (int i = 0; i < n; i++)
    {
        root = insertNode(root, elements[i]);
    }

    printf("BST before insertion: ");
    inorderTraversal(root);
    printf("\n");

    // inserting nodes in the BST
    root = insertNode(root, 16);
    root = insertNode(root, 577);

    printf("BST after insertion: ");
    inorderTraversal(root);
    printf("\n");

    return 0;
}
struct Node *insertNode(struct Node *root, int data)
{
    if (root == NULL)
        root = createNode(data);
    else if (data < root->data)
        root->lchild = insertNode(root->lchild, data);
    else if (data > root->data)
        root->rchild = insertNode(root->rchild, data);
    return root;
}

Deleting a Node in BST :

  • For the element that is to be deleted, we'll find the Inorder predecessor and Inorder Successor of that node. Then the deleted Node can be replaced with either of these nodes.
  • Inorder Predecessor of a Node : Right Most child of the left child.
  • Inorder Successor of a Node : Leftmost child of the right most child.
    Pasted image 20240420201033.png
    But what if we are deleting a node with only 1 child or with no child at all.
    Or what if the inorder predecessor or inorder successor have child node/s ? Then we'll have to the replace the inorder successor/predecessor with it's inorder successor or predecessor.
  • Pasted image 20240420201836.png
  • Examples :
    Pasted image 20240420201943.png
#include <iostream>

using namespace std;
 
class Node{
public:
    Node* lchild;
    int data;
    Node* rchild;
};
 
class BST{
private:
    Node* root;
public:
    BST(){ root = nullptr; }
    Node* getRoot(){ return root; }
    void iInsert(int key);
    void Inorder(Node* p);
    Node* iSearch(int key);
    Node* rInsert(Node* p, int key);
    Node* rSearch(Node* p, int key);
    Node* Delete(Node* p, int key);
    int Height(Node* p);
    Node* InPre(Node* p);
    Node* InSucc(Node* p);
};
 
void BST::iInsert(int key) {
 
    Node* t = root;
    Node* p;
    Node* r;
 
    // root is empty
    if (root == nullptr){
        p = new Node;
        p->data = key;
        p->lchild = nullptr;
        p->rchild = nullptr;
        root = p;
        return;
    }
 
    while(t != nullptr){
        r = t;
        if (key < t->data){
            t = t->lchild;
        } else if (key > t->data){
            t = t->rchild;
        } else {
            return;
        }
    }
 
    // Now t points at NULL and r points at insert location
    p = new Node;
    p->data = key;
    p->lchild = nullptr;
    p->rchild = nullptr;
 
    if (key < r->data){
        r->lchild = p;
    } else {
        r->rchild = p;
    }
 
}
 
void BST::Inorder(Node* p) {
    if (p){
        Inorder(p->lchild);
        cout << p->data << ", " << flush;
        Inorder(p->rchild);
    }
}
 
Node* BST::iSearch(int key) {
    Node* t = root;
    while (t != nullptr){
        if (key == t->data){
            return t;
        } else if (key < t->data){
            t = t->lchild;
        } else {
            t = t->rchild;
        }
    }
    return nullptr;
}
 
Node* BST::rInsert(Node *p, int key) {
    Node* t;
    if (p == nullptr){
        t = new Node;
        t->data = key;
        t->lchild = nullptr;
        t->rchild = nullptr;
        return t;
    }
 
    if (key < p->data){
        p->lchild = rInsert(p->lchild, key);
    } else if (key > p->data){
        p->rchild = rInsert(p->rchild, key);
    }
    return p;  // key == p->data?
}
 
Node* BST::rSearch(Node *p, int key) {
    if (p == nullptr){
        return nullptr;
    }
 
    if (key == p->data){
        return p;
    } else if (key < p->data){
        return rSearch(p->lchild, key);
    } else {
        return rSearch(p->rchild, key);
    }
}
 
Node* BST::Delete(Node *p, int key) {
    Node* q;
 
    if (p == nullptr){
        return nullptr;
    }
 
    if (p->lchild == nullptr && p->rchild == nullptr){
        if (p == root){
            root = nullptr;
        }
        delete p;
        return nullptr;
    }
 
    if (key < p->data){
        p->lchild = Delete(p->lchild, key);
    } else if (key > p->data){
        p->rchild = Delete(p->rchild, key);
    } else {
        if (Height(p->lchild) > Height(p->rchild)){
            q = InPre(p->lchild);
            p->data = q->data;
            p->lchild = Delete(p->lchild, q->data);
        } else {
            q = InSucc(p->rchild);
            p->data = q->data;
            p->rchild = Delete(p->rchild, q->data);
        }
    }
    return p;
}
 
int BST::Height(Node *p) {
    int x;
    int y;
    if (p == nullptr){
        return 0;
    }
    x = Height(p->lchild);
    y = Height(p->rchild);
    return x > y ? x + 1 : y + 1;
}
 
Node* BST::InPre(Node *p) {
    while (p && p->rchild != nullptr){
        p = p->rchild;
    }
    return p;
}
 
Node* BST::InSucc(Node *p) {
    while (p && p->lchild != nullptr){
        p = p->lchild;
    }
    return p;
}
 
 
int main() {
 
    BST bst;
 
    // Iterative insert
    bst.iInsert(10);
    bst.iInsert(5);
    bst.iInsert(20);
    bst.iInsert(8);
    bst.iInsert(30);
 
    // Inorder traversal
    bst.Inorder(bst.getRoot());
    cout << endl;
 
    // Iterative search
    Node* temp = bst.iSearch(2);
    if (temp != nullptr){
        cout << temp->data << endl;
    } else {
        cout << "Element not found" << endl;
    }
 
    // Recursive search
    temp = bst.rSearch(bst.getRoot(), 20);
    if (temp != nullptr){
        cout << temp->data << endl;
    } else {
        cout << "Element not found" << endl;
    }
 
    // Recursive insert
    bst.rInsert(bst.getRoot(), 50);
    bst.rInsert(bst.getRoot(), 70);
    bst.rInsert(bst.getRoot(), 1);
    bst.Inorder(bst.getRoot());
    cout << "\n" << endl;
 
    // Inorder predecessor and inorder successor
    BST bs;
    bs.iInsert(5);
    bs.iInsert(2);
    bs.iInsert(8);
    bs.iInsert(7);
    bs.iInsert(9);
    bs.iInsert(1);
 
    temp = bs.InPre(bs.getRoot());
    cout << "InPre: " << temp->data << endl;
 
    temp = bs.InSucc(bs.getRoot());
    cout << "InSucc: " << temp->data << endl;
 
    bs.Inorder(bs.getRoot());
    cout << endl;
 
    // Delete
    bs.Delete(bs.getRoot(), 7);
    bs.Inorder(bs.getRoot());
    
    return 0;
}

AVL Trees :

  • Balance factor of every node should belong to {-1, 0, 1}.
    Pasted image 20240421004922.png

Rotations for Insertion :

To balance a node of an AVL tree we perform rotations.

LL Problem

Pasted image 20240421005511.png

  • Here, notice that we are inserting the node after the left node of unbalanced node to the left side. In short, insertion occurs on "left node ke left side" therefore we call it LL Imbalance.
  • Imagine a thread in place of the tree and pull it.

RR Problem

Pasted image 20240421010031.png

LR Problem

Pasted image 20240421010535.png
Pasted image 20240421011158.png

RL Problem

Pasted image 20240421011302.png

Examples :

1.

Pasted image 20240421012951.png

  • Here, notice that when the balance factor (B) = +1, we'll consider A to be LL Imbalanced
  • Pasted image 20240421013341.png
    • will come on the right hand side of the tree only and at the left side of .

2.

Pasted image 20240421013802.png

  • Rotation : Only the dotted portion will be considered in determining what kind of problem this is. For example if 6 was inserted instead of 4 here then also it'd have been LL problem.

Pasted image 20240421020112.png

3.

Pasted image 20240421021139.png

Deletion in AVL Tree :

Deletion from AVL tree is similar to Deletion from Binary Search Tree :

  • First search for the key that you want to delete, if found delete it. Then either Inorder Successor or Inorder Predecessor will take it's place.
  • Then if any node is imbalanced we'll perform rotations.
  • Pasted image 20240421193809.png

Heap :

  • Heap is a Complete Binary Tree : i.e. when it is represented in the form of an array it'll not have any void spaces / gaps in between the elements.
  • Duplicates are also allowed as this not a BST.
  • Max Heap : Every node should have it's element greater than or equal to it's descendants.
  • Min Heap : Every node should have it's element less than or equal to it's descendants.
  • Heap is mostly represented using arrays.
  • Since heap is a complete binary tree therefore it's height will always be log(n).
  • Heap is not useful for searching purposes.
    Pasted image 20240421202226.png

Insertion in Heap :

Pasted image 20240421202721.png

  • Here 40 is inserted into the heap, but after insertion it no longer remains a Max Heap. Therefore we'll have to fix it.
    • To fix it, we compare the value of 40 with it's parent, for max heap if then we'll swap them both. To swap, we'll keep 40 in some temporary variable and replace the value at 40's place with 5 and leave 5's place as empty, now, since 40>20 also, therefore 20 will be placed at the original place of 5. This process continues as long as the condition is satisfied.
    • Pasted image 20240421203427.png
      • At last we'll copy 40 at the empty place.
  • In the array it is done like this :
    Pasted image 20240421203615.png
    This is the original array, the array in the image below is the final array after the insertion process has been completed.

Pasted image 20240421202656.png

  • Pasted image 20240421210720.png
  • Pasted image 20240421212313.png
    • Time Complexity of insertion of 1 element is : height of the tree i.e
    • We'll always add the node to the leaf, then we'll call the insert function which will then rearrange the elements of the array.

Creation of a heap :

Max Heap :

Pasted image 20240421215756.png

  • The idea here is to move the elements on the right hand side of the array one by one to the left hand side of the array.
  • There should be at least one element present in the array before calling the insert function.

Pasted image 20240421222255.png

Min Heap :

  • Pasted image 20240421222545.png
  • We just change the condition to temp < A[i/2]

Deletion in Heap :

In heap we can only delete the largest element (we are talking about the max heap here) i.e. the root element. In other words only the highest priority element can be deleted from the heap.

Pasted image 20240421232214.png

  • After the deletion of the root element the last element in the BT will be moved to the root node.
    Pasted image 20240421232323.png
    Pasted image 20240421232333.png
    Now this is no longer a max heap, we'll have to fix this.

We'll do the same thing in the array :

  • Pasted image 20240421234035.png
    • take out the value 40 and store it in variable x then move 5 at at the place of 40
  • Pasted image 20240421234318.png
  • Pasted image 20240421234534.png
    Compare the children of new root, whichever one is greater will then be compared with the root and then will get swapped with the root if that element is greater than the root (for max heap ofc).
  • Pasted image 20240421234741.png
    Now do this process again by taking the children of 5 and swap if necessary.
  • Pasted image 20240421234427.png

In array :
Pasted image 20240421235029.png

  • If i is the root, compare 2i and 2i+1 then compare the greater element with the parent and swap if child > root.

Pasted image 20240422003649.png

Heap Sort : Skipped : Lec 332

  • Basically jo element delete kiya hai wo maximum element hoga. And since array me ab ek space khali hai end me therefore delete element ko ham last me place kar denge and size ko n-1 kar dege taaki element delete bhi ho jaye from the heap and ham store bhi kar sake deleted element ko.
  • We perform the delete function 1 by 1 and store the elements from last to first then we'll get a sorted array. this is called heap sort.
  • For more see Abdul Bari sir's Lecture.
#include <stdio.h>
#include <stdlib.h>

void swap(int *x, int *y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

void insert(int a[], int n)
{
    int x = a[n];
    int i = n;
    while (i > 1 && a[i / 2] < x)
    {
        a[i] = a[i / 2];
        i = i / 2;
    }
    a[i] = x;
}

int delete(int a[], int n)
{
    int i = 1, j = 2 * i;
    int x = a[i];
    a[i] = a[n];
    a[n] = x;

    while (j < n - 1)
    {
        if (a[j + 1] > a[j])
            j = j + 1;
        if (a[j] > a[i])
        {
            swap(&a[i], &a[j]);

            i = j;
            j = 2 * i;
        }
        else
            break;
    }
    return x;
}

int main()
{

    int a[] = {0, 10, 20, 30, 25, 5, 40, 35};

    for(int i =1; i<=7; i++)
        insert(a, i);


    for(int i = 7; i>=1; i--)
        delete(a, i);

    // Heap Sort
    for(int i = 1; i<=7; i++)
        printf("%d ", a[i]);

    return 0;
}

Heapify :

  • In create heap we adjust from bottom to up and start from the first element of the array of binary tree where as in heapify we adjust towards the bottom and start from the last element in the array
  • Pasted image 20240422011324.png
  • Where as create heap has O(n * log n) time complexity.