0% found this document useful (0 votes)
64 views

Tree Data Structure

Uploaded by

saadkhann005
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
64 views

Tree Data Structure

Uploaded by

saadkhann005
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 13

Tree Data Structure

A Tree is a hierarchical data structure consisting of nodes connected by edges. Each tree has a root node, from
which all other nodes descend. Trees are widely used in computer science for modeling hierarchical structures, such
as file systems, organizational charts, and database indexing.

1. Definition of Tree

 A Tree consists of a collection of nodes.


 The topmost node is called the root.
 Each node can have children (other nodes that are directly connected beneath it).
 Nodes that do not have children are called leaf nodes.

Basic Terminology:

 Root: The top node in the tree.


 Parent: A node that has one or more children.
 Child: A node directly connected to another node, which is considered its parent.
 Leaf: A node that has no children.
 Subtree: A tree that is a part of another tree (rooted at a given node).
 Height of Tree: The length of the longest path from the root to any leaf.
 Depth of Node: The number of edges from the root to the node.
 Level of Node: The level in which a node resides, typically defined as the number of edges from the root.

2. Properties of Trees

 Hierarchical Structure: Nodes are organized in levels, and each node can have multiple children, forming
a parent-child relationship.

 Connected: There is exactly one path between any two nodes.
 Non-linear: Unlike arrays or lists, trees are non-linear data structures.

3. Use Cases of Trees

 File Systems: The directories and files in an operating system can be modeled as a tree.
 Databases: Indexes in databases, such as B-trees, are used to optimize searching and sorting.
 Networking: Routing tables are often implemented using trees.
 Expression Parsing: In compilers, syntax trees are used to represent expressions.
 Decision Trees: Used in machine learning for classification tasks.

4. Advantages and Disadvantages

Pros:

 Hierarchical Representation: Trees efficiently represent hierarchical data.


 Efficient Searching: Many tree-based structures (like binary search trees) allow for fast searching,
insertion, and deletion (average time complexity O(log n)).
 Dynamic Size: Unlike arrays, the size of a tree can dynamically change (you can add/remove nodes as
needed).
 Sorting: Can be used for sorting data by inserting nodes in a sorted order.

Cons:
 Complexity: Trees can be more complex to implement compared to simpler data structures like arrays or
linked lists.
 Memory Consumption: Each node may require additional memory to store references to child nodes,
leading to higher memory usage.
 Imbalance: Trees may become unbalanced (in binary trees, for instance), leading to performance
degradation (e.g., O(n) time complexity in the worst case).

5. Basic Operations on Trees

Let's look at the basic operations in a tree: creation, insertion, deletion, searching, and traversal.

Basic Operations in C++

cpp
Copy code
#include <iostream>
using namespace std;

// Define a Node structure


struct Node {
int data;
Node* left;
Node* right;

// Constructor to create a new node


Node(int value) {
data = value;
left = right = nullptr;
}
};

// 1. Create a Tree (Root node)


Node* createTree(int rootValue) {
return new Node(rootValue);
}

// 2. Add a new node (Insert a node)


Node* insertNode(Node* root, int value) {
if (root == nullptr) {
return new Node(value); // If the tree is empty, create the new node
}
// Insert into left or right subtree
if (value < root->data) {
root->left = insertNode(root->left, value);
} else {
root->right = insertNode(root->right, value);
}
return root;
}

// 3. Search for an element in the tree


bool searchNode(Node* root, int value) {
if (root == nullptr) {
return false; // Tree is empty or value not found
}
if (root->data == value) {
return true; // Found the node
}
// Search left or right subtree based on value
if (value < root->data) {
return searchNode(root->left, value);
} else {
return searchNode(root->right, value);
}
}

// 4. Delete a node (in a binary search tree)


Node* deleteNode(Node* root, int value) {
if (root == nullptr) {
return nullptr; // Element not found
}

// If the value to be deleted is smaller than the root's value, then it


lies in the left subtree
if (value < root->data) {
root->left = deleteNode(root->left, value);
}
// If the value to be deleted is larger, it lies in the right subtree
else if (value > root->data) {
root->right = deleteNode(root->right, value);
}
// If the value is same as the root's value, then this is the node to be
deleted
else {
// Node with only one child or no child
if (root->left == nullptr) {
Node* temp = root->right;
delete root;
return temp;
} else if (root->right == nullptr) {
Node* temp = root->left;
delete root;
return temp;
}

// Node with two children: Get the inorder successor (smallest in the
right subtree)
Node* temp = minValueNode(root->right);
root->data = temp->data; // Copy the inorder successor's content to
this node
root->right = deleteNode(root->right, temp->data); // Delete the
inorder successor
}
return root;
}

// Helper function to find the node with the minimum value in a given tree
Node* minValueNode(Node* node) {
Node* current = node;
while (current && current->left != nullptr) {
current = current->left;
}
return current;
}

// 5. Pre-order Traversal (root, left, right)


void preorder(Node* root) {
if (root == nullptr) return;
cout << root->data << " ";
preorder(root->left);
preorder(root->right);
}

// 6. In-order Traversal (left, root, right)


void inorder(Node* root) {
if (root == nullptr) return;
inorder(root->left);
cout << root->data << " ";
inorder(root->right);
}

// 7. Post-order Traversal (left, right, root)


void postorder(Node* root) {
if (root == nullptr) return;
postorder(root->left);
postorder(root->right);
cout << root->data << " ";
}

int main() {
Node* root = createTree(10); // Create root node with value 10

// Insert nodes
insertNode(root, 5);
insertNode(root, 15);
insertNode(root, 2);
insertNode(root, 7);
insertNode(root, 12);
insertNode(root, 18);

// Search for a node


cout << "Search 7: " << (searchNode(root, 7) ? "Found" : "Not Found") <<
endl;
cout << "Search 20: " << (searchNode(root, 20) ? "Found" : "Not Found") <<
endl;

// Traversals
cout << "Pre-order Traversal: ";
preorder(root);
cout << endl;

cout << "In-order Traversal: ";


inorder(root);
cout << endl;

cout << "Post-order Traversal: ";


postorder(root);
cout << endl;

// Deleting a node
cout << "Deleting node 5." << endl;
root = deleteNode(root, 5);

cout << "In-order Traversal after deletion: ";


inorder(root);
cout << endl;

return 0;
}

Explanation of Code:

1. Create a Tree: The function createTree() creates a tree with a single root node.
2. Insert a Node: The function insertNode() inserts a new node in the tree based on the value. It places
smaller values in the left subtree and larger values in the right subtree.
3. Search a Node: The function searchNode() searches for a given value in the tree by recursively
traversing down the left or right subtree.
4. Delete a Node: The function deleteNode() removes a node from the tree. It handles three cases:
o Node has no children (leaf node).
o Node has one child.
o Node has two children (replace with the inorder successor).
5. Traversals: The functions preorder(), inorder(), and postorder() perform different tree
traversals.

Time Complexity of Operations:

 Search, Insert, Delete: O(log n) on average for balanced trees (e.g., AVL, Red-Black Trees). In the worst
case (unbalanced tree), it can be O(n).
 Traversal: O(n), where n is the number of nodes in the tree.

Conclusion:

Trees are versatile structures, useful for representing hierarchical data and performing fast searching, insertion, and
deletion operations when properly balanced.

4o mini

You said:

What"s BST and Heap?

ChatGPT said:

ChatGPT

Binary Search Tree (BST)

A Binary Search Tree (BST) is a type of binary tree where the nodes are arranged in a specific order. The key
property of a BST is that for any given node:

 All the nodes in its left subtree have values less than the node's value.
 All the nodes in its right subtree have values greater than the node's value.
This ordering property allows for efficient searching, insertion, and deletion operations.

Properties of BST:

 Ordered Structure: As explained above, for any node:


o Left child < Node < Right child.
 No Duplicates: Typically, in most implementations of BST, duplicates are not allowed (although it can be
modified to allow them).
 Efficient Operations:
o Search: O(log n) in average case.
o Insertion: O(log n) in average case.
o Deletion: O(log n) in average case.

Use Cases of BST:

 Searching: BSTs are used when you need to perform many fast searches on sorted data.
 Dynamic Set Operations: In dynamic sets, BSTs support operations like insertions, deletions, and look-
ups efficiently.
 Range Queries: In a BST, you can easily find all values in a given range by performing an in-order
traversal.

Basic Operations (in C++):

Here’s an implementation of a basic BST with insertion and search functionality:

cpp
Copy code
#include <iostream>
using namespace std;

struct Node {
int data;
Node* left;
Node* right;

Node(int value) : data(value), left(nullptr), right(nullptr) {}


};

// Insert node in BST


Node* insert(Node* root, int value) {
if (root == nullptr) {
return new Node(value); // If the tree is empty, create the new node.
}
if (value < root->data) {
root->left = insert(root->left, value); // Insert in left subtree
} else {
root->right = insert(root->right, value); // Insert in right subtree
}
return root;
}

// Search in BST
bool search(Node* root, int value) {
if (root == nullptr) return false; // If the node is null, return false.
if (root->data == value) return true; // Found the node.
if (value < root->data) return search(root->left, value); // Search in
left subtree.
return search(root->right, value); // Search in right subtree.
}

int main() {
Node* root = nullptr;

// Insert values
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);

// Search for a value


cout << "Search for 40: " << (search(root, 40) ? "Found" : "Not Found") <<
endl;
cout << "Search for 25: " << (search(root, 25) ? "Found" : "Not Found") <<
endl;

return 0;
}

Heap

A Heap is a specialized tree-based data structure that satisfies the heap property. There are two main types of
heaps:

1. Max-Heap: In a max-heap, the value of each parent node is greater than or equal to the values of its
children. The largest element is at the root.
2. Min-Heap: In a min-heap, the value of each parent node is less than or equal to the values of its children.
The smallest element is at the root.

Heaps are usually implemented as binary trees, but they are often represented using arrays for simplicity and
efficiency.

Properties of Heaps:

 Complete Binary Tree: A heap is always a complete binary tree, meaning it is fully filled at all levels,
except possibly the last one, which is filled from left to right.
 Heap Order Property:
o In a Max-Heap, the key at each node is greater than or equal to the keys of its children.
o In a Min-Heap, the key at each node is less than or equal to the keys of its children.

Use Cases of Heaps:

 Priority Queues: Heaps are often used to implement priority queues, where elements are inserted with
priorities and the highest or lowest priority element is removed first.
 Efficient Sorting: The heap sort algorithm uses heaps to efficiently sort data in O(n log n) time.
 Dijkstra's Algorithm: Heaps can be used in graph algorithms, like Dijkstra’s algorithm for finding the
shortest path.

Basic Operations in Heaps:

 Insert: Inserting a new element while maintaining the heap property.


 Extract: Extracting the maximum (in a max-heap) or minimum (in a min-heap) element while maintaining
the heap property.

Operations in C++:

Here’s a basic implementation of a max-heap with insert and remove functionality:

cpp
Copy code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// Heapify function to maintain the heap property


void heapify(vector<int>& heap, int n, int i) {
int largest = i;
int left = 2 * i + 1; // Left child index
int right = 2 * i + 2; // Right child index

if (left < n && heap[left] > heap[largest]) {


largest = left;
}
if (right < n && heap[right] > heap[largest]) {
largest = right;
}

if (largest != i) {
swap(heap[i], heap[largest]); // Swap the nodes
heapify(heap, n, largest); // Recursively heapify the affected
subtree
}
}

// Insert a new element into the heap


void insert(vector<int>& heap, int value) {
heap.push_back(value); // Add the new element at the end
int i = heap.size() - 1;

// Fix the heap property by comparing the element with its parent and
swapping if needed
while (i > 0 && heap[(i - 1) / 2] < heap[i]) {
swap(heap[i], heap[(i - 1) / 2]);
i = (i - 1) / 2;
}
}

// Remove and return the max element (root of the heap)


int extractMax(vector<int>& heap) {
if (heap.size() == 0) {
cout << "Heap is empty!" << endl;
return -1; // Return an error value
}
int maxElement = heap[0]; // The root element is the max
heap[0] = heap[heap.size() - 1]; // Replace the root with the last
element
heap.pop_back(); // Remove the last element

heapify(heap, heap.size(), 0); // Heapify the tree to restore heap


property

return maxElement;
}

int main() {
vector<int> maxHeap;

// Insert elements into the heap


insert(maxHeap, 20);
insert(maxHeap, 15);
insert(maxHeap, 30);
insert(maxHeap, 40);
insert(maxHeap, 50);
insert(maxHeap, 10);

cout << "Heap after insertions: ";


for (int num : maxHeap) {
cout << num << " ";
}
cout << endl;

// Extract the max element


cout << "Extract max: " << extractMax(maxHeap) << endl;

cout << "Heap after extraction: ";


for (int num : maxHeap) {
cout << num << " ";
}
cout << endl;

return 0;
}

Explanation of Code:

1. Heapify: The heapify() function is used to maintain the heap property after operations like insertion or
extraction. It ensures that a subtree rooted at a given node satisfies the heap property.
2. Insert: The insert() function adds a new element at the end of the heap (as the last leaf) and then
"bubbles it up" to restore the heap property by comparing it with its parent and swapping if necessary.
3. Extract Max: The extractMax() function removes and returns the maximum element (the root). After
removing the root, the last element is moved to the root position, and heapify() is called to restore the
heap property.

Time Complexity of Operations:


 Insert: O(log n), because we may have to "bubble up" the new element from the leaf to the root.
 Extract Max: O(log n), because after removing the root, we may have to "heapify" the tree from the root
down to the leaves.
 Heapify: O(log n) in the worst case.

Conclusion:

 BST is an ordered binary tree used mainly for efficient searching, insertion, and deletion operations, and it
is well-suited for tasks involving dynamic sets and range queries.
 Heap is a complete binary tree used for efficient priority queue operations, and it is well-suited for
scenarios like heap sort and graph algorithms like

CREATING A TREE:
#include <iostream>

using namespace std;

// Define the structure for each node in the binary tree

struct TreeNode {

int data; // Stores the value of the node

TreeNode* left; // Pointer to the left child

TreeNode* right; // Pointer to the right child

// Constructor to initialize the node

TreeNode(int value) : data(value), left(nullptr), right(nullptr) {}

};

// Function to insert a node in the binary tree (recursive insert)

TreeNode* insertNode(TreeNode* root, int value) {

if (root == nullptr) {

// If the tree is empty, create a new node and return it

return new TreeNode(value);

// Decide to insert in the left or right subtree based on the value

if (value < root->data) {

root->left = insertNode(root->left, value); // Insert in the left subtree

} else {

root->right = insertNode(root->right, value); // Insert in the right subtree

return root; // Return the root after insertion

// Function to perform in-order traversal (left, root, right)

void inorderTraversal(TreeNode* root) {

if (root == nullptr) {

return; // Base case: if the node is null, do nothing

}
inorderTraversal(root->left); // Traverse the left subtree

cout << root->data << " "; // Print the node's data

inorderTraversal(root->right); // Traverse the right subtree

// Function to perform pre-order traversal (root, left, right)

void preorderTraversal(TreeNode* root) {

if (root == nullptr) {

return;

cout << root->data << " "; // Print the node's data

preorderTraversal(root->left); // Traverse the left subtree

preorderTraversal(root->right); // Traverse the right subtree

// Function to perform post-order traversal (left, right, root)

void postorderTraversal(TreeNode* root) {

if (root == nullptr) {

return;

postorderTraversal(root->left); // Traverse the left subtree

postorderTraversal(root->right); // Traverse the right subtree

cout << root->data << " "; // Print the node's data

// Main function to demonstrate binary tree creation and traversal

int main() {

TreeNode* root = nullptr; // Initialize the root of the tree as null

int n;

// Prompt user to enter the number of nodes for the binary tree

cout << "Enter number of nodes for the binary tree: ";
cin >> n;

// Insert nodes into the binary tree

for (int i = 0; i < n; i++) {

int value;

cout << "Enter value for node " << (i + 1) << ": ";

cin >> value;

root = insertNode(root, value); // Insert each node into the tree

// Perform tree traversals

cout << "\nIn-order Traversal: ";

inorderTraversal(root);

cout << endl;

cout << "Pre-order Traversal: ";

preorderTraversal(root);

cout << endl;

cout << "Post-order Traversal: ";

postorderTraversal(root);

cout << endl;

return 0;

You might also like