0% found this document useful (0 votes)
12 views200 pages

Complete DSC Notes 1

The document discusses derived and user-defined data types in C, explaining how derived types like arrays and pointers extend primitive types, while user-defined types like structures, unions, and enums allow programmers to create complex data structures. It highlights the advantages of using these data types for better organization and management of data, particularly in applications like student information systems and employee management. Additionally, it provides examples of how to declare, initialize, and manipulate structures and pointers to structures in C.

Uploaded by

jathinkumar72
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views200 pages

Complete DSC Notes 1

The document discusses derived and user-defined data types in C, explaining how derived types like arrays and pointers extend primitive types, while user-defined types like structures, unions, and enums allow programmers to create complex data structures. It highlights the advantages of using these data types for better organization and management of data, particularly in applications like student information systems and employee management. Additionally, it provides examples of how to declare, initialize, and manipulate structures and pointers to structures in C.

Uploaded by

jathinkumar72
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 200

Unit I

Structures and Files

1. Derived Data Types in C

What Are Derived Data Types?

Derived data types in C are formed using fundamental (primitive) data types such as int, char,
float, and double. These derived types do not define new types but rather extend or modify
existing types to handle more complex data structures.

How Are Derived Data Types Formed?

Derived data types are created using C's language constructs such as:

1. Arrays
2. Pointers

1. Arrays (Collection of Similar Data Types)

An array is a collection of elements of the same data type stored in contiguous memory locations.

How Arrays Are Derived from Primitive Types?

 An int variable stores a single integer.


 But an array of integers (int arr[5]) is derived from int to store multiple values.

int arr[5] = {1, 2, 3, 4, 5};

Memory Representation of arr[5]

Index Value Address (example)


arr[0] 1 1000
arr[1] 2 1004
arr[2] 3 1008
arr[3] 4 1012
arr[4] 5 1016
2. Pointers (Variables That Store Memory Addresses)

A pointer is a derived data type that stores the memory address of another variable.

How Pointers Are Derived from Primitive Types?

A normal variable holds a value, but a pointer variable holds an address of a value.

int a = 10;
int *ptr = &a; // 'ptr' stores the address of 'a'

Memory Representation

Variable Value Address


a 10 2000
ptr 2000 3000

Pointer to an Array

int arr[3] = {10, 20, 30};


int *ptr = arr; // Pointer points to first element

Pointers make dynamic memory allocation possible (malloc, calloc, etc.).

Conclusion

Derived data types in C are powerful extensions of fundamental data types, allowing for efficient
memory management, better code organization, and enhanced functionality. Understanding
them helps in writing modular, scalable, and optimized programs.

2. User-Defined Data Types in C

What are User-Defined Data Types?

User-defined data types in C allow programmers to define new data types using existing primitive
types (int, char, float, etc.). They help represent complex data in a structured way.

Why Are User-Defined Data Types Needed?

 Primitive data types (int, float, etc.) store only single values.
 Complex real-world objects (e.g., student records, employee data) require multiple data
fields.
 User-defined data types group related data into a single unit, making code more
modular and readable.
Types of User-Defined Data Types in C

C provides four main user-defined data types:

1. Structures (struct)
2. Unions (union)
3. Enumerations (enum)
4. Typedef (typedef) and #define Macros

Advantages of User-Defined Data Types?

✔ Organizes complex data (e.g., student records, bank accounts).


✔ Improves code readability (e.g., typedef).
✔ Saves memory (union).
✔ Enhances maintainability (e.g., modular programming).

Key Differences Between Derived and User-Defined Data Types

Feature Derived Data Types User-Defined Data Types


Definition Built from existing primitive types Created entirely by the programmer
Complexity Enhances primitive types Groups multiple data types
Examples Arrays, Pointers, Function Pointers Structures, Unions, Enums, Typedef
Memory Usage Pre-determined Can be optimized for efficiency
Use Case Extending single data types Combining multiple data fields

Conclusion

Derived data types help in handling collections (arrays), references (pointers), and function
manipulations.
User-defined data types provide structure to complex data, making programs more
manageable and efficient.

3. Structures:

Why Do We Need Structures?

1. Student Information System

Problem: A university wants to store student details like name, roll number, marks, and
department.
Without Structures: You’d need to define multiple separate arrays, making it hard to manage
and prone to errors.

For Student 1 :

char student1_name[50][100];
int roll1_number[100];
float marks1[100];
char department1[100][50];

For Student 2 :

char student2_name[50][100];
int roll2_number[100];
float marks2[100];
char department2[100][50];
.
.
.
Go on…..

2. Employee Management System

Problem: A company wants to store employee details like name, ID, salary, and department.

Without Structures: Separate variables for each property can lead to disorganized code.

3. Online Shopping System (E-commerce)

Problem: An online store needs to store details of each product, such as name, price, and
stock.

4. Banking System (Customer Accounts)

Problem: A bank needs to store customer details like account numbers, name, and balance.

Note: Why Not Use Arrays Instead of Structures?

 Arrays store only one data type (e.g., all integers, all floats).
What is a Structure in C?

A structure in C is a user-defined data type that allows grouping different data types under a single
name. It helps in organizing complex data efficiently, making it easy to manage and manipulate.

How to Declare and Use Structures

1. Declaring a Structure

A structure is defined using the struct keyword:

struct Student {
char name[50];
int roll_no;
float marks;
};

2. Creating Structure Variables

After defining a structure, you can create variables of that type:

struct Student s1, s2; // Two student variables

3. Accessing Structure Members

Use the dot (.) operator to access members:

s1.roll_no = 101;
s1.marks = 95.5;
strcpy(s1.name, "John Doe");

4. Printing Structure Members


printf("Student Name: %s\n", s1.name);
printf("Roll No: %d\n", s1.roll_no);
printf("Marks: %.2f\n", s1.marks);

Different Ways to Initialize Structures

Direct Initialization

struct Student s1 = {"Alice", 102, 89.5};


User Input

printf("Enter name, roll number, and marks:\n");


scanf("%s %d %f", s1.name, &s1.roll_no, &s1.marks);

Here are some simple structure examples in C to help you understand how structures work.

Example 1: Basic Structure

This program defines a Student structure and assigns values to it.

#include <stdio.h>
#include <string.h>

// Defining a structure
struct Student {
char name[50];
int roll_no;
float marks;
};

int main() {
// Declaring a structure variable
struct Student s1;

// Assigning values
strcpy(s1.name, "Alice");
s1.roll_no = 101;
s1.marks = 85.5;

// Printing values
printf("Student Name: %s\n", s1.name);
printf("Roll No: %d\n", s1.roll_no);
printf("Marks: %.2f\n", s1.marks);

return 0;
}

Output:
Student Name: Alice
Roll No: 101
Marks: 85.50
Example 2: Structure with User Input

This program takes student details from the user and displays them.

#include <stdio.h>

// Defining the structure


struct Student {
char name[50];
int roll_no;
float marks;
};

int main() {
struct Student s1;

// Taking user input


printf("Enter name: ");
scanf("%s", s1.name);
printf("Enter roll number: ");
scanf("%d", &s1.roll_no);
printf("Enter marks: ");
scanf("%f", &s1.marks);

// Printing values
printf("\nStudent Details:\n");
printf("Name: %s\n", s1.name);
printf("Roll No: %d\n", s1.roll_no);
printf("Marks: %.2f\n", s1.marks);

return 0;
}

Sample Output:
Enter name: Bob
Enter roll number: 102
Enter marks: 90.5

Student Details:
Name: Bob
Roll No: 102
Marks: 90.50
Example 3: Structure Initialization

a. Direct Initialization at Declaration


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

int main() {
struct Student s1 = {101, "Alice", 89.5}; // Direct Initialization
printf("Roll No: %d, Name: %s, Marks: %.2f\n", s1.rollNo, s1.name,
s1.marks);
return 0;
}

b. Designated Initialization (C99 and later)


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

int main() {
struct Student s1 = {.name = "Bob", .rollNo = 102, .marks = 92.5};
// Named Initialization
printf("Roll No: %d, Name: %s, Marks: %.2f\n", s1.rollNo, s1.name,
s1.marks);
return 0;
}

Array of Structures

An array of structures is used when you need to store and process multiple records of the same
structure type efficiently. Instead of creating multiple separate structure variables, an array of
structures allows handling large data sets systematically.
Use Cases

 Storing student records (name, roll number, marks, etc.).


 Keeping track of employee details in an organization.
 Managing inventory in a store.
 Recording book details in a library system.

Declaration of an Array of Structures

To declare an array of structures, define the structure first and then create an array of that structure
type.

struct Student {
int rollNo;
char name[20];
float marks;
};

struct Student students[50]; // Declaring an array of 50 student records

Here, students is an array containing 50 Student structure elements.

Accessing Members of an Array of Structures

 Use the dot (.) operator when accessing individual elements.


 Use a loop to process multiple records efficiently.

Example 1: Basic Declaration, Initialization & Access


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

int main() {
// Array of structure initialization
struct Student students[3] = {
{101, "Alice", 85.5},
{102, "Bob", 90.2},
{103, "Charlie", 78.8}
};

// Accessing structure array elements


for (int i = 0; i < 3; i++) {
printf("Roll No: %d, Name: %s, Marks: %.2f\n",
students[i].rollNo, students[i].name,
students[i].marks);
}

return 0;
}

Explanation:

 We declared an array of 3 students.


 Used a for loop to iterate through each student and display their details.

Example 2: Input & Output Using an Array of Structures


#include <stdio.h>

struct Employee {
int id;
char name[30];
float salary;
};

int main() {
struct Employee employees[2]; // Array of 2 employees

// Taking input from the user


for (int i = 0; i < 2; i++) {
printf("Enter ID, Name, and Salary for Employee %d:\n", i + 1);
scanf("%d", &employees[i].id);
scanf("%s", employees[i].name);
scanf("%f", &employees[i].salary);
}

// Displaying stored data


printf("\nEmployee Details:\n");
for (int i = 0; i < 2; i++) {
printf("ID: %d, Name: %s, Salary: %.2f\n",
employees[i].id, employees[i].name,
employees[i].salary);
}

return 0;
}
Explanation:

 The program allows the user to input details of employees.


 It stores them in an array of structures and prints the records.

This approach is useful in handling large datasets in real-world applications like student
databases, employee management, inventory tracking, and more.

Pointer to Structure

A pointer to a structure is a pointer variable that stores the address of a structure. It allows
dynamic memory allocation, efficient data manipulation, and reduces memory usage.

Declaring a Pointer to a Structure


struct Student {
int rollNo;
char name[20];
float marks;
};

struct Student *ptr; // Pointer to structure

 The ptr variable is a pointer that can store the address of a Student structure.
 The dot operator (.) is used for accessing structure members using a normal variable.
 The arrow operator (->) is used to access members via a pointer.

Example 1 :Accessing Structure Members Using Pointer


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};
int main() {
struct Student s1 = {101, "Alice", 89.5}; // Normal structure
variable
struct Student *ptr = &s1; // Pointer to structure

// Access members using pointer


printf("Roll No: %d\n", ptr->rollNo);
printf("Name: %s\n", ptr->name);
printf("Marks: %.2f\n", ptr->marks);

return 0;
}
Output:
Roll No: 101
Name: Alice
Marks: 89.50

Explanation:

 ptr = &s1; → Stores the address of s1 in the pointer.


 ptr->rollNo → Accesses rollNo using the pointer.

Example 2: Pointer to Structure with User Input


#include <stdio.h>

struct Employee {
int id;
char name[30];
float salary;
};

int main() {
struct Employee e1;
struct Employee *ptr = &e1; // Pointer to structure

// Taking input using pointer


printf("Enter Employee ID, Name, and Salary:\n");
scanf("%d", &ptr->id);
scanf("%s", ptr->name);
scanf("%f", &ptr->salary);

// Displaying data using pointer


printf("\nEmployee Details:\n");
printf("ID: %d, Name: %s, Salary: %.2f\n", ptr->id, ptr->name, ptr-
>salary);

return 0;
}

Explanation:

 We used ptr->member to take input and display values.


 No need to use (*ptr).member, making it simpler and readable.
Example 3: Dynamic Memory Allocation for Structure Using Pointer
#include <stdio.h>
#include <stdlib.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

int main() {
struct Student *ptr;

// Allocating memory dynamically


ptr = (struct Student *)malloc(sizeof(struct Student));

if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Taking input
printf("Enter Roll No, Name, and Marks:\n");
scanf("%d", &ptr->rollNo);
scanf("%s", ptr->name);
scanf("%f", &ptr->marks);

// Displaying output
printf("\nStudent Details:\n");
printf("Roll No: %d, Name: %s, Marks: %.2f\n", ptr->rollNo, ptr-
>name, ptr->marks);

// Freeing allocated memory


free(ptr);

return 0;
}

Explanation:

 malloc() is used to dynamically allocate memory for the structure.


 We access the structure members using ptr->member.
 free(ptr); is used to release memory after use.
Example 4: Array of Structure Pointers
#include <stdio.h>

struct Car {
char name[20];
float price;
};

int main() {
struct Car cars[2] = {{"Toyota", 50000.0}, {"Honda", 45000.0}};
struct Car *ptr = cars; // Pointer to structure array

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


printf("Car: %s, Price: %.2f\n", (ptr + i)->name, (ptr + i)-
>price);
}

return 0;
}

Explanation:

 ptr = cars; stores the base address of the array.


 (ptr + i)->member accesses each structure element.

Summary

Concept Explanation
Pointer to Structure Stores the address of a structure variable.
Accessing Members Use ptr->member instead of (*ptr).member.
Dynamic Memory Allocation Use malloc() for runtime memory allocation.
Array of Structure Pointers Efficient for handling multiple records.

Using pointers with structures makes programs memory-efficient and flexible.

Passing Structures to Functions

In C, structures can be passed to functions in three ways:

1. Pass by Value
2. Pass by Reference (Using Pointers)
3. Pass an Array of Structures
1. Pass Structure to Function by Value

When passing by value, a copy of the structure is made and passed to the function.
Changes inside the function do not affect the original structure.

Example 1: Pass by Value


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

// Function to display student details (Pass by Value)


void displayStudent(struct Student s) {
printf("Roll No: %d, Name: %s, Marks: %.2f\n", s.rollNo, s.name,
s.marks);
}

int main() {
struct Student s1 = {101, "Alice", 89.5};

displayStudent(s1); // Passing structure by value

return 0;
}

Key Points:

✔ A copy of s1 is passed to displayStudent().


✔ Any modifications inside displayStudent() will not affect s1.
✔ Suitable when the structure is small (doesn’t use much memory).
✔ Downside: If the structure is large, copying it takes extra memory and time.

2. Pass Structure to Function by Reference (Using Pointers)

Instead of passing a copy, we can pass the address of the structure using a pointer.
This allows modifications inside the function to reflect in the original structure.
Example 1 : Pass by Reference
#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};

// Function to update student marks (Pass by Reference)


void updateMarks(struct Student *s) {
s->marks += 5; // Increase marks by 5
}

int main() {
struct Student s1 = {102, "Bob", 80.0};

printf("Before Update: Marks = %.2f\n", s1.marks);


updateMarks(&s1); // Passing structure by reference
printf("After Update: Marks = %.2f\n", s1.marks);

return 0;
}

Key Points:

✔ The address of s1 is passed (&s1), so changes inside the function modify the original
structure.
✔ We use arrow operator (->) to access members inside the function.
✔ Efficient for large structures, as no copy is created.
✔ Use Case: When we need to modify structure members inside the function.

3. Pass an Array of Structures to a Function

Passing an array of structures is like passing an array of basic types.


Only the base address of the array is passed, avoiding copying.

Example 1: Passing an Array of Structures


#include <stdio.h>

struct Student {
int rollNo;
char name[20];
float marks;
};
// Function to display student details
void displayStudents(struct Student students[], int size) {
for (int i = 0; i < size; i++) {
printf("Roll No: %d, Name: %s, Marks: %.2f\n",
students[i].rollNo, students[i].name, students[i].marks);
}
}

int main() {
struct Student students[3] = {
{103, "Charlie", 78.5},
{104, "David", 92.0},
{105, "Emma", 85.5}
};

displayStudents(students, 3); // Passing array of structures

return 0;
}

Key Points:

✔ Only the base address of the array is passed, making it efficient.


✔ The function modifies the original array elements if necessary.
✔ Use Case: Handling large datasets like student records, employee details, etc.

Summary

Changes Original
Method What is Passed? Best Use Case
Structure?
A copy of the Small structures where
Pass by Value No
structure modification is not needed
Pointer (Address Large structures where
Pass by Reference Yes
of structure) modification is required
Pass an Array of Base address of Handling multiple records
Yes
Structures array efficiently

Conclusion
✔ Pass by Value → Simple but inefficient for large structures.
✔ Pass by Reference → Efficient and allows modification.
✔ Passing Array of Structures → Best for handling multiple records.

Using pointers is recommended for better performance and memory efficiency when dealing
with large structures.
Different Structure Declarations in C

In C, structures can be declared in multiple ways. Let's analyze the different declarations and
understand their differences.

Case 1: Basic Structure Definition


struct Student {
int rollNo;
char name[20];
};

✔ Explanation

 This defines a structure called Student but does not declare any variables.
 Memory is not allocated yet.
 To use it, we must declare variables like:
 struct Student s1, s2;
 Access members using dot (.) operator:
 s1.rollNo = 101;
 strcpy(s1.name, "Alice");

Case 2: Anonymous Structure with a Typedef-Like Variable


struct {
int rollNo;
char name[20];
} Student;

✔ Explanation

 This defines an anonymous structure (no name after struct).


 It directly declares a variable Student of this structure type.
 Student is NOT a type, it's just a variable.
 We cannot declare another variable using Student, because it is not a type:
 Student s1; // ERROR: "Student" is a variable, not a type.
 To access members:

Student.rollNo = 102;
strcpy(Student.name, "Bob");

Key Differences:

 Case 1 (struct Student) → Creates a named structure type.


 Case 2 (Anonymous structure) → Creates a single global variable.
Case 3: Structure Definition with Variables
struct Student {
int rollNo;
char name[20];
} s1, s2;

✔ Explanation

Here, Student is a named structure type, and s1, s2 are declared together.

Equivalent to:

struct Student {
int rollNo;
char name[20];
};

struct Student s1, s2;

We can declare more variables later:

struct Student s3; // Allowed

Accessing members:

s1.rollNo = 103;
strcpy(s1.name, "Charlie");

Key Difference:

 Case 1 (struct Student) → Just defines the structure type, no variables created.
 Case 3 (struct Student { ... } s1, s2;) → Defines the structure and declares
variables.

Case 5: Declaring a Structure Inside main()


int main() {
struct {
int rollNo;
char name[20];
} s1, s2;
}
✔ Explanation

 This defines an anonymous structure inside main().


 s1 and s2 are variables of this structure.
 Structure is local to main(), so it cannot be used elsewhere.

Key Difference:

 This structure exists only inside main().


 Cannot declare another variable like struct Student s3; outside main().

Summary

Creates Structure Can Declare More


Case Code Creates Variable?
Type? Variables?
struct Student { Yes (struct
1 ... }; Yes (Student) No
Student s1;)
struct { ... } No Yes (Student is No (Student s1;
2 Student; (Anonymous) a variable) is invalid)
struct Student { Yes (struct
3 ... } s1, s2; Yes (Student) Yes (s1, s2)
Student s3;)

No No (Only inside
4 Inside main() Yes (s1, s2)
(Anonymous) main())

Nested Structures in C

Nested structures in C allow us to define one structure inside another. They help manage and group
related data logically and hierarchically. This approach is particularly useful for complex data
models, where the relationships between the data entities must be well-defined and organized.

Why are Nested Structures Needed?

1. Logical Grouping: They help organize related data hierarchically within a single
structure.
2. Ease of Access: Simplify accessing and updating specific components of a larger
structure.
3. Reusability: By nesting structures, you can reuse one structure in multiple larger
structures, reducing redundancy.
4. Improved Readability: Helps to maintain clean and readable code by separating
complex data into manageable parts.

Syntax of Nested Structures


struct Outer {
struct Inner {
// Fields of Inner
} innerVariable;
// Fields of Outer
};

How to Access Fields in Nested Structures

Access fields using the dot (.) operator:

Outer structure field:

person1.name;

Nested structure field:

person1.address.street;

Pointer to structure: Use the arrow (->) operator when accessing nested fields via pointers.

personPtr->address.city;

Advantages of Nested Structures

 Simplifies representation of hierarchical data.


 Reduces redundancy in code.
 Ensures better readability and maintenance of code.
 Promotes modularity by allowing reuse of inner structures in multiple places.
Examples of Nested Structures

Here’s an example of a nested structure where a student's date of birth (DOB) is included as a
nested structure.

Code Example: Student with Date of Birth


#include <stdio.h>

// Define the inner structure for Date of Birth


struct Date {
int day;
int month;
int year;
};

// Define the outer structure for Student


struct Student {
int id;
char name[50];
struct Date dob; // Nested structure for Date of Birth
};

int main() {
struct Student student1;

// Input student details


printf("Enter Student ID: ");
scanf("%d", &student1.id);
printf("Enter Student Name: ");
scanf("%s", student1.name);
printf("Enter Date of Birth (DD MM YYYY): ");
scanf("%d %d %d", &student1.dob.day, &student1.dob.month,
&student1.dob.year);

// Display student details


printf("\nStudent Details:\n");
printf("ID: %d\n", student1.id);
printf("Name: %s\n", student1.name);
printf("Date of Birth: %02d-%02d-%04d\n", student1.dob.day,
student1.dob.month, student1.dob.year);

return 0;
}
Sample Input and Output

Input:

Enter Student ID: 101


Enter Student Name: John
Enter Date of Birth (DD MM YYYY): 15 08 2005

Output:

Student Details:
ID: 101
Name: John
Date of Birth: 15-08-2005

Explanation

1. Structure Definition:
o The struct Date represents the day, month, and year fields for the date of birth.
o The struct Student contains the id, name, and a nested struct Date named
dob.
2. Input and Access:
o student1.dob.day is used to access the day part of the DOB.
o Similarly, student1.dob.month and student1.dob.year are used for the month
and year.
3. Formatting:
o The %02d ensures two-digit formatting for day and month, and %04d ensures four-
digit formatting for the year.

This example is highly practical for applications like student records management systems!

Example 2: Address and Person Details

A real-life scenario where nested structures are useful is for managing a person's details, including
their address.

#include <stdio.h>

// Define inner structure


struct Address {
char street[50];
char city[20];
int zipCode;
};
// Define outer structure
struct Person {
char name[30];
int age;
struct Address address; // Nested structure
};

int main() {
struct Person person1;

// Input data
printf("Enter Name: ");
scanf("%s", person1.name);
printf("Enter Age: ");
scanf("%d", &person1.age);
printf("Enter Street: ");
scanf("%s", person1.address.street);
printf("Enter City: ");
scanf("%s", person1.address.city);
printf("Enter Zip Code: ");
scanf("%d", &person1.address.zipCode);

// Display data
printf("\nDetails:\n");
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Address: %s, %s, %d\n", person1.address.street,
person1.address.city, person1.address.zipCode);

return 0;
}

Example 3: Company Employee and Department

Managing employee details and their department information.

#include <stdio.h>

// Define inner structure


struct Department {
int deptId;
char deptName[30];
};

// Define outer structure


struct Employee {
char name[30];
int empId;
struct Department dept; // Nested structure
};

int main() {
struct Employee emp;

// Input employee details


printf("Enter Employee Name: ");
scanf("%s", emp.name);
printf("Enter Employee ID: ");
scanf("%d", &emp.empId);
printf("Enter Department ID: ");
scanf("%d", &emp.dept.deptId);
printf("Enter Department Name: ");
scanf("%s", emp.dept.deptName);

// Display details
printf("\nEmployee Details:\n");
printf("Name: %s\n", emp.name);
printf("ID: %d\n", emp.empId);
printf("Department ID: %d\n", emp.dept.deptId);
printf("Department Name: %s\n", emp.dept.deptName);

return 0;
}

Complex Example: Library System

This example models a library system with books, their authors, and publishers.

#include <stdio.h>

// Define inner structures


struct Author {
char name[30];
char country[30];
};

struct Publisher {
char name[30];
char location[30];
};

// Define outer structure


struct Book {
char title[50];
struct Author author;
struct Publisher publisher;
};

int main() {
struct Book book1;

// Input details
printf("Enter Book Title: ");
scanf("%s", book1.title);
printf("Enter Author Name: ");
scanf("%s", book1.author.name);
printf("Enter Author Country: ");
scanf("%s", book1.author.country);
printf("Enter Publisher Name: ");
scanf("%s", book1.publisher.name);
printf("Enter Publisher Location: ");
scanf("%s", book1.publisher.location);

// Display book details


printf("\nBook Details:\n");
printf("Title: %s\n", book1.title);
printf("Author: %s (%s)\n", book1.author.name,
book1.author.country);
printf("Publisher: %s, %s\n", book1.publisher.name,
book1.publisher.location);

return 0;
}

Explanation of Nested Structure Access

 Author Details:
o Access author's name: book1.author.name
o Access author's country: book1.author.country
 Publisher Details:
o Access publisher's name: book1.publisher.name
o Access publisher's location: book1.publisher.location

This example demonstrates the importance of grouping related fields (like author and publisher)
logically within a larger structure.

Nested structures are powerful for modeling real-world entities with logical hierarchies, ensuring
clean and manageable code.
Case Study 1:

Structures are needed to write programs for complex numbers in C (and similar languages) because
complex numbers have two components: a real part and an imaginary part. These two parts are
logically connected and should be treated as a single entity for better organization, readability, and
efficiency in operations. Here's a detailed explanation:

Why Use Structures for Complex Numbers?

1. Group Related Data Together:


o A complex number consists of two parts:
 Real Part (Re)
 Imaginary Part (Im)
o Using a structure allows us to group these two related components into a single
data type, making the program easier to understand and maintain.
2. Improved Code Readability:
o Instead of using two separate variables (e.g., real1, imag1, real2, imag2),
you can represent a complex number as a single structured variable. This reduces
confusion and enhances clarity.
3. Simplified Operations:
o When performing operations (addition, subtraction, multiplication, etc.) on
complex numbers, it’s much easier to pass and return a single structured variable
instead of handling multiple separate variables.
4. Reusability:
o The structure for complex numbers can be reused across different functions and
parts of the program without redefining the components.
5. Maintainability:
o If you ever need to extend the definition (e.g., include a modulus or phase angle),
structures make it easier to scale.

How to Represent Complex Numbers Using Structures

Using structures, you can define a custom data type Complex to represent the real and imaginary
parts of a complex number. Here's an example:

#include <stdio.h>

// Define a structure for complex numbers


struct Complex {
float real; // Real part
float imaginary; // Imaginary part
};
Example: Why Structures Simplify Complex Number Operations

Without Structures:

You would need separate variables for the real and imaginary parts:

float real1, imag1, real2, imag2; // Components of two complex numbers


real1 = 2.5;
imag1 = 3.0;
real2 = 1.0;
imag2 = 4.5;

// Adding the two complex numbers


float resultReal = real1 + real2;
float resultImag = imag1 + imag2;

printf("Sum: %.2f + %.2fi\n", resultReal, resultImag);

Here, managing separate variables for every operation becomes tedious, especially as the
program grows.

With Structures:

The same program becomes simpler and more logical:

#include <stdio.h>

// Define a structure for complex numbers


struct Complex {
float real;
float imaginary;
};

// Function to add two complex numbers


struct Complex addComplex(struct Complex c1, struct Complex c2) {
struct Complex result;
result.real = c1.real + c2.real;
result.imaginary = c1.imaginary + c2.imaginary;
return result;
}

int main() {
struct Complex c1 = {2.5, 3.0}; // First complex number
struct Complex c2 = {1.0, 4.5}; // Second complex number
struct Complex sum = addComplex(c1, c2);
printf("Sum: %.2f + %.2fi\n", sum.real, sum.imaginary);
return 0;
}
Operations Made Easier with Structures

With a structure, you can define reusable functions to perform various operations on complex
numbers, such as:

Addition:

result.real = c1.real + c2.real;


result.imaginary = c1.imaginary + c2.imaginary;

Subtraction:

result.real = c1.real - c2.real;


result.imaginary = c1.imaginary - c2.imaginary;

Multiplication:

result.real = c1.real * c2.real - c1.imaginary * c2.imaginary;


result.imaginary = c1.real * c2.imaginary + c1.imaginary * c2.real;

Division: Division requires normalizing the denominator and can also be implemented
cleanly using structures.

CASE STUDY 2 :

Example 1 : dynamic memory allocation to handle an array of structures using double pointers
(struct Student **ptr).

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

struct Student {
int rollNo;
char name[20];
float marks;
};

int main() {

int n,i;
printf("Enter the number of students\n");
scanf("%d",&n);
struct Student ** ptr;
// Allocating memory dynamically
ptr = (struct Student **)calloc(n,sizeof(struct Student*));

if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for(i=0;i<n;i++){
ptr[i] = (struct Student *)malloc(sizeof(struct Student));
printf("Enter Roll No, Name, and Marks of student %d:\n",i+1);
scanf("%d", &(ptr[i]->rollNo));
scanf("%s", ptr[i]->name);
scanf("%f", &(ptr[i]->marks));
}
// Displaying output
for(i=0;i<n;i++){
printf("\nStudent %d Details:\n",i+1);
printf("Roll No: %d, Name: %s, Marks: %.2f\n", ptr[i]->rollNo, ptr[i]-
>name, ptr[i]->marks);
}
// Freeing allocated memory
free(ptr);

return 0;
}

1. Why use a double pointer (struct Student **ptr) here?

A double pointer (struct Student **) is used to represent a dynamically allocated array of
pointers to structures. Here's why:

 Each struct Student occupies a certain amount of memory (depending on its


members).
 To store multiple struct Student objects, we need to allocate an array of pointers,
where each pointer can dynamically point to a memory block holding a struct
Student.

The double pointer allows us to:

1. Allocate memory for an array of pointers (struct Student *).


2. For each pointer, dynamically allocate memory for individual struct Student objects.

2. Is it a 2D array?

No, this is not a true 2D array. However, the use of a double pointer might give the appearance
of a 2D array because:

 The first level of pointers (ptr) is like a "row" array, storing addresses of the individual
struct Student objects.
 The second level represents each struct Student object, where each "row" can have its
own allocated memory.

In reality, this is more like an array of dynamically allocated structures rather than a true 2D
array. The relationship can be visualized as:
ptr ->[ pointer to Student1, pointer to Student2, ..., pointer to Student n ]
| | |
V V V
{ struct Student } { struct Student } { struct Student }

3. Dynamic Memory Allocation Process

Step 1: Allocate an array of pointers

ptr = (struct Student **)calloc(n, sizeof(struct Student *));

 ptr is a double pointer. It holds the addresses of multiple struct Student * pointers.
 calloc allocates space for n pointers (of type struct Student *).
 After this step, ptr is an array of n uninitialized pointers (all set to NULL by calloc).

Step 2: Allocate memory for each struct Student

ptr[i] = (struct Student *)malloc(sizeof(struct Student));

 For each i, this line dynamically allocates memory for a single struct Student object.
 ptr[i] is now pointing to the memory block allocated for that specific student.

Step 3: Populate data

scanf("%d", &(ptr[i]->rollNo));
scanf("%s", ptr[i]->name);
scanf("%f", &(ptr[i]->marks));

 ptr[i] is dereferenced to access the struct Student at that address.


 ptr[i]->rollNo accesses the roll number of the i-th student.
 ptr[i]->name accesses the name, and ptr[i]->marks accesses the marks.

Step 4: Display the data

printf("Roll No: %d, Name: %s, Marks: %.2f\n", ptr[i]->rollNo, ptr[i]->name,


ptr[i]->marks);

 Similar to the input step, this dereferences ptr[i] to access the data fields of the i-th
student.

Step 5: Free memory

free(ptr);

 Issue here: While free(ptr) releases the memory allocated for the array of pointers, the
memory allocated for each struct Student object is not freed explicitly. This can lead
to a memory leak.
4. Why not use a single pointer (struct Student *ptr)?

A single pointer (struct Student *ptr) could be used to allocate a contiguous block of
memory for all n students, like this:

struct Student *ptr = (struct Student *)calloc(n, sizeof(struct Student));

This is simpler and does not require a double pointer. You can access each student as:

ptr[i].rollNo, ptr[i].name, ptr[i].marks

However, this method allocates a single block of memory for all n students, which:

 Does not allow for independent allocation of each student.


 Makes it difficult to deallocate memory for individual students.

The double-pointer method provides flexibility, as each struct Student can be allocated and
deallocated independently.

5. What’s happening in this code? (Visualization)

Assume n = 3. After each step:

Step 1: Allocate an array of pointers

ptr -> [ NULL, NULL, NULL ]

Step 2: Allocate memory for each struct Student

ptr -> [ Address1, Address2, Address3 ]


| | |
V V V
{ Student1 } { Student2 } { Student3 }

Step 3: Populate data

Each struct Student now contains the entered roll number, name, and marks:

ptr -> [ Address1, Address2, Address3 ]


| | |
V V V
{ 1, "Alice", 85.5 } { 2, "Bob", 90.0 } { 3, "Charlie", 78.5 }

Step 4: Display data

The program accesses each student and displays their details.


6. Memory Deallocation Issue

In the current code:

free(ptr);

This only frees the memory allocated for the array of pointers ( ptr) but not the memory
allocated for each student. To avoid memory leaks:

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


free(ptr[i]); // Free memory for each student
}
free(ptr); // Free memory for the array of pointers

7. Conclusion

 A double pointer (struct Student **) is used to dynamically manage an array of


pointers, providing flexibility in allocating and freeing memory.
 The array of pointers resembles a 2D structure but is not a true 2D array.
 The code needs improvement in memory deallocation to avoid leaks.

Self-Referential Structures

A self-referential structure in C is a structure that contains a pointer to itself or another instance


of the same structure. This means the structure has a member that is a pointer to the same type as
the structure.

Syntax:

struct Node {
int data;
struct Node *next; // Pointer to the same type of structure
};

In this example, the structure Node contains a pointer (next) to another Node.

Why is it Required?

Self-referential structures are essential for creating dynamic and recursive data structures.
They allow structures to "link" with one another to form complex data relationships.

Advantages:

1. Dynamic Data Structures: Enable the creation of linked lists, trees, and graphs, where
the size can grow or shrink dynamically.
2. Efficient Memory Usage: Memory is allocated only when needed, avoiding the need to
allocate a large fixed array.
3. Recursive Algorithms: Useful in recursive definitions of data structures like trees
(binary trees, binary search trees, etc.).
4. Flexibility: Provide a flexible way to handle relationships among data elements, like
parent-child or predecessor-successor relationships.

Examples of Self-Referential Structures

1. Linked Lists:
o Used in memory management, file systems, and data queues.
2. Binary Trees:
o Common in databases and searching algorithms.
3. Graphs:
o Used in network routing and shortest path algorithms.
4. Stacks and Queues:
o Used in operating systems, scheduling, and recursion management.

Simpler Examples:

Self-referential structures can also be used in simpler scenarios, like creating relationships
among objects, organizing data that references itself, or managing data with cycles. Below are
examples that are simpler and easier to grasp than data structures like linked lists and trees.

1. Employee and Manager Relationship

An employee structure can have a pointer to another employee (e.g., their manager).

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

struct Employee {
int id;
char name[20];
struct Employee *manager; // Pointer to the manager (another Employee)
};

int main() {
struct Employee emp1, emp2;

// Initialize employee 1 (Manager)


emp1.id = 101;
sprintf(emp1.name, "Alice");
emp1.manager = NULL; // No manager, Alice is the manager

// Initialize employee 2
emp2.id = 102;
sprintf(emp2.name, "Bob");
emp2.manager = &emp1; // Bob's manager is Alice
// Display information
printf("Employee: %s, ID: %d\n", emp2.name, emp2.id);
printf("Manager: %s, ID: %d\n", emp2.manager->name, emp2.manager->id);

return 0;
}

2. Family Tree (Parent-Child Relationship)

A simple structure to represent a family member, referencing their parent.

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

struct Person {
char name[20];
struct Person *parent; // Pointer to the parent (another Person)
};

int main() {
struct Person child, parent;

// Initialize parent
sprintf(parent.name, "John");

// Initialize child
sprintf(child.name, "Emma");
child.parent = &parent; // Emma's parent is John

// Display family relationship


printf("Child: %s\n", child.name);
printf("Parent: %s\n", child.parent->name);

return 0;
}

3. Circular Reference for a Simple Relationship

A self-referential structure can be used to represent objects that refer back to each other.

#include <stdio.h>

struct Friend {
char name[20];
struct Friend *bestFriend; // Pointer to another Friend
};

int main() {
struct Friend alice, bob;

// Initialize Alice
sprintf(alice.name, "Alice");
alice.bestFriend = &bob; // Alice's best friend is Bob

// Initialize Bob
sprintf(bob.name, "Bob");
bob.bestFriend = &alice; // Bob's best friend is Alice

// Display relationships
printf("%s's best friend is %s.\n", alice.name, alice.bestFriend->name);
printf("%s's best friend is %s.\n", bob.name, bob.bestFriend->name);

return 0;
}

4. Simple Network Node

A structure where each node references another connected node.

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

struct Node {
int id;
struct Node *connectedNode; // Pointer to another Node
};

int main() {
struct Node node1, node2;

// Initialize nodes
node1.id = 1;
node2.id = 2;

node1.connectedNode = &node2; // Node 1 is connected to Node 2


node2.connectedNode = &node1; // Node 2 is connected to Node 1

// Display connections
printf("Node %d is connected to Node %d\n", node1.id,
node1.connectedNode->id);
printf("Node %d is connected to Node %d\n", node2.id,
node2.connectedNode->id);

return 0;
}

5. Simple Inventory Tracking

A product that points to a similar or related product in the inventory.

#include <stdio.h>

struct Product {
char name[20];
float price;
struct Product *relatedProduct; // Pointer to a related product
};

int main() {
struct Product product1, product2;
// Initialize products
sprintf(product1.name, "Laptop");
product1.price = 50000.0;

sprintf(product2.name, "Mouse");
product2.price = 1000.0;

product1.relatedProduct = &product2; // Laptop is related to Mouse


product2.relatedProduct = NULL; // Mouse has no related product

// Display product information


printf("Product: %s, Price: %.2f\n", product1.name, product1.price);
if (product1.relatedProduct != NULL) {
printf("Related Product: %s, Price: %.2f\n", product1.relatedProduct-
>name, product1.relatedProduct->price);
}

return 0;
}

6. Course Prerequisites

A course structure that references a prerequisite course.

#include <stdio.h>

struct Course {
char name[30];
struct Course *prerequisite; // Pointer to the prerequisite course
};

int main() {
struct Course course1, course2;

// Initialize courses
sprintf(course1.name, "C Programming");
course1.prerequisite = NULL; // No prerequisite for this course

sprintf(course2.name, "Data Structures");


course2.prerequisite = &course1; // Prerequisite for Data Structures is
C Programming

// Display course information


printf("Course: %s\n", course2.name);
if (course2.prerequisite != NULL) {
printf("Prerequisite: %s\n", course2.prerequisite->name);
}
return 0;
}
Unions in C:
What is a Union?

A union in C is a user-defined data type similar to a structure but with one key difference: all
members of a union share the same memory location. This means that at any given time, a
union can hold only one value, and the size of a union is equal to the size of its largest member.

Why Are Unions Required?

1. Efficient Memory Usage: Unlike structures, which allocate memory for all members, a
union allocates memory only for the largest member, making it ideal for scenarios where
only one value needs to be stored at a time.
2. Type Flexibility: Unions allow multiple representations of the same memory, enabling
type punning (treating the same memory as different data types).
3. Efficient Handling of Intermediate Results: When performing arithmetic operations,
unions can store intermediate values in different formats, reducing redundant memory
allocation.

How Memory is Allocated in a Union


 In a structure, memory is allocated separately for each member.
 In a union, memory is shared, and the total size of the union is equal to the size of its
largest member.

Example: Memory Allocation in Structure vs Union

#include <stdio.h>

// Structure Example
struct StructExample {
int a;
float b;
char c;
};

// Union Example
union UnionExample {
int a;
float b;
char c;
};

int main() {
printf("Size of struct: %lu\n", sizeof(struct StructExample));
printf("Size of union: %lu\n", sizeof(union UnionExample));
return 0;
}
Output (on a 32-bit system):

Size of struct: 12
Size of union: 4

 Structure allocates space for all members (int = 4 bytes, float = 4 bytes, char = 1 byte,
padding).
 Union allocates space only for the largest member (int or float = 4 bytes).

When is a Union Useful?


1. Efficient Memory Management

In embedded systems, memory is limited. Using unions saves memory where multiple variables
are used at different times but never together.

Example: Sensor Data Representation

A temperature sensor might store data in multiple formats (integer for raw values, float for
precise readings):

#include <stdio.h>

union SensorData {
int rawValue;
float calibratedValue;
};

int main() {
union SensorData temp;

temp.rawValue = 1024; // Store raw sensor value


printf("Raw Sensor Value: %d\n", temp.rawValue);

temp.calibratedValue = 36.5; // Store processed/calibrated value


printf("Calibrated Sensor Value: %.2f\n", temp.calibratedValue);

return 0;
}

Note: When calibratedValue is assigned, rawValue gets overwritten.


2. Type Interpretation (Type Punning)

Unions allow interpreting the same memory as different data types.

Example: Floating Point to Binary Conversion

#include <stdio.h>

union FloatInt {
float f;
unsigned int i;
};

int main() {
union FloatInt num;
num.f = 3.14; // Store float value

printf("Float: %.2f\n", num.f);


printf("Binary Representation: %X\n", num.i); // Print as hex (binary
representation)

return 0;
}

Use Case: This technique is useful in low-level bitwise operations, such as checking IEEE-754
floating-point representations.

3. Storing Intermediate Results in Arithmetic Operations

Unions are useful in operations where an intermediate result can be stored in a different format
to save space.

Example: Complex Arithmetic Operations

#include <stdio.h>

union Result {
int intValue;
float floatValue;
};

int main() {
union Result res;

res.intValue = 10;
printf("Integer Result: %d\n", res.intValue);

res.floatValue = 10.5 / 2;
printf("Float Result: %.2f\n", res.floatValue);

return 0;
}
 First, the result is stored as an integer.
 Later, the same memory is used for a floating-point calculation.

4. Variant Data Structures (Handling Multiple Data Types)

In applications like parsing files or network packets, a union can store different types of values.

Example: Packet Data Interpretation

#include <stdio.h>

union Packet {
int intData;
char charData[4];
};

int main() {
union Packet p;

p.intData = 0x41424344; // Hex representation of "ABCD"


printf("Stored as int: %X\n", p.intData);
printf("Stored as char: %s\n", p.charData);

return 0;
}

Use Case: Used in network protocols where a packet may contain different data types.

Comparison: Union vs Structure


Feature Structure Union
Memory Allocates memory for the largest
Allocates memory for all members
Allocation member
Stores multiple values
Data Storage Stores only one value at a time
simultaneously
All members have independent
Access Modifying one member affects others
storage
When only one value is needed at a
Use Case When multiple values are needed
time
Real-Time Scenarios Where Unions are Useful
1. Embedded Systems & IoT

 Efficient memory storage for sensor readings.


 Switching between integer and floating-point values dynamically.

2. Operating Systems

 Used in kernel structures to manage different types of resources with minimal memory.
 Example: Interrupt Descriptor Table (IDT) uses unions for different descriptor formats.

3. Network Programming

 Packet headers often contain different types of data that need to be interpreted differently.

4. File Format Parsers

 Unions help in parsing binary file formats, where a section of data can have different
interpretations.

5. Cryptography & Compression Algorithms

 Used in bit manipulation techniques for efficient processing.

Conclusion
 Unions provide an efficient way to manage memory by sharing space among different
variables.
 They are particularly useful when only one value needs to be stored at a time.
 They are widely used in low-level programming, embedded systems, network
protocols, and arithmetic operations.
 While they have limitations (overwriting previous values), they are powerful tools when
used correctly.
Case Study 1:

Student Data Management System

This project demonstrates how unions can be used efficiently to store student data where different
attributes are required based on the student's type (Regular/Exchange Student). Instead of storing
all details separately for every student, a union helps save memory by storing only the relevant
information.

Project: Student Data Management System Using Unions

Features:

 Stores student details such as Roll Number, Name, and Type (Regular or Exchange).
 Uses union to store additional details:
o Regular students: CGPA and attendance.
o Exchange students: Country of origin and duration of the program.
 Efficient memory usage since only one type of student-specific data is stored at a time.

C Code Implementation
#include <stdio.h>
#include <string.h>

// Define a structure to store common student details


struct Student {
int roll_no;
char name[50];
char type; // 'R' for Regular, 'E' for Exchange

// Union to store specific details based on student type


union {
struct {
float cgpa;
int attendance; // Percentage
} regular;

struct {
char country[30];
int duration; // Months
} exchange;
} details;
}s1;

// Function to display student details


void displayStudent(struct Student s) {
printf("\nStudent Roll No: %d", s.roll_no);
printf("\nName: %s", s.name);
printf("\nStudent Type: %s", (s.type == 'R') ? "Regular" : "Exchange");
// Access the appropriate union member based on student type
if (s.type == 'R') {
printf("\nCGPA: %.2f", s.details.regular.cgpa);
printf("\nAttendance: %d%%", s.details.regular.attendance);
} else {
printf("\nCountry of Origin: %s", s.details.exchange.country);
printf("\nProgram Duration: %d months", s.details.exchange.duration);
}
printf("\n---------------------------\n");
}

// Main function to demonstrate the usage of union


int main() {
struct Student students[2]; // Array of two students

// First student (Regular)


students[0].roll_no = 101;
strcpy(students[0].name, "Alice Johnson");
students[0].type = 'R';
students[0].details.regular.cgpa = 9.1;
students[0].details.regular.attendance = 85;

// Second student (Exchange)


students[1].roll_no = 102;
strcpy(students[1].name, "David Kim");
students[1].type = 'E';
strcpy(students[1].details.exchange.country, "South Korea");
students[1].details.exchange.duration = 6;

// Display student details


displayStudent(students[0]);
displayStudent(students[1]);

return 0;
}

Explanation of the Code


1. Structure (struct Student):
o Common student attributes: roll_no, name, and type ('R' for Regular, 'E' for
Exchange).
o A union (details) inside the structure stores either Regular or Exchange student
details, not both.
2. Union (details):
o If the student is Regular, it stores:
 cgpa
 attendance
o If the student is Exchange, it stores:
 country
 duration (months)
3. Memory Efficiency:
o The union ensures that either Regular or Exchange student details are stored at a
time, saving memory.
4. Function displayStudent():
o Displays student details.
o Checks the type field to decide which union member to print.
5. Demonstration (main()):
o Two student records are created (one Regular, one Exchange).
o The details are assigned and displayed using displayStudent().

Sample Output
Student Roll No: 101
Name: Alice Johnson
Student Type: Regular
CGPA: 9.10
Attendance: 85%
---------------------------

Student Roll No: 102


Name: David Kim
Student Type: Exchange
Country of Origin: South Korea
Program Duration: 6 months
---------------------------

Why is a Union Useful Here?


✔ Saves Memory: Instead of storing both CGPA and Attendance as well as Country and
Duration, only the relevant details are stored.
✔ Efficient for Mixed Data: Unions handle cases where an entity does not need all
attributes at once.
✔ Ideal for Real-Time Applications: Commonly used in student databases, network
protocols, and sensor data management.

What is typedef in C?

typedef (Type Definition) is a keyword in C programming used to create a new name (alias)
for an existing data type. It makes the code more readable, simplifies complex type
definitions, and improves code portability.
Syntax of typedef
typedef existing_datatype new_typename;

Example 1: Using typedef for Basic Data Types


#include <stdio.h>

// Creating alias for 'unsigned int'


typedef unsigned int UINT;

int main() {
UINT num = 100; // 'num' is now of type 'unsigned int'
printf("Value: %u\n", num);
return 0;
}

Why use this? Instead of writing unsigned int repeatedly, we can simply use UINT.

Why is typedef Needed?


1. Simplifies Complex Declarations
o Used to define new names for complex types like function pointers and
structures.
2. Improves Code Readability
o Shortens long type definitions.
3. Enhances Portability
o If you decide to change a data type later, you only need to modify the typedef
definition.
4. Prevents Errors
o Reduces confusion when using complex structures or function pointers.

Using typedef with Structures


Example 2: Without typedef
#include <stdio.h>

struct Student {
int rollno;
char name[50];
float cgpa;
};

int main() {
struct Student s1 = {101, "Alice", 9.2};
printf("Roll No: %d, Name: %s, CGPA: %.2f\n", s1.rollno, s1.name,
s1.cgpa);
return 0;
}

Problem: Every time we declare a structure variable, we must write struct Student.

Example 3: With typedef


#include <stdio.h>

// Creating an alias 'Student' for 'struct Student'


typedef struct {
int rollno;
char name[50];
float cgpa;
} Student;

int main() {
Student s1 = {101, "Alice", 9.2}; // No need to write 'struct'
printf("Roll No: %d, Name: %s, CGPA: %.2f\n", s1.rollno, s1.name,
s1.cgpa);
return 0;
}

Advantage: Now, we can simply use Student instead of struct Student, making the code
cleaner.

typedef with Pointers


Example 4: Defining a Function Pointer
#include <stdio.h>

typedef int (*FuncPtr)(int, int); // Alias for function pointer

int add(int a, int b) {


return a + b;
}

int main() {
FuncPtr fptr = add; // Function pointer assignment
printf("Sum: %d\n", fptr(10, 20));
return 0;
}

Benefit: We don’t have to write complex function pointer syntax repeatedly.


Difference Between typedef and #define
Feature typedef #define
Used for Creating new type names Creating macros
Data types (structs, pointers, Constants, macros, inline
Works with
arrays) functions
Scoping Follows scope rules Global (no scope restrictions)
Checked by
Yes No (handled by preprocessor)
Compiler?

Example: typedef vs #define


#include <stdio.h>

#define INT_ALIAS int


typedef int INT_TYPE;

int main() {
INT_ALIAS x = 10; // Works
INT_TYPE y = 20; // Works
printf("x: %d, y: %d\n", x, y);
return 0;
}

Prefer typedef over #define for type definitions since the compiler performs type
checking.

Conclusion
 typedef is used to create aliases for existing types.
 It simplifies complex type definitions, making the code cleaner and easier to maintain.
 Works well with structures, pointers, and function pointers.
 Prefer typedef over #define for type safety.

Command-line arguments:

Command-line arguments are a powerful way to provide input to a program when it's executed
from the command line or terminal. They allow users to customize a program's behavior without
modifying its source code.

What are Command-Line Arguments?

 Essentially, they're extra pieces of information you type after the program's name when
you run it.
 These arguments are then passed to the program, which can use them to alter its operation.
In C, command-line arguments are handled by the main() function, which can take two
parameters:

Syntax:

Here's the typical main() function declaration:

int main(int argc, char *argv[]) {


// ... your code here ...
return 0;
}

 int argc:
o This stands for "argument count."
o It's an integer that holds the number of command-line arguments passed to the
program, including the program's name itself.
o Therefore, argc is always at least 1.
 char *argv[]:
o This stands for "argument vector."
o It's an array of strings (character pointers).
o Each element of argv points to a command-line argument.
o argv[0] contains the program's name.
o argv[1] to argv[argc-1] contain the additional arguments.

Usage:

To use command-line arguments:

1. Compile your C program.


2. Open your command-line terminal.
3. Execute your program, followed by any arguments, separated by spaces.

Example:

If you have a program named ./a.out, you might run it like this:

./a.out cvr mvsr cbit

In this case:

 argc would be 4.
 argv[0] would be "a.out".
 argv[1] would be "cvr".
 argv[2] would be "mvsr".
 argv[3] would be "cbit".
Importance:

 Flexibility: Command-line arguments make programs more versatile by allowing users to


change their behavior without recompiling.
 Automation: They're essential for scripting and automating tasks, where programs need
to be run with different inputs.
 Configuration: They can be used to pass configuration options to a program.
 Testing: They're valuable for testing different scenarios by providing various inputs.

Real-Time Scenarios:

 File Processing:
o A program that processes files might take the filenames as command-line
arguments. For example: ./a.out file1.txt file2.csv.
 Image Manipulation:
o An image processing tool could take arguments for input and output filenames, as
well as options for resizing or filtering: ./a.out input.jpg output.png -
resize 500x500.
 Compilers and Interpreters:
o Compilers and interpreters heavily rely on command-line arguments to specify
source files, output files, and various compilation or execution options: gcc
mycode.c -o myprogram.
 System Utilities:
o Many system utilities, like grep, find, and ls, use command-line arguments to
customize their behavior.
 Database interactions:
o A program that connects to a database could take the database name, username and
password as command line arguments.

In essence, command-line arguments are a fundamental tool for creating flexible and powerful
command-line applications.

C programs that demonstrate the use of command-line arguments

1. Echo Program (Simple):

This program prints all the command-line arguments passed to it.

#include <stdio.h>

int main(int argc, char *argv[]) {


printf("Program Name: %s\n", argv[0]); // Print program name

if (argc > 1) {
printf("Arguments:\n");
for (int i = 1; i < argc; i++) {
printf(" argv[%d]: %s\n", i, argv[i]);
}
} else {
printf("No additional arguments provided.\n");
}

return 0;
}

How to compile and run:

gcc echo.c -o echo


./echo hello world 123

2. Simple Calculator:

This program performs basic arithmetic operations based on command-line arguments.

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

int main(int argc, char *argv[]) {


if (argc != 4) {
printf("Usage: %s <num1> <operator> <num2>\n", argv[0]);
return 1;
}

double num1 = atof(argv[1]);


double num2 = atof(argv[3]);
char operator = argv[2][0];

double result;

switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 != 0) {
result = num1 / num2;
} else {
printf("Error: Division by zero.\n");
return 1;
}
break;
default:
printf("Error: Invalid operator.\n");
return 1;
}
printf("Result: %lf\n", result);
return 0;
}

How to compile and run:

gcc calculator.c -o calculator


./calculator 10 + 5

3. File Grep (Simple):

This program searches for a given string in a file specified as a command-line argument.

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

#define MAX_LINE_LENGTH 1024

int main(int argc, char *argv[]) {


if (argc != 3) {
printf("Usage: %s <search_string> <filename>\n", argv[0]);
return 1;
}

char *search_string = argv[1];


char *filename = argv[2];

FILE *file = fopen(filename, "r");


if (file == NULL) {
perror("Error opening file");
return 1;
}

char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), file) != NULL) {
if (strstr(line, search_string) != NULL) {
printf("%s", line);
}
}

fclose(file);
return 0;
}

How to compile and run:

gcc grep.c -o grep


# create a file called test.txt with some text in it.
./grep "some text" test.txt
Conclusion:

1. Provides dynamic input without user interaction.


2. Efficient in automation & scripting tasks.
3. Essential for file handling & configuration.
4. Avoids unnecessary re-compilation.
5. Helps in passing options (e.g., --help, --version).

Case Study:

To understand how main() and main(int argc, char *argv[]) are related, it's crucial to grasp
the role of the operating system and the C runtime environment.

The Role of the Operating System and C Runtime:

 Operating System (OS):


o When you execute a C program from the command line, the OS is responsible for
loading the program into memory and starting its execution.
o The OS also handles the command-line arguments you provide.
 C Runtime Environment:
o Before your main() function is called, the C runtime environment performs
essential setup tasks.
o One of these tasks is to process the command-line arguments provided by the OS.
o The C runtime then prepares the argc and argv values and passes them to your
main() function.

How it Works:

1. Command-Line Input:
o When you type a command in the terminal, the OS parses the input, separating the
program name from the arguments.
2. OS to C Runtime:
o The OS passes the command-line arguments to the C runtime environment.
3. C Runtime Processing:
o The C runtime:
 Counts the number of arguments and stores it in argc.
 Creates an array of strings (argv) where each element points to a command-
line argument.
 argv[0] typically holds the program's name.
4. Calling main():
o The C runtime then calls your main() function, passing argc and argv as
arguments.
o If your main() function is defined as main(int argc, char *argv[]), it receives
these values.
o If your main() function is defined as main(), the C runtime still performs all the
steps to gather the command line arguments, but your program simply does not
utilize them.

Key Points:

 Whether you use main() or main(int argc, char *argv[]), the OS and C runtime
handle the command-line arguments.
 The difference lies in whether your program chooses to access and use those arguments.
 Therefore, main() is essentially a version of main(int argc, char *argv[]) where the
arguments are not used.
 The C standard allows for these variations of the main function.
 The system, through the C runtime environment, always sets up the command line
arguments, so they are there, it is just a matter of if the programmer chooses to use them.

The C runtime ensures that the command-line arguments are available; it's up to the programmer
to decide whether to use them within the main() function.

Enumeration (enum) :

What is Enumeration in C?

Enumeration (enum) in C is a user-defined data type used to assign named integer constants
to a set of values, improving code readability and maintainability.

Syntax of Enumeration

enum enum_name {
constant1,
constant2,
constant3
};

Example: Defining Days of the Week

#include <stdio.h>
enum Days{ MONDAY=1, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
=0};

int main() {
enum Days today;
today = WEDNESDAY;

printf("Today is day number: %d\n", today);


return 0;
}
Output

Today is day number: 3

Explanation:
By default, enum assigns integer values starting from 0 (i.e., SUNDAY = 0, MONDAY = 1,
TUESDAY = 2, ...).

Why Use Enumeration?

Improves Code Readability

 Instead of using numbers directly, you can use meaningful names.

Avoids Magic Numbers

 Instead of if(day == 1), we can write if(day == MONDAY), making the code self-
explanatory.

Easier Maintenance

 If values change, you only need to modify them in the enum definition.

Ensures Limited Values

 Only predefined values are allowed, reducing errors.

Why Only Certain Values are Allowed?

Unlike other types, enum only accepts predefined constants.


This is because enum maps symbolic names to fixed integer values at compile-time, making it
type-safe.

For example:

enum Color { RED, GREEN, BLUE };


enum Color c = 5; // ERROR: Not an allowed value in the enum

This ensures that variables of enum type are not assigned arbitrary values, maintaining data
integrity.

How is enum Stored Internally?

1. enum is internally stored as an int.


o Compiler assigns integer values (default: 0, 1, 2...).
o You can manually assign values:
enum Color { RED = 10, GREEN = 20, BLUE = 30 };

2. Each value is stored in memory like an integer constant.


3. Only one value at a time is stored in a variable of enum type.

Custom Values in enum

You can assign custom integer values explicitly:

enum Status {
SUCCESS = 1,
FAILURE = -1,
PENDING = 0
};

Example: Checking Status

#include <stdio.h>

enum Status { SUCCESS = 1, FAILURE = -1, PENDING = 0 };

int main() {
enum Status result = SUCCESS;

if (result == SUCCESS) {
printf("Operation was successful!\n");
}
return 0;
}

Output:

Operation was successful!

Where Can You Use enum?

1.Menu-Driven Programs

enum Menu { ADD = 1, DELETE, UPDATE, EXIT };

2.Managing Flags & Status Codes

enum Status { RUNNING, STOPPED, PAUSED };


3.Handling Days/Months/Seasons

enum Month { JAN = 1, FEB, MAR, APR, MAY };

4. Defining State Machines

enum TrafficLight { RED, YELLOW, GREEN };

Difference Between enum, struct, and union

Feature enum struct union


Share memory among
Purpose Define named constants Group related variables
members
Each member gets separate All members share the same
Storage Stored as int
memory memory
Takes memory of int (usually Memory of the largest
Memory Sum of all members' sizes
4 bytes) member
Used for complex data Used for efficient memory
Usage Used for options, states, flags
storage usage

1. Basic Enum Example: Days of the Week


#include <stdio.h>

enum Weekdays { Mon, Tue, Wed, Thu, Fri, Sat, Sun };

int main() {
enum Weekdays today = Wed;
printf("Today is day number %d\n", today); // Output: 2 (since Mon=0)
return 0;
}

Explanation:

 Enums start at 0 by default. Wed becomes 2.


 Use enums to avoid magic numbers like 2 in code.

2. Explicit Values: HTTP Status Codes


#include <stdio.h>
enum HttpStatus { OK = 200, NotFound = 404, ServerError = 500 };

void handle_response(enum HttpStatus status) {


if (status == OK) {
printf("Request succeeded!\n");
} else {
printf("Error code: %d\n", status);
}
}

int main() {
handle_response(OK); // Output: Request succeeded!
handle_response(NotFound); // Output: Error code: 404
return 0;
}

Explanation:

 Assign explicit values to enum constants for clarity.


 Use enums to document valid response codes.

3. State Machine: Traffic Light Control#include <stdio.h>

enum TrafficLight { RED, YELLOW, GREEN };

void control_light(enum TrafficLight light) {


switch (light) {
case RED: printf("STOP\n"); break;
case YELLOW: printf("SLOW DOWN\n"); break;
case GREEN: printf("GO\n"); break;
}
}

int main() {
control_light(RED); // Output: STOP
control_light(GREEN); // Output: GO
return 0;
}

Explanation:

 Enums make state transitions readable.


 Avoids hardcoding states like 0, 1, 2.

4. Bit Flags: File Permissions


#include <stdio.h>

enum Permissions {
READ = 1 << 0, // 0001
WRITE = 1 << 1, // 0010
EXECUTE = 1 << 2 // 0100
};

int main() {
int user_perms = READ | WRITE; // 0011 (3 in decimal)
if (user_perms & READ) {
printf("User has read access.\n");
}
return 0;
}

Explanation:

 Enums can represent bitwise flags for compact storage.


 Use bitwise operators (|, &) to combine/check flags.

5. Enum with Typedef: Directions


#include <stdio.h>

typedef enum { NORTH, SOUTH, EAST, WEST } Direction;

const char* direction_to_string(Direction dir) {


switch (dir) {
case NORTH: return "North";
case SOUTH: return "South";
case EAST: return "East";
case WEST: return "West";
default: return "Unknown";
}
}

int main() {
Direction dir = EAST;
printf("Direction: %s\n", direction_to_string(dir)); // Output: East
return 0;
}

Explanation:

 Use typedef to simplify enum declarations.


 Convert enum values to strings for user-friendly output.
6. Enum in a Loop: Menu System
#include <stdio.h>

enum MenuOption { EXIT, ADD, DELETE, EDIT };

void display_menu() {
printf("\n1. Add\n2. Delete\n3. Edit\n0. Exit\n");
}

int main() {
enum MenuOption choice;
do {
display_menu();
printf("Enter choice: ");
scanf("%d", &choice);
switch (choice) {
case ADD: printf("Adding...\n"); break;
case DELETE: printf("Deleting...\n"); break;
case EDIT: printf("Editing...\n"); break;
}
} while (choice != EXIT);
return 0;
}

Explanation:

 Enums make menu systems more maintainable.


 Avoids magic numbers in loops and conditionals.

7. Error Handling with Enums


#include <stdio.h>

typedef enum { SUCCESS, INVALID_INPUT, FILE_NOT_FOUND } ErrorCode;

ErrorCode process_file(const char* filename) {


if (filename == NULL) return INVALID_INPUT;
// Simulate file check
if (filename[0] == '\0') return FILE_NOT_FOUND;
return SUCCESS;
}

int main() {
ErrorCode err = process_file("data.txt");
if (err == SUCCESS) {
printf("File processed!\n");
} else {
printf("Error code: %d\n", err);
}
return 0;
}

Explanation:

 Enums provide clear error codes instead of arbitrary integers.


 Functions return enum values for better documentation.

8. Enum vs. Struct/Union Comparison


#include <stdio.h>

// Enum for status


enum Status { PENDING, COMPLETED, FAILED };

// Struct to group data


struct Task {
int id;
enum Status status;
};

// Union to share memory


union Data {
int num;
char str[4];
};

int main() {
struct Task task = {1, PENDING};
union Data data;
data.num = 65;
printf("Task %d status: %d\n", task.id, task.status); // Output: 1, 0
printf("Union str: %s\n", data.str); // Output: "A" (ASCII 65)
return 0;
}

Explanation:

 Enums define states (e.g., PENDING).


 Structs group data (e.g., id and status).
 Unions share memory (e.g., num and str).

Key Takeaways

1. Readability: Enums replace magic numbers with meaningful names.


2. Type Safety: While C allows integer assignments, enums encourage restricted
values.
3. Use Cases: State machines, error codes, flags, menus, and categories.
4. Compiler Handling: Enums are stored as integers (size depends on compiler).

Types of Files in C
In C programming, files are broadly classified into two types:
1.Text Files
2.Binary Files

Each type is used for different purposes based on the data format and how the system processes
the file.

1. Text Files
A text file stores data in human-readable ASCII characters. These files can be opened and edited
using a text editor (like Notepad, VS Code).

Characteristics of Text Files

✔ Data is stored in human-readable format (plain text).


✔ Each line ends with a newline character (\n).
✔ Takes more storage due to character encoding.
✔ Slower processing compared to binary files (since conversion is needed).

2. Binary Files
A binary file stores data in machine-readable binary format (0s and 1s). These files are faster
and take up less storage space compared to text files.

Characteristics of Binary Files

✔ Stores data in raw binary format (not human-readable).


✔ Faster access because no conversion is needed.
✔ More efficient storage (uses exact bytes, unlike text files).
✔ Cannot be edited with a normal text editor.
How File Data is Stored Internally in C?
When working with files in C, understanding how text and binary files store data is essential.

1. Text Files (Human-Readable Storage)

In a text file, data is stored as ASCII or Unicode characters. Each character is represented using
its ASCII value (1 byte per character).

Example:

Consider writing the text "123" to a text file.

How "123" is stored in a Text File

Character ASCII Value Binary Representation


'1' 49 00110001
'2' 50 00110010
'3' 51 00110011
\n (newline) 10 00001010

 Each digit is stored as an ASCII character.


 The file size is 4 bytes (3 digits + newline character).
 The newline (\n) ensures that the next write operation starts on a new line.
 When you read this file in C, fscanf() or fgets() converts these ASCII values back
to integer format.

✔ The integer 123 is stored as three separate ASCII characters in the file (49 50 51 in
binary).

2. Binary Files (Machine-Readable Storage)

A binary file stores raw memory representation of data, meaning the data is stored exactly as it
is in RAM.

Example:

Storing the integer 123 in a binary file (instead of a text file).


How 123 is stored in a Binary File

Decimal 4-Byte Binary Representation


123 00000000 00000000 00000000 01111011

 In a 32-bit system, an int typically takes 4 bytes.


 The binary representation of 123 is 00000000 00000000 00000000 01111011.
 The entire number is stored directly in 4 bytes (unlike text files, which store each digit
as an ASCII character).

Text Files vs Binary Files: Storage Differences


Feature Text File (ASCII) Binary File (Raw Memory)
Data Format Human-readable Machine-readable (0s & 1s)
Storage Size Larger (stores characters) Smaller (stores raw bytes)
Read/Write
Slower (needs conversion) Faster (direct memory storage)
Speed
"123" → 49 50 51 00000000 00000000 00000000 01111011
Example Storage (Binary)
(ASCII)
Editable in Notepad/VS
Editing Not editable in text editors
Code

How C Converts Between Text and Binary Files


1. Text File Conversion:

 When writing an integer (123), C converts it to ASCII (49 50 51) before storing it in a
text file.
 When reading, C converts ASCII characters back to an integer (123).

2. Binary File Conversion:

 When writing an integer (123), C stores the raw binary data (01111011).
 When reading, C directly loads the binary representation into memory without
conversion.
Difference Between Standard Input/Output (Console) and File
Input/Output in C
In C, we can read and write data using standard input/output (console) or file input/output (disk
files). The way they work is fundamentally different.

1. Console Input/Output (Using scanf() and printf())


How Does Console I/O Work?

 The console (keyboard & screen) is treated as a default input/output device.


 scanf() reads data from standard input (stdin), which is connected to the keyboard.
 printf() writes data to standard output (stdout), which is connected to the screen.

How Connection Happens?

 The Operating System (OS) creates a terminal (shell/command prompt).


 The program runs inside this terminal, taking input from stdin (keyboard) and
outputting to stdout (screen).
 When you type in a command-line terminal, your keystrokes are buffered before they
are processed.

Example: Console I/O


#include <stdio.h>

int main() {
int num;

printf("Enter a number: "); // Writes to stdout (screen)


scanf("%d", &num); // Reads from stdin (keyboard)

printf("You entered: %d\n", num); // Writes to stdout (screen)

return 0;
}

✔ printf("Enter a number: ") → The text is sent to stdout (screen).


✔ scanf("%d", &num) → The number is read from stdin (keyboard).
2. File Input/Output
How Does File I/O Work?

 A file on disk is treated as a stream of data.


 We must explicitly open the file before reading/writing.
 fscanf() reads data from a file stream instead of stdin.
 fprintf() writes data to a file stream instead of stdout.

How Connection Happens?

1. The C program requests the OS to open a file.


2. The OS checks if the file exists and allows access.
3. A file pointer (FILE) is created*, pointing to the file stream.
4. Data can be read (fscanf) or written (fprintf) through this pointer.
5. The file must be closed (fclose) after use to free resources.

Comparison: Console I/O vs. File I/O


Feature Console I/O (printf, scanf) File I/O (fprintf, fscanf)
Input Source Keyboard (stdin) File stream (FILE *)
Output Destination Screen (stdout) File (disk storage)
Persistence Data is lost after execution Data remains after execution
Opening/Closing
No Yes (fopen, fclose)
Required?
fopen() returns NULL if file not
Error Handling Limited (scanf() failure)
found
Fast (direct memory
Speed Slower (disk read/write operations)
interaction)
Use Case Temporary inputs/outputs Long-term data storage

How is the Console Directly Connected to a Program?

The console (or terminal) acts as an interface between the user and the program. In C, the
standard input (stdin) and standard output (stdout) streams are automatically available when a
program starts executing. The connection between a C program and the console is managed by
the Operating System (OS), using file descriptors, buffers, and system calls.
1. Console I/O Connection in C
printf("Hello, world!\n");

or

scanf("%d", &x);

internally, the OS redirects these calls to read from or write to standard I/O streams (stdin,
stdout, stderr). These streams are connected to the terminal window (console).

How does this connection work?

 The OS provides each running program with three default file streams:
o stdin (Standard Input - Keyboard)
o stdout (Standard Output - Screen)
o stderr (Standard Error - Screen)
 When the program starts, these streams are already open and ready to use.
 Input from the user is temporarily stored in a buffer by the OS until the program reads
it.
 Output from the program is also stored in a buffer before being displayed on the
screen.

2. Console I/O via System Calls

Operating systems provide system calls to handle console input and output. In Linux/Unix, printf
and scanf internally use write() and read() system calls:

Example: How printf() Works Internally

#include <stdio.h>
#include <unistd.h> // For low-level system calls

int main() {
write(1, "Hello, World!\n", 14); // File descriptor 1 = stdout
return 0;
}

Explanation:

 write(1, "Hello, World!\n", 14) directly writes to stdout (file descriptor 1).
 1 is the file descriptor for the console output.
 "Hello, World!\n" is written to the console.
Example: How scanf() Works Internally

#include <stdio.h>
#include <unistd.h>

int main() {
char buffer[20];
read(0, buffer, 20); // File descriptor 0 = stdin
printf("You entered: %s", buffer);
return 0;
}

Explanation:

 read(0, buffer, 20); reads up to 20 bytes from stdin (file descriptor 0).
 The user input is stored in buffer and then printed.

3. Is the Console Directly Connected in All Programming Languages?

Yes, but the implementation differs based on the programming language and OS.

Programming
Console I/O Mechanism
Language
Uses printf(), scanf(), puts(), gets(), internally calls write(),
C
read().
C++ Uses cin, cout, which internally use printf() and scanf().
Uses input() (stdin) and print() (stdout), which interact with
Python
system calls.
Uses System.in, System.out, which are connected to the OS I/O
Java
streams.
JavaScript (Node.js) Uses console.log() for output and process.stdin for input.

4. Console Connection in Different Operating Systems

The way the console is connected to the program depends on the OS.

Windows OS:

 The console is handled by the Windows Terminal (Command Prompt or PowerShell).


 System calls like WriteConsole() and ReadConsole() are used.
 Programs are connected through file descriptors (stdin = 0, stdout = 1).
Linux/Unix:

 The console is a shell (e.g., Bash, Zsh).


 Console I/O is managed through standard file descriptors (0, 1, 2).
 Programs communicate using pipes, redirection (>, <), and system calls.

MacOS:

 Uses Unix-based terminal, similar to Linux.


 The POSIX system calls (write(), read()) are used.

5. Key Differences Between Console I/O and File I/O

Feature Console I/O File I/O


Connection Directly connected to stdin/stdout Requires fopen() to connect to a file
Persistence Data is lost after execution Data is stored permanently in a file
Speed Faster (uses RAM buffer) Slower (writes to disk)
Use Case Temporary input/output Data logging, configuration storage

6. Can We Redirect Console Input/Output?

Yes! Since the console is treated as a file, you can redirect input/output:

Redirect output to a file (Linux/macOS/Windows)

./program > output.txt

Read input from a file

./program < input.txt

Pipe output from one program to another

./program1 | ./program2

Conclusion

✔ The console is directly connected to the program via stdin, stdout, stderr.
✔ OS manages I/O via system calls (write(), read()).
✔ All programming languages provide built-in functions that internally use system calls.
✔ Each OS has a different mechanism, but the fundamental process is the same.
✔ Console I/O is temporary, while File I/O provides permanent storage.

FILE in C:

<stdio.h> (Standard Input/Output Header) is a library in C that provides functions for handling
input and output operations, including console I/O and file I/O.

The FILE data type is defined in <stdio.h>, and it is used to represent a file stream in C. It is
declared as:

typedef struct _IO_FILE FILE;

This abstracts the file system, allowing programs to read and write data to files using various
functions.

File I/O Functions in <stdio.h>


Here are the most important file-handling functions categorized based on their usage:

Operation Function Description


Opens a file in a specific mode
Opening a file fopen()
(read/write/append).
Closing a file fclose() Closes a file to free system resources.
Reading from a fgetc(), fgets(), fscanf(), Reads characters, lines, formatted data,
file fread() or binary data.
fputc(), fputs(), Writes characters, lines, formatted data,
Writing to a file
fprintf(), fwrite() or binary data.
File Positioning fseek(), ftell(), rewind() Moves, retrieves, or resets file position.
Checking End- feof()
Check if the end of the file has been
of-File reached.
Error Handling ferror(), clearerr() Detects or clears errors in file operations.
File Modes in C
When opening a file using fopen(), different modes determine how the file will be accessed:

Mode Description
"r" Opens file for reading (must exist).
"w" Creates a new file or overwrites an existing file.
"a" Opens file for appending (adds content at the end).
"r+" Opens for reading and writing (doesn't create a new file).
"w+" Opens for reading and writing, but overwrites if the file exists.
"a+" Opens for reading and appending (keeps existing content).

Character I/O vs. Binary I/O


1. Character I/O (Text Files)

 Used for storing readable text data (e.g., .txt, .csv).


 Functions: fputc(), fgetc(), fputs(), fgets(), fprintf(), fscanf().
 Example:

FILE *fp = fopen("data.txt", "w");


fputs("Hello, world!", fp);
fclose(fp);

 Best for: Text-based storage, configuration files.

2. Binary I/O (Binary Files)

 Used for storing non-text data (e.g., .exe, .jpg, .dat).


 Functions: fwrite(), fread().
 Example:

FILE *fp = fopen("data.bin", "wb");


int num = 123;
fwrite(&num, sizeof(num), 1, fp);
fclose(fp);

 Best for: Performance-critical applications, images, multimedia files.


Best Functions for Different Use Cases
Use Case Recommended Function
Writing formatted text fprintf()
Reading formatted text fscanf()
Writing a single character fputc()
Reading a single character fgetc()
Writing a string fputs()
Reading a string fgets()
Writing binary data fwrite()
Reading binary data fread()
Checking End-of-File feof()
Seeking a position in a file fseek()

1. Character I/O in C
Character I/O refers to reading and writing data one character at a time in text files. It is simple,
human-readable, and widely used for storing structured data like CSV files, logs, and reports.

How Character I/O Works in C?


1. Text files store data as ASCII/Unicode characters.
2. Each character is encoded into bytes (ASCII = 1 byte, Unicode = 2 bytes or more).
3. C provides functions to read/write characters and strings to/from files.
4. File handling operations require opening and closing files using fopen() and
fclose().

Character I/O Functions in C


Function Description
fputc() Writes a single character to a file
fgetc() Reads a single character from a file
fputs() Writes a string to a file
fgets() Reads a string from a file
fprintf() Writes formatted data to a file (like printf)
Function Description
fscanf() Reads formatted data from a file (like scanf)
feof() Checks if end of file (EOF) is reached
ferror() Checks if an error occurred in file operations

Step-by-Step Character I/O Operations


1.Opening and Closing a File (fopen() and fclose())

Before performing any operation, a file must be opened.

FILE *file;
file = fopen("data.txt", "w"); // Opens "data.txt" in write mode
if (file == NULL) {
printf("Error opening file!");
return 1;
}
fclose(file); // Closes the file

fopen("filename", "mode") opens the file in read/write/append mode.


fclose(file) closes the file and releases system resources.

2.Writing a Single Character (fputc())

Writes one character at a time to a file.

#include <stdio.h>

int main() {
FILE *file = fopen("output.txt", "w"); // Open file in write mode
if (file == NULL) {
printf("Error opening file!");
return 1;
}

fputc('A', file); // Writes 'A' to file


fputc('\n', file); // Writes a newline character

fclose(file); // Close file


return 0;
}

How It Works?

 The ASCII value of 'A' (65) is converted to binary (01000001) and stored.
How is it stored?

File: output.txt
Hexadecimal: 41 0A (41 = 'A', 0A = newline)

3.Reading a Single Character (fgetc())

Reads one character at a time from a file.

#include <stdio.h>

int main() {
FILE *file = fopen("output.txt", "r"); // Open file in read mode
if (file == NULL) {
printf("Error opening file!");
return 1;
}

char ch;
while ((ch = fgetc(file)) != EOF) { // Read until End-of-File
printf("%c", ch);
}

fclose(file); // Close file


return 0;
}

How It Works?

 Reads character-by-character until EOF (End of File).


 EOF is a special marker (-1) indicating no more data.

Encoding in Memory

File contains: "Hello"


Memory (ASCII): 48 65 6C 6C 6F (Hex)

4. Writing a String (fputs())

Writes an entire string to a file.

#include <stdio.h>

int main() {
FILE *file = fopen("message.txt", "w");
if (file == NULL) {
printf("Error opening file!");
return 1;
}
fputs("Hello, File Handling!", file);

fclose(file);
return 0;
}

How It Works?

 fputs() writes the string without adding \n automatically.


 Stored character by character in memory.

Storage in File

File: message.txt
Hex: 48 65 6C 6C 6F 2C 20 46 69 6C 65 20 48 61 6E 64 6C 69 6E 67

5. Reading a String (fgets())

Reads an entire line from a file.

#include <stdio.h>

int main() {
FILE *file = fopen("message.txt", "r");
if (file == NULL) {
printf("Error opening file!");
return 1;
}

char line[100];
fgets(line, 100, file); // Reads max 100 chars

printf("Read from file: %s", line);

fclose(file);
return 0;
}

How It Works?

 Reads characters until a newline (\n) or max size is reached.


 Adds null terminator (\0) at the end.

Storage Format

File: "Hello, World!\n"


Memory: "Hello, World!\0"
6. C Program to Take Input Character by Character and Write to a File
(Filename from Command Line)

This program takes the filename from the command line and reads characters from standard
input (keyboard) until the user enters EOF (Ctrl+D in Linux/Mac or Ctrl+Z in Windows),
then writes them to the specified file.

Key Concepts

 Command Line Arguments: Filename is passed as argv[1].


 File Handling: fopen(), fputc(), fclose().
 Reading Character by Character: getchar().
 EOF Handling: Detect Ctrl+D or Ctrl+Z.

C Program
#include <stdio.h>

int main(int argc, char *argv[]) {


// Check if filename is provided
if (argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}

FILE *file = fopen(argv[1], "w"); // Open file in write mode


if (file == NULL) {
printf("Error opening file!\n");
return 1;
}

char ch;
printf("Enter text (Press Ctrl+D to stop input on Linux/Mac or Ctrl+Z on
Windows):\n");

// Read characters from standard input and write to file


while ((ch = getchar()) != EOF) {
fputc(ch, file); // Write character to file
}

fclose(file);
printf("\nData written to file '%s' successfully!\n", argv[1]);

return 0;
}
How to Compile and Run

Step 1: Compile the Program

gcc program.c -o program

Step 2: Run with Filename

./program output.txt

Step 3: Enter Characters (Type and Press Enter)

Hello, this is a test.


Writing character by character.
Press Ctrl+D (Linux/Mac) or Ctrl+Z (Windows) to stop.

Step 4: Check File Contents

cat output.txt

Output in output.txt

Hello, this is a test.


Writing character by character.

Explanation

1. Check if the filename is provided:


o argc != 2 ensures the program is run correctly.
2. Open the file using fopen(argv[1], "w"):
o "w" mode creates or overwrites the file.
3. Read characters using getchar():
o Reads input character by character from standard input.
4. Write each character to the file using fputc().
5. EOF (Ctrl+D or Ctrl+Z) stops the input.
6. File is closed using fclose().

Alternative: Append to Existing File

To append instead of overwriting, change:

file = fopen(argv[1], "a"); // Opens file in append mode

Now, running the program again adds new text instead of deleting old content.
7. C Program Using fgets() and fputs() with Character Arrays

This program:

1. Takes the filename from the command line.


2. Reads an entire line from standard input using fgets().
3. Writes the line to the file using fputs().
4. Reads back the contents from the file using fgets() and prints them.

C Program
#include <stdio.h>

int main(int argc, char *argv[]) {


// Check if filename is provided
if (argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}

FILE *file;
char buffer[100]; // Character array to store input

// Open file in write mode


file = fopen(argv[1], "w");
if (file == NULL) {
printf("Error opening file for writing!\n");
return 1;
}

// Get input from user


printf("Enter a line of text: ");
fgets(buffer, sizeof(buffer), stdin); // Read input from user

// Write to file
fputs(buffer, file);

fclose(file);
printf("Data written to file successfully!\n");

// Open file in read mode


file = fopen(argv[1], "r");
if (file == NULL) {
printf("Error opening file for reading!\n");
return 1;
}

// Read from file and display


printf("Reading from file:\n");
fgets(buffer, sizeof(buffer), file); // Read from file
printf("%s", buffer);

fclose(file);
return 0;
}

Explanation of fgets() and fputs()

1. fgets(char *str, int n, FILE *stream)

Reads a string (line of text) from the input stream.


Stops when:

 n-1 characters are read (to leave space for \0).


 Newline (\n) is encountered.
 End-of-File (EOF) is reached.

Parameters:

 char *str → Buffer (array) where input is stored.


 int n → Maximum characters to read (n-1 characters + \0).
 FILE *stream → The input stream (stdin for keyboard, file pointer for file).

Example in our program:

fgets(buffer, sizeof(buffer), stdin);

 Reads a line from standard input (keyboard) into buffer.


 Stores at most 99 characters (sizeof(buffer) - 1).
 Automatically adds \0 at the end.

2. fputs(const char *str, FILE *stream)

Writes a string to the given output stream (console or file).


Unlike printf(), it does not format the output.

Parameters:

 const char *str → The string to write.


 FILE *stream → Output stream (file pointer).

Example in our program:

fputs(buffer, file);

 Writes the buffer (user input) to the file.


Sample Execution

1. Compile the Program


gcc program.c -o program

2.Run the Program


./program myfile.txt

3.Input Text
Enter a line of text: Hello, World!

4.Output
Data written to file successfully!
Reading from file:
Hello, World!

8. C Program to Copy Contents from One File to Another

This program:

1. Takes the source and destination filenames from the command line.
2. Reads the contents from the source file.
3. Writes the contents to the destination file.
4. Closes both files after copying.

C Program
#include <stdio.h>

int main(int argc, char *argv[]) {


FILE *sourceFile, *destFile;
char ch;

// Check if correct number of arguments is provided


if (argc != 3) {
printf("Usage: %s <source_file> <destination_file>\n", argv[0]);
return 1;
}

// Open source file in read mode


sourceFile = fopen(argv[1], "r");
if (sourceFile == NULL) {
printf("Error: Cannot open source file %s\n", argv[1]);
return 1;
}

// Open destination file in write mode


destFile = fopen(argv[2], "w");
if (destFile == NULL) {
printf("Error: Cannot open destination file %s\n", argv[2]);
fclose(sourceFile); // Close the source file before exiting
return 1;
}

// Read from source file and write to destination file character by


character
while ((ch = fgetc(sourceFile)) != EOF) {
fputc(ch, destFile);
}

printf("File copied successfully from %s to %s\n", argv[1], argv[2]);

// Close both files


fclose(sourceFile);
fclose(destFile);

return 0;
}

Explanation

1.Opening Files

 Source file is opened in "r" mode to read.


 Destination file is opened in "w" mode to write.
 If the destination file already exists, it overwrites it.

2.Reading & Writing

 Uses fgetc() to read one character at a time.


 Uses fputc() to write each character to the destination file.

3.Error Handling

 If source file doesn’t exist, prints an error and exits.


 If destination file cannot be opened, prints an error and exits.

4.Closing Files

 fclose(sourceFile);
 fclose(destFile);
Ensures that file resources are released.
Sample Execution

1. Compile the Program


gcc copyfile.c -o copyfile

2. Run the Program


./copyfile source.txt destination.txt

3.Output
File copied successfully from source.txt to destination.txt

Formatted Input and Output in C


In C, Formatted I/O (Input and Output) allows us to read and write data in a structured format.
It includes functions like printf(), scanf(), fprintf(), fscanf(), and sprintf(). These
functions help display and process data with specific formats, making them more readable and
usable.

Formatted Output (printf, fprintf, sprintf)

Formatted output functions allow you to display data in specific formats.

1.printf() – Print to Standard Output (Console)

printf() is used to print formatted data on the screen.

Syntax:
printf("format string", arguments);

format string: Defines how data should be displayed.


arguments: The values to be displayed.

Example:
#include <stdio.h>

int main() {
int age = 22;
float height = 5.9;
char name[] = "Sofiya";

printf("Name: %s\n", name);


printf("Age: %d years\n", age);
printf("Height: %.1f feet\n", height);

return 0;
}

Output:
Name: Sofiya
Age: 22 years
Height: 5.9 feet

2.fprintf() – Print to a File

fprintf() writes formatted data to a file instead of the screen.

Syntax:
fprintf(FILE *file_pointer, "format string", arguments);

Example:
#include <stdio.h>

int main() {
FILE *file = fopen("output.txt", "w");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}

int id = 101;
float marks = 95.6;

fprintf(file, "Student ID: %d\nMarks: %.2f\n", id, marks);

fclose(file);
printf("Data written to file successfully.\n");

return 0;
}

Output in output.txt:
Student ID: 101
Marks: 95.60
3.sprintf() – Store Formatted Data in a String

sprintf() formats data and stores it in a string.

Syntax:
sprintf(char *str, "format string", arguments);

Example:
#include <stdio.h>

int main() {
char buffer[100];
int num = 50;
float pi = 3.14159;

sprintf(buffer, "The value of pi is %.2f and num is %d", pi, num);

printf("%s\n", buffer); // Prints the formatted string

return 0;
}

Output:
The value of pi is 3.14 and num is 50

Formatted Input (scanf, fscanf, sscanf)

Formatted input functions allow structured reading of user input.

1.scanf() – Read from Standard Input (Console)

scanf() is used to read formatted input from the user.

Syntax:
scanf("format string", &variable);

Example:
#include <stdio.h>

int main() {
int age;
float weight;
char name[30];

printf("Enter your name, age, and weight: ");


scanf("%s %d %f", name, &age, &weight);

printf("Name: %s, Age: %d, Weight: %.2f kg\n", name, age, weight);

return 0;
}

Input:
Sofiya 22 58.5

Output:
Name: Sofiya, Age: 22, Weight: 58.50 kg

2.fscanf() – Read from a File

fscanf() is similar to scanf() but reads input from a file.

Example:
#include <stdio.h>

int main() {
FILE *file = fopen("input.txt", "r");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}

char name[30];
int age;
float gpa;

fscanf(file, "%s %d %f", name, &age, &gpa);


printf("Name: %s, Age: %d, GPA: %.2f\n", name, age, gpa);

fclose(file);
return 0;
}

Input File (input.txt):


Sofiya 22 9.39
Output:
Name: Sofiya, Age: 22, GPA: 9.39

3.sscanf() – Read from a String

sscanf() reads formatted input from a string.

Example:
#include <stdio.h>

int main() {
char data[] = "Sofiya 22 9.39";
char name[30];
int age;
float gpa;

sscanf(data, "%s %d %f", name, &age, &gpa);


printf("Name: %s, Age: %d, GPA: %.2f\n", name, age, gpa);

return 0;
}

Output:
Name: Sofiya, Age: 22, GPA: 9.39

Format Specifiers

Specifier Type Example


%d Integer 25
%f Float 3.14
%lf Double 3.14159
%c Character A
%s String "Hello"
%x Hexadecimal 0xFF
%o Octal 077
Key Differences: Console vs File I/O

Feature Console (printf & scanf) File (fprintf & fscanf)


Output location Screen (stdout) File (on disk)
Input source Keyboard (stdin) File (on disk)
Uses stdio.h Yes Yes
Formatting Support Yes Yes

Summary

printf() and scanf() are used for console input/output.


fprintf() and fscanf() are used for file input/output.
sprintf() and sscanf() work with strings instead of console/files.
Format specifiers help in structured output (%d, %f, %s, etc.).

2. Binary I/O in C
Binary Input/Output (I/O) in C deals with reading and writing raw binary data to and from files.
Unlike text I/O, which processes data as human-readable characters, binary I/O works with raw
byte sequences, making it faster and more efficient for large or complex data structures.

Why Use Binary Files?

Efficient Storage – Stores data in compact binary format.


Faster Read/Write – No need for text conversion (e.g., ASCII to binary).
Precision – Avoids loss of precision (e.g., floating-point numbers).
Complex Data Storage – Ideal for storing structs, arrays, and large datasets.

Key Functions for Binary File Handling

Binary files in C are handled using fopen(), fwrite(), fread(), fseek(), ftell(), and
fclose().

Function Description
fopen() Opens a file in binary mode ("rb", "wb", "ab", etc.).
fwrite()Writes data to a binary file.
fread() Reads data from a binary file.
fseek() Moves the file pointer to a specific location.
Function Description
ftell() Returns the current position of the file pointer.
fclose() Closes an open file.

Writing to a Binary File (fwrite)

The fwrite() function writes raw bytes from memory to a file.

Syntax:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

ptr → Pointer to the data to write


size → Size of each element in bytes
count → Number of elements to write
stream → Pointer to the file

Example: Writing an Array to a Binary File


#include <stdio.h>

int main() {
FILE *file = fopen("data.bin", "wb"); // Open file in binary write mode
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}

int numbers[] = {10, 20, 30, 40, 50};


fwrite(numbers, sizeof(int), 5, file); // Write entire array

fclose(file);
printf("Data written to binary file successfully.\n");

return 0;
}

Output (Binary Data in data.bin):

(Not human-readable since it's raw binary)


Reading from a Binary File (fread)

The fread() function reads raw binary data from a file.

Syntax:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

ptr → Pointer to buffer where data is stored


size → Size of each element in bytes
count → Number of elements to read
stream → Pointer to file

Example: Reading an Array from a Binary File


#include <stdio.h>

int main() {
FILE *file = fopen("data.bin", "rb"); // Open file in binary read mode
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}

int numbers[5]; // Buffer to store data


fread(numbers, sizeof(int), 5, file); // Read entire array

fclose(file);

printf("Data read from binary file:\n");


for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}

return 0;
}

Output:
Data read from binary file:
10 20 30 40 50
Storing and Reading Structures in Binary Files

Binary I/O is particularly useful for structs because it allows direct memory storage.

Example: Writing and Reading Student Records


#include <stdio.h>

struct Student {
int rollNo;
char name[30];
float marks;
};

int main() {
struct Student s1 = {101, "Sofiya", 95.5};

// Writing to a binary file


FILE *file = fopen("student.bin", "wb");
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}
fwrite(&s1, sizeof(struct Student), 1, file);
fclose(file);

struct Student s2; // Variable to read data

// Reading from the binary file


file = fopen("student.bin", "rb");
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}
fread(&s2, sizeof(struct Student), 1, file);
fclose(file);

printf("Student Details:\nRoll No: %d\nName: %s\nMarks: %.2f\n",


s2.rollNo, s2.name, s2.marks);

return 0;
}

Output:
Student Details:
Roll No: 101
Name: Sofiya
Marks: 95.50
Using fseek() to Move File Pointer

fseek() moves the file pointer to a specific position.

Syntax:
int fseek(FILE *stream, long offset, int whence);
whence Value Meaning
SEEK_SET Beginning of file
SEEK_CUR Current position
SEEK_END End of file

Example: Jump to 3rd Record in a Binary File


fseek(file, 2 * sizeof(struct Student), SEEK_SET);

This moves the file pointer past the first two records, pointing at the third record.

Using ftell() to Get File Pointer Position

The ftell() function tells the current position of the file pointer.

Syntax:
long ftell(FILE *stream);

Example:
long position = ftell(file);
printf("Current File Pointer Position: %ld\n", position);

C Program Using fseek() and ftell() to Manage Student Records

This program demonstrates: ✔ Writing student records to a binary file


✔ Reading all records from the file
✔ Jumping to a specific record using fseek()
✔ Finding the current position using ftell()
Code:
#include <stdio.h>
#include <stdlib.h>

// Structure to store student data


struct Student {
int rollNo;
char name[30];
float marks;
};

// Function to add a new student record


void addStudent(FILE *file) {
struct Student s;
printf("Enter Roll No: ");
scanf("%d", &s.rollNo);
printf("Enter Name: ");
scanf("%s", s.name);
printf("Enter Marks: ");
scanf("%f", &s.marks);

fseek(file, 0, SEEK_END); // Move to end of file


fwrite(&s, sizeof(struct Student), 1, file);
printf("Student record added successfully!\n");
}

// Function to display all student records


void displayStudents(FILE *file) {
struct Student s;
rewind(file); // Move file pointer to beginning
printf("\nStudent Records:\n");
while (fread(&s, sizeof(struct Student), 1, file)) {
printf("Roll No: %d | Name: %s | Marks: %.2f\n", s.rollNo, s.name,
s.marks);
}
}

// Function to jump to a specific student record using fseek


void searchStudent(FILE *file, int rollNo) {
struct Student s;
rewind(file); // Move to start
int index = 0;

while (fread(&s, sizeof(struct Student), 1, file)) {


if (s.rollNo == rollNo) {
printf("\nStudent Found at Position: %ld\n", ftell(file) -
sizeof(struct Student));
printf("Roll No: %d | Name: %s | Marks: %.2f\n", s.rollNo,
s.name, s.marks);
return;
}
index++;
}
printf("\nStudent with Roll No %d not found.\n", rollNo);
}
// Function to get file pointer position
void getFilePosition(FILE *file) {
long position = ftell(file);
printf("\nCurrent File Pointer Position: %ld\n", position);
}

// Main function
int main() {
FILE *file = fopen("students.bin", "rb+");

if (file == NULL) {
file = fopen("students.bin", "wb+"); // Create if not exists
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}
}

int choice, rollNo;


while (1) {
printf("\n1. Add Student\n2. Display All Students\n3. Search Student
by Roll No\n4. Get File Position\n5. Exit\nEnter choice: ");
scanf("%d", &choice);

switch (choice) {
case 1:
addStudent(file);
break;
case 2:
displayStudents(file);
break;
case 3:
printf("Enter Roll No to Search: ");
scanf("%d", &rollNo);
searchStudent(file, rollNo);
break;
case 4:
getFilePosition(file);
break;
case 5:
fclose(file);
return 0;
default:
printf("Invalid choice! Try again.\n");
}
}
}
Explanation of Key Functions

1.seek(file, 0, SEEK_END); → Moves file pointer to end of file before writing a new record.
2.rewind(file); → Moves file pointer back to the start of the file before reading.
3.ftell(file); → Returns current file pointer position, useful to track records.
4.fseek(file, index * sizeof(struct Student), SEEK_SET); → Jumps to specific
record in file.

Sample Run:
1. Add Student
2. Display All Students
3. Search Student by Roll No
4. Get File Position
5. Exit
Enter choice: 1

Enter Roll No: 101


Enter Name: Sofiya
Enter Marks: 94.5
Student record added successfully!

Enter choice: 2
Student Records:
Roll No: 101 | Name: Sofiya | Marks: 94.50

Enter choice: 3
Enter Roll No to Search: 101
Student Found at Position: 0
Roll No: 101 | Name: Sofiya | Marks: 94.50

Enter choice: 4
Current File Pointer Position: 24

---------------------------------The End-------------------------------
UNIT 2

1. Introduction to Data Structures

A data structure is a specialized format for organizing, processing, and storing data efficiently.
The choice of data structure can affect the performance of algorithms and impact memory
usage, speed, and processing power.

2. Why Do We Need Data Structures?

Imagine a library with thousands of books:

 If the books are arranged randomly, finding a book would take hours.
 If books are arranged in an ordered manner, finding a book takes minutes.
 If the books are indexed in a computer system, a search takes seconds!

Similarly, data structures help organize data efficiently so that:

1. Storage is efficient (minimizes memory waste).


2. Searching is fast (optimized retrieval of data).
3. Operations like insertion, deletion, and updating are quick and easy.
4. Multiple users/processes can access data at once.

3. Types of Data Structures

Data structures are broadly categorized into two types:

Type Description Examples


Linear Data Data is arranged sequentially in Arrays, Linked Lists, Stacks,
Structures memory. Queues
Non-Linear Data Data is arranged hierarchically (not
Trees, Graphs, Hash Tables
Structures sequentially).

4. Linear Data Structures (Data stored in a sequence)

1. Arrays

 Definition: A collection of elements stored in contiguous memory locations.


 Operations: Insertion, Deletion, Searching, Sorting.
 Access time: O(1) (Direct access using index).
 Limitations: Fixed size, costly insertions and deletions.
Example:

int arr[5] = {10, 20, 30, 40, 50};


printf("%d", arr[2]); // Output: 30

Use Case: Used in database indexing, 2D matrices, game leaderboards.

2. Linked Lists

 Definition: A collection of nodes, where each node contains data and a pointer to the
next node.
 Operations: Insertions & deletions are O(1) (efficient), but searching is O(n).
 Advantages: Dynamic size (unlike arrays), no memory waste.
 Types: Singly Linked List, Doubly Linked List, Circular Linked List.

Example:

struct Node {
int data;
struct Node* next;
};

Use Case: Memory management, undo functionality in software.

3. Stack

 Definition: A Last In, First Out (LIFO) data structure.


 Operations: Push (Insert), Pop (Remove), Peek (Top Element).
 Implementation: Using Arrays or Linked Lists.
 Time Complexity: All operations O(1).

Example:

#include <stdio.h>
#define SIZE 10
int stack[SIZE], top = -1;
void push(int x) { stack[++top] = x; }
void pop() { top--; }

Use Case: Undo/Redo operations, browser history, expression evaluation.

4. Queue

 Definition: A First In, First Out (FIFO) data structure.


 Operations: Enqueue (Insert), Dequeue (Remove).
 Implementation: Arrays or Linked Lists.
 Time Complexity: All operations O(1).
Example:

#include <stdio.h>
#define SIZE 5
int queue[SIZE], front = -1, rear = -1;
void enqueue(int x) { queue[++rear] = x; }
void dequeue() { front++; }

Use Case: CPU Scheduling, Task management, Print queue.

5. Non-Linear Data Structures (Data stored in a hierarchical manner)

1. Trees

 Definition: A tree is a hierarchical structure where each node has child nodes.
 Types: Binary Tree, Binary Search Tree (BST), AVL Tree, B-Trees.
 Operations: Insert, Delete, Search, Traverse.
 Time Complexity: Insert/Search/Delete = O(log n) in a balanced tree.

Example (Binary Tree Node Definition):

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

Use Case: File systems, Hierarchical databases, AI decision trees.

2. Graphs

 Definition: A set of vertices (nodes) and edges (connections between nodes).


 Types: Directed, Undirected, Weighted, Unweighted.
 Representation: Adjacency Matrix or Adjacency List.
 Traversal Algorithms: DFS (Depth-First Search) and BFS (Breadth-First Search).

Example:

struct Graph {
int vertices;
int adjMatrix[10][10]; // 10x10 adjacency matrix
};

Use Case: Google Maps, Social Networks (Facebook, LinkedIn).


6. Choosing the Right Data Structure

Scenario Best Data Structure


Fixed-size dataset, quick access Arrays
Frequent insertions & deletions Linked List
Backtracking (undo/redo) Stack
Task scheduling Queue
Hierarchical data Tree
Complex relationships (networks, maps) Graph

**List:

1. What is a List?

A list is a linear data structure that stores a collection of elements in a specific order. It allows
insertion, deletion, searching, and traversal of elements.

Key Properties of Lists:


✔ Ordered collection of elements.
✔ Can store homogeneous (same type) or heterogeneous (different type) data.
✔ Can be fixed size (arrays) or dynamic (linked lists).
✔ Provides fast traversal and access to elements.

2. Types of Lists and Their Implementations

A list can be implemented in multiple ways based on how data is stored and accessed.

Type of List Description Implementation


Array-Based
Stores elements in continuous memory locations Array
List
Singly Linked
Each node contains data and a pointer to the next node Linked List
List
Doubly Linked Each node contains data, a pointer to the next node, and
Linked List
List a pointer to the previous node
Circular The last node connects to the first node, forming a
Linked List
Linked List circular structure
Operations on Lists in C

Operation Description Time Complexity (Array-Based List)


Insert an element at the beginning, O(1) (end), O(n) (beginning/middle due
Insertion
middle, or end of the list. to shifting)
Remove an element from the list at O(1) (end), O(n) (beginning/middle due
Deletion
any position. to shifting)
Find an element in the list (Linear O(n) (Linear Search), O(log n) (Binary
Searching
Search / Binary Search). Search - Sorted List)
Visit all elements in the list
Traversal O(n)
sequentially.
Updating Modify an element at a specific index. O(1) (Direct Indexing)

1. Array-Based List (Static List)

What is an Array-Based List?

An array-based list stores elements contiguously in memory. It is fixed in size, and elements
are stored in continuous memory locations.

Key Properties: ✔ Fixed size (allocated at compile time).


✔ Fast element access using an index (O (1)).
✔ Inefficient insertions and deletions (due to shifting elements).
✔ Better for small datasets where size is known in advance.

Basic Operations on Array-Based List

1. Insert an Element in an Array-Based List

We can insert elements at three positions:

 At the end (O(1))


 At a specific index (O(n))
 At the beginning (O(n))

Example: Insert an element at a specific index

#include <stdio.h>
#define SIZE 10

int list[SIZE]; // Array-based list


int count = 0; // Number of elements in the list
// Function to insert an element at a given index
void insert(int index, int value) {
if (count == SIZE) {
printf("List is full!\n");
return;
}
if (index < 0 || index > count) {
printf("Invalid index!\n");
return;
}
// Shift elements to the right
for (int i = count; i > index; i--) {
list[i] = list[i - 1];
}
list[index] = value; // Insert the element
count++;
}

// Function to display the list


void display() {
for (int i = 0; i < count; i++) {
printf("%d ", list[i]);
}
printf("\n");
}

int main() {
insert(0, 10);
insert(1, 20);
insert(1, 15); // Insert 15 at index 1
display(); // Output: 10 15 20
return 0;
}

2. Delete an Element from an Array-Based List

We can delete elements from:

 The end (O(1))


 A specific index (O(n))
 The beginning (O(n))

Example: Delete an element at a given index

#include <stdio.h>
#define SIZE 10

int list[SIZE];
int count = 0;

// Function to delete an element at a given index


void delete(int index) {
if (index < 0 || index >= count) {
printf("Invalid index!\n");
return;
}
// Shift elements to the left
for (int i = index; i < count - 1; i++) {
list[i] = list[i + 1];
}
count--; // Reduce list size
}

// Function to display the list


void display() {
for (int i = 0; i < count; i++) {
printf("%d ", list[i]);
}
printf("\n");
}

int main() {
list[count++] = 10;
list[count++] = 20;
list[count++] = 30;
display(); // Output: 10 20 30
delete(1); // Delete element at index 1
display(); // Output: 10 30
return 0;
}

3. Searching for an Element in an Array-Based List

We can search for elements using:

 Linear Search (O(n))


 Binary Search (O (log n)) (only for sorted lists)

Example: Linear Search

#include <stdio.h>
#define SIZE 10

int list[SIZE] = {10, 20, 30, 40, 50};


int count = 5; // Number of elements

// Function to search for an element


int search(int value) {
for (int i = 0; i < count; i++) {
if (list[i] == value) return i;
}
return -1;
}

int main() {
int index = search(30);
if (index != -1)
printf("Element found at index %d\n", index);
else
printf("Element not found\n");
return 0;
}

4. Traversing an Array-Based List

Example: Traverse and print elements

#include <stdio.h>

int main() {
int list[] = {10, 20, 30, 40, 50};
int size = sizeof(list) / sizeof(list[0]);

printf("List elements: ");


for (int i = 0; i < size; i++) {
printf("%d ", list[i]);
}
printf("\n");
return 0;
}

5. Updating an Element in an Array-Based List

Example: Modify an element at a given index

#include <stdio.h>
#define SIZE 10

int list[SIZE] = {10, 20, 30, 40, 50};


int count = 5;

// Function to update an element at a given index


void update(int index, int newValue) {
if (index < 0 || index >= count) {
printf("Invalid index!\n");
return;
}
list[index] = newValue;
}

int main() {
update(2, 100); // Update index 2 with 100
for (int i = 0; i < count; i++) {
printf("%d ", list[i]);
}
printf("\n");
return 0;
}
Advantages & Disadvantages of Array-Based Lists

✔ Advantages

✔ Fast random access (O (1) for accessing elements).


✔ Simple and easy to implement.
✔ Cache-friendly due to contiguous memory storage.

Disadvantages

Fixed size, cannot grow dynamically.


Expensive insertions/deletions (O(n) shifting required).
Memory wastage if the allocated size is larger than required.

When to Use an Array-Based List?

When the number of elements is known in advance.


When fast access (O (1)) is required using indexing.
When insertions and deletions are rare.

If dynamic resizing and frequent insertions/deletions are needed, use linked lists instead.

Why Linked Lists Are Needed?

In many programming scenarios, arrays are not sufficient due to their fixed size, inefficient
insertions/deletions, and memory usage. This is where linked lists come in.

Limitations of Arrays:

 Fixed Size: Once an array is declared, its size is fixed, leading to either wasted memory
(if the array is too large) or memory shortage (if the array is too small).
 Expensive Insertions/Deletions: Inserting or deleting an element in an array requires
shifting multiple elements, which is time-consuming (O(n) complexity).
 Contiguous Memory: Arrays require memory to be allocated contiguously, which may
not always be available.

Advantages of Linked Lists:


✔ Dynamic Size: Can grow or shrink at runtime as needed.
✔ Efficient Insertions & Deletions: No need to shift elements; only pointers are adjusted (O(1)
complexity for insertions and deletions at the beginning/middle).
✔ Efficient Memory Usage: Allocates memory as needed, preventing wastage.
What Are Linked Lists?

A linked list is a linear data structure where elements (called nodes) are not stored in
contiguous memory locations. Each node contains:
1.Data – The actual value stored in the node.
2.Pointer (next) – A reference to the next node in the list.

Types of Linked Lists:

1. Singly Linked List – Each node has a single pointer to the next node.
2. Doubly Linked List – Each node has pointers to both the next and previous nodes.
3. Circular Linked List – The last node points back to the first node, forming a circular
chain.

Real-Time Applications of Linked Lists

1. Music Playlist

 Each song (node) is connected to the next song.


 Doubly Linked List allows moving forward and backward through songs.

2. Web Browsers (Back & Forward Buttons)

 Browsers use a Doubly Linked List to navigate between visited pages.

3. Undo/Redo Functionality

 Text editors and graphic software use Linked Lists to store different versions of a
document.

4. Dynamic Memory Allocation

 malloc() and free() in C use Linked Lists to manage memory blocks.

5. Task Scheduling in OS 🖥

 Operating Systems use Circular Linked Lists for CPU scheduling in Round Robin.
Key Differences Between Arrays & Linked Lists

Feature Arrays Linked Lists


Size Fixed at declaration Dynamic, grows or shrinks as needed
Memory Usage Wastes extra memory if unused Uses only required memory
Insert/Delete Costly (O(n) shifting needed) Fast (O(1) when modifying pointers)
Random Access Fast (O(1), direct index access) Slow (O(n), need to traverse nodes)
Memory Location Contiguous Non-contiguous
Best Use Case Fast access required Frequent insertions/deletions

C Program: Singly Linked List with All Operations

This program covers all possible operations on a Singly Linked List, including:
Insertion (at beginning, at end, at a specific position)
Deletion (at beginning, at end, at a specific position)
Searching (to find an element)
Reversing the linked list
Displaying the linked list
Counting the number of nodes

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

// Node structure
struct Node {
int data;
struct Node* next;
};

// Function to create a new node


struct Node* createNode(int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;
return newNode;
}

// Function to insert a node at the beginning


void insertAtBeginning(struct Node** head, int value) {
struct Node* newNode = createNode(value);
newNode->next = *head;
*head = newNode;
printf("Inserted %d at the beginning\n", value);
}
// Function to insert a node at the end
void insertAtEnd(struct Node** head, int value) {
struct Node* newNode = createNode(value);
if (*head == NULL) {
*head = newNode;
return;
}
struct Node* temp = *head;
while (temp->next != NULL)
temp = temp->next;
temp->next = newNode;
printf("Inserted %d at the end\n", value);
}

// Function to insert a node at a specific position


void insertAtPosition(struct Node** head, int value, int position) {
struct Node* newNode = createNode(value);
if (position == 1) {
newNode->next = *head;
*head = newNode;
return;
}
struct Node* temp = *head;
for (int i = 1; i < position - 1 && temp != NULL; i++)
temp = temp->next;
if (temp == NULL) {
printf("Invalid position!\n");
return;
}
newNode->next = temp->next;
temp->next = newNode;
printf("Inserted %d at position %d\n", value, position);
}

// Function to delete a node from the beginning


void deleteFromBeginning(struct Node** head) {
if (*head == NULL) {
printf("List is empty!\n");
return;
}
struct Node* temp = *head;
*head = (*head)->next;
printf("Deleted %d from the beginning\n", temp->data);
free(temp);
}

// Function to delete a node from the end


void deleteFromEnd(struct Node** head) {
if (*head == NULL) {
printf("List is empty!\n");
return;
}
struct Node* temp = *head;
struct Node* prev = NULL;
while (temp->next != NULL) {
prev = temp;
temp = temp->next;
}
if (prev == NULL) // Only one node
*head = NULL;
else
prev->next = NULL;
printf("Deleted %d from the end\n", temp->data);
free(temp);
}

// Function to delete a node from a specific position


void deleteFromPosition(struct Node** head, int position) {
if (*head == NULL) {
printf("List is empty!\n");
return;
}
struct Node* temp = *head;
if (position == 1) {
*head = (*head)->next;
printf("Deleted %d from position %d\n", temp->data, position);
free(temp);
return;
}
struct Node* prev = NULL;
for (int i = 1; i < position && temp != NULL; i++) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) {
printf("Invalid position!\n");
return;
}
prev->next = temp->next;
printf("Deleted %d from position %d\n", temp->data, position);
free(temp);
}

// Function to search an element in the linked list


void search(struct Node* head, int key) {
struct Node* temp = head;
int position = 1;
while (temp != NULL) {
if (temp->data == key) {
printf("Element %d found at position %d\n", key, position);
return;
}
temp = temp->next;
position++;
}
printf("Element %d not found!\n", key);
}

// Function to reverse the linked list


void reverse(struct Node** head) {
struct Node *prev = NULL, *current = *head, *next = NULL;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
*head = prev;
printf("Linked list reversed!\n");
}

// Function to display the linked list


void display(struct Node* head) {
if (head == NULL) {
printf("List is empty!\n");
return;
}
struct Node* temp = head;
printf("Linked List: ");
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

// Function to count the number of nodes


int countNodes(struct Node* head) {
int count = 0;
struct Node* temp = head;
while (temp != NULL) {
count++;
temp = temp->next;
}
return count;
}

// Main function
int main() {
struct Node* head = NULL;
int choice, value, position;

while (1) {
printf("\n--- Singly Linked List Operations ---\n");
printf("1. Insert at Beginning\n2. Insert at End\n3. Insert at
Position\n");
printf("4. Delete from Beginning\n5. Delete from End\n6. Delete from
Position\n");
printf("7. Search an Element\n8. Reverse Linked List\n9. Display
Linked List\n");
printf("10. Count Nodes\n11. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("Enter value to insert: ");
scanf("%d", &value);
insertAtBeginning(&head, value);
break;
case 2:
printf("Enter value to insert: ");
scanf("%d", &value);
insertAtEnd(&head, value);
break;
case 3:
printf("Enter value and position: ");
scanf("%d %d", &value, &position);
insertAtPosition(&head, value, position);
break;
case 4:
deleteFromBeginning(&head);
break;
case 5:
deleteFromEnd(&head);
break;
case 6:
printf("Enter position to delete: ");
scanf("%d", &position);
deleteFromPosition(&head, position);
break;
case 7:
printf("Enter value to search: ");
scanf("%d", &value);
search(head, value);
break;
case 8:
reverse(&head);
break;
case 9:
display(head);
break;
case 10:
printf("Total Nodes: %d\n", countNodes(head));
break;
case 11:
printf("Exiting program...\n");
exit(0);
default:
printf("Invalid choice! Try again.\n");
}
}

return 0;
}
Understanding Stacks in Data Structures
1. What is a Stack?

A Stack is a linear data structure that follows the LIFO (Last In, First Out) principle. This
means that the last element added to the stack is the first one to be removed.

Think of a stack of plates in a cafeteria—


You add a plate on top → PUSH operation
You take a plate from the top → POP operation

2. Why Do We Need a Stack When We Already Have Arrays and Linked Lists?

Special Use Case:


While arrays and linked lists provide general storage, stacks provide a specialized way to
access elements (LIFO), making certain operations more efficient.

Here’s why stacks are needed:

Feature Array Linked List Stack


Insertion O(1) (at end) O(1) (at head) O(1) (PUSH)
Deletion O(1) (at end) O(1) (at head) O(1) (POP)
Access Any Element O(1) O(n) Not Allowed
LIFO Operations Not Efficient Not Direct Best for LIFO

Key Difference:

 Arrays and Linked Lists allow accessing any element at any time, but stacks restrict
access to only the top element (Last In, First Out).
 This restriction helps in specific problem-solving scenarios like function calls,
recursion, undo operations, and backtracking.

3. Why is Stack Called an Abstract Data Type (ADT)?

A Stack is called an Abstract Data Type (ADT) because:

 It only defines operations (PUSH, POP, PEEK) without specifying how they are
implemented.
 The underlying implementation can be done using arrays or linked lists, but users only
care about the operations, not how the stack is built internally.

Example:
Imagine a vending machine:
You press a button (operation) to get a snack.
You don't need to know how the machine internally processes your request.
Similarly, in a stack:
You PUSH, POP, and PEEK without worrying about whether it's implemented using an
array or linked list.

4. Real-Life Applications of Stacks

Stacks are used in many real-world applications, including:

Function Calls (Recursion) → The Call Stack manages function calls in programming.
Undo/Redo Operations → Used in text editors like MS Word.
Browser History → Backtracking in web browsers.
Expression Evaluation → Converting infix to postfix, evaluating expressions.
Backtracking Algorithms → Solving mazes, Sudoku, etc.

C Program: Stack ADT using Arrays


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

#define MAX 5 // Maximum size of stack

// Stack structure definition


struct Stack {
int top;
int arr[MAX];
};

// Function to initialize the stack


void initialize(struct Stack *s) {
s->top = -1; // Stack is empty initially
}

// Function to check if stack is empty


int isEmpty(struct Stack *s) {
return (s->top == -1);
}

// Function to check if stack is full


int isFull(struct Stack *s) {
return (s->top == MAX - 1);
}

// Function to push an element into the stack


void push(struct Stack *s, int value) {
if (isFull(s)) {
printf("Stack Overflow! Cannot push %d\n", value);
return;
}
s->arr[++s->top] = value; // Increment top and insert value
printf("%d pushed into stack\n", value);
}

// Function to pop an element from the stack


int pop(struct Stack *s) {
if (isEmpty(s)) {
printf("Stack Underflow! No elements to pop.\n");
return -1;
}
return s->arr[s->top--]; // Return top element and decrement top
}

// Function to peek (view top element)


int peek(struct Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty!\n");
return -1;
}
return s->arr[s->top];
}

// Function to display the stack


void display(struct Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty!\n");
return;
}
printf("Stack elements: ");
for (int i = s->top; i >= 0; i--) {
printf("%d ", s->arr[i]);
}
printf("\n");
}

// Main function
int main() {
struct Stack s;
initialize(&s); // Initialize the stack

int choice, value;

while (1) {
printf("\nSTACK OPERATIONS:\n");
printf("1. Push\n2. Pop\n3. Peek\n4. Display\n5. Exit\nEnter your
choice: ");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("Enter value to push: ");
scanf("%d", &value);
push(&s, value);
break;
case 2:
value = pop(&s);
if (value != -1) {
printf("Popped element: %d\n", value);
}
break;
case 3:
value = peek(&s);
if (value != -1) {
printf("Top element: %d\n", value);
}
break;
case 4:
display(&s);
break;
case 5:
printf("Exiting program.\n");
exit(0);
default:
printf("Invalid choice! Try again.\n");
}
}
return 0;
}

Explanation of the Program


1. struct Stack
o Contains an integer array arr[MAX] to store stack elements.
o top variable to track the top element of the stack.
2. Functions in the Program:
o initialize() → Initializes the stack (top = -1).
o isEmpty() → Checks if the stack is empty.
o isFull() → Checks if the stack is full.
o push() → Adds an element to the stack.
o pop() → Removes and returns the top element.
o peek() → Returns the top element without removing it.
o display() → Prints all stack elements.
3. Menu-Driven Approach:
o User selects operations: Push, Pop, Peek, Display, or Exit.
o The program continues running until the user chooses Exit (5).
Sample Output
STACK OPERATIONS:
1. Push
2. Pop
3. Peek
4. Display
5. Exit
Enter your choice: 1
Enter value to push: 10
10 pushed into stack

Enter your choice: 1


Enter value to push: 20
20 pushed into stack

Enter your choice: 3


Top element: 20

Enter your choice: 4


Stack elements: 20 10

Enter your choice: 2


Popped element: 20

Enter your choice: 4


Stack elements: 10

Enter your choice: 5


Exiting program.

Why is This the Best Way to Implement Stack using Arrays?

✔ Uses a structured approach with struct Stack for better readability.


✔ Implements all core stack operations efficiently.
✔ Uses a menu-driven system for real-time user interaction.
✔ Checks for underflow and overflow conditions to prevent errors.
✔ Ensures modularity through well-defined functions.
C program that implements Stack ADT (Abstract Data Type) using a Singly Linked List,
including all necessary operations like push, pop, peek, and display:

C Program: Stack Using Singly Linked List


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

// Node structure for Stack


struct Node {
int data;
struct Node* next;
};

// Stack top pointer


struct Node* top = NULL;

// Function to check if stack is empty


int isEmpty() {
return top == NULL;
}

// Push operation
void push(int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (!newNode) {
printf("Stack Overflow!\n");
return;
}

newNode->data = value;
newNode->next = top;
top = newNode;
printf("Pushed %d to stack.\n", value);
}

// Pop operation
int pop() {
if (isEmpty()) {
printf("Stack Underflow! Cannot pop.\n");
return -1;
}

struct Node* temp = top;


int popped = top->data;
top = top->next;
free(temp);
return popped;
}

// Peek operation
int peek() {
if (isEmpty()) {
printf("Stack is empty.\n");
return -1;
}
return top->data;
}

// Display stack
void display() {
if (isEmpty()) {
printf("Stack is empty.\n");
return;
}

struct Node* temp = top;


printf("Stack elements (Top to Bottom):\n");
while (temp != NULL) {
printf("%d\n", temp->data);
temp = temp->next;
}
}

// Main function
int main() {
int choice, value;

while (1) {
printf("\n--- Stack Menu ---\n");
printf("1. Push\n2. Pop\n3. Peek\n4. Display\n5. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("Enter value to push: ");
scanf("%d", &value);
push(value);
break;

case 2:
value = pop();
if (value != -1)
printf("Popped value: %d\n", value);
break;

case 3:
value = peek();
if (value != -1)
printf("Top element: %d\n", value);
break;

case 4:
display();
break;

case 5:
printf("Exiting program...\n");
exit(0);

default:
printf("Invalid choice! Try again.\n");
}
}

return 0;
}

Output Example
--- Stack Menu ---
1. Push
2. Pop
3. Peek
4. Display
5. Exit
Enter your choice: 1
Enter value to push: 10
Pushed 10 to stack.

Enter your choice: 1


Enter value to push: 20
Pushed 20 to stack.

Enter your choice: 4


Stack elements (Top to Bottom):
20
10

Enter your choice: 2


Popped value: 20

Enter your choice: 3


Top element: 10

Why Use Linked List for Stack?

 Unlike arrays, linked lists do not have size limits.


 Memory is allocated dynamically—efficient use of space.
 No need to manage index manually like in array stack.
 Suitable when you don’t know how many elements will be pushed in advance.
Applications of Stack (Data Structure)

A Stack is a Last-In-First-Out (LIFO) data structure, meaning the last element inserted is the
first one to be removed. Due to this behavior, stacks are widely used in various real-world
applications.

1. Function Call Management (Recursion)

How it Works?

 When a function is called, the system stores return addresses and local variables in a
stack (call stack).
 When the function completes, the return address is popped to resume execution from
where it left off.

Example: Recursive Function Call


int factorial(int n) {
if (n == 0) return 1; // Base case
return n * factorial(n - 1);
}

Each call to factorial(n) is pushed onto the stack. Once n == 0, the function calls start
returning, popping from the stack.

Real-Time Example:
✔ Function Calls in C, Java, Python
✔ Recursive Algorithms (Factorial, Fibonacci, Tower of Hanoi)

2. Undo/Redo Operations

How it Works?

 Applications like Microsoft Word, Photoshop, Code Editors (VS Code) use a stack to
store actions.
 Undo (Ctrl + Z) → Pops the last action from the stack and restores the previous state.
 Redo (Ctrl + Y) → Pushes the undone action back onto the stack.

Real-Time Example:
✔ Text Editors (Undo/Redo Feature)
✔ Image Editing (Revert Previous Actions)
3. Backtracking Algorithms

How it Works?

 Backtracking involves exploring all possible solutions, reverting back when an


incorrect path is encountered.
 A stack is used to keep track of previous decisions and revert when needed.

Example: Maze Solver

 Move forward and push the direction to the stack.


 If a dead-end is reached, pop and move in a different direction.

Real-Time Example:
✔ Maze Solving
✔ Puzzle Solving (Sudoku, N-Queens)
✔ Pathfinding Algorithms (DFS - Depth First Search)

4. Expression Evaluation and Conversion

How it Works?

Stacks are used to evaluate and convert expressions like:

 Infix to Postfix / Prefix Conversion


 Postfix Expression Evaluation

Example: Postfix Expression Evaluation

Postfix:

5 3 + 8 * = (5 + 3) * 8 = 64

Steps:

1. Push 5
2. Push 3
3. + → Pop 3 and 5, compute 5+3=8, push 8
4. Push 8
5. * → Pop 8 and 8, compute 8*8=64
Real-Time Example:
✔ Compilers (Expression Parsing)
✔ Calculators

5. Parenthesis Matching / Syntax Checking

How it Works?

 Stacks are used to validate balanced parentheses {}, [], () in expressions.


 Every opening bracket is pushed onto the stack.
 Every closing bracket is matched by popping the stack.

Example:

Valid Expression: ( [ a + b ] * c )

 ( → Push
 [ → Push
 ] → Pop [ (matched )
 ) → Pop ( (matched )

If the stack is empty at the end, the expression is balanced.

Real-Time Example:
✔ Code Editors (Syntax Checking)
✔ XML / HTML Tag Matching

6. Browser History (Forward / Back Navigation)

How it Works?

 Every time you visit a webpage, the URL is pushed onto a stack.
 When you click Back, the last visited page is popped.
 If you click Forward, it is pushed back.

Real-Time Example:
✔ Web Browsers (Chrome, Firefox, Edge)
✔ Navigation Systems
7. Memory Management (Stack Memory Allocation)

How it Works?

 Functions, variables, and return addresses are stored in a stack frame.


 When a function is called, a new frame is pushed.
 When the function returns, the frame is popped.

Real-Time Example:
✔ Program Execution (Function Calls)
✔ Stack Overflow Errors (Infinite Recursion)

8. Reversing a String

How it Works?

 Push each character of the string into a stack.


 Pop characters one by one to reverse the string.

Example:

Input: "HELLO"
Stack: H → E → L → L → O
Output: "OLLEH"

Real-Time Example:
✔ String Reversal in Programming

9.Tower of Hanoi (Algorithm Implementation)

How it Works?

 The Tower of Hanoi problem uses a stack to move disks between rods following the
LIFO principle.

Real-Time Example:
✔ Algorithmic Problem Solving
✔ AI and Robotics
Differences: Stack vs Other Data Structures

Feature Stack Queue Array Linked List


Order LIFO FIFO Sequential Non-Sequential
Insert at Any
Insertion Push (Top) Enqueue (Rear) Insert at Index
Position
Remove at Remove at Any
Deletion Pop (Top) Dequeue (Front)
Index Position
Use Function Calls, Undo, Scheduling, Print Fixed Size
Dynamic Storage
Cases Parsing Queue Storage

Summary
✔ Stack is needed when we require a Last-In-First-Out (LIFO) approach.
✔ Used in Function Calls, Undo/Redo, Expression Parsing, Backtracking, Memory
Management, Navigation, and more.
✔ Implemented using Arrays or Linked Lists.

Expression in Computer Science

An expression is a combination of operands (values or variables) and operators that produces a


result when evaluated.

Examples of Expressions:

1. Arithmetic Expression: 5 + 3 * 2
2. Logical Expression: a > b && b < c
3. Relational Expression: x == y

Expressions follow specific rules based on operator precedence and associativity.

What is an Infix Expression?

An infix expression is an expression where operators are placed between operands.

Example:

A+B

Here, + is placed between A and B, which is natural for humans to read.


Complex Example:

A+B*C-D/E

In infix notation, we use operator precedence and associativity to determine the order of
operations.

Operator Precedence Associativity


*/% High Left to Right
+- Medium Left to Right
= Low Right to Left

Why Do We Convert Infix to Prefix & Postfix?

Even though humans prefer infix expressions, computers struggle with them due to ambiguity
and need for parentheses.

Problems with Infix Expression:

1. Requires Parentheses for Clarity:


o Example: (A + B) * C vs. A + (B * C)
o Without parentheses, precedence rules must be strictly followed.
2. Difficult for Machines to Parse Directly:
o Operators are in between operands.
o Extra work needed to maintain precedence.
3. Evaluating Infix Requires a Stack & Two-Pass Algorithm:
o Need a separate stack for operators and operands.
o Must handle precedence manually.

To simplify evaluation, prefix and postfix notations were introduced.

What is a Postfix Expression?

In postfix notation (Reverse Polish Notation - RPN), the operator appears after the
operands.

Example:

A + B → AB+
(A + B) * C → AB+C*
No Parentheses Needed
No Operator Precedence Issues
Can be evaluated in a single pass using a stack.

Evaluating a Postfix Expression:

Postfix: 5 3 + 8 * (equivalent to (5 + 3) * 8)

Steps:

1. Push 5 onto the stack.


2. Push 3 onto the stack.
3. Encounter + → Pop 5, Pop 3, Compute (5+3=8), Push 8.
4. Push 8 onto the stack.
5. Encounter * → Pop 8, Pop 8, Compute (8*8=64), Push 64.
6. Result: 64 (Final value in the stack)

What is a Prefix Expression?

In prefix notation (Polish Notation), the operator appears before the operands.

Example:

A + B → +AB
(A + B) * C → *+ABC

No Parentheses Needed
No Operator Precedence Issues
Can be evaluated from Right to Left using a stack.

Evaluating a Prefix Expression:

Prefix: * + 5 3 8 (equivalent to (5 + 3) * 8)

Steps:

1. Read from Right to Left.


2. Push 8 onto the stack.
3. Push 3 onto the stack.
4. Push 5 onto the stack.
5. Encounter + → Pop 5, Pop 3, Compute (5+3=8), Push 8.
6. Encounter * → Pop 8, Pop 8, Compute (8*8=64), Push 64.
7. Result: 64
Why Use Postfix or Prefix Over Infix?

Feature Infix Postfix Prefix


Hard for Hard for
Readability Easy for humans
humans humans
Parentheses Required Not required Not required
Parsing Complexity High Low Low
Needs precedence
Evaluation Order Left to Right Right to Left
rules
Suitable for
No Yes Yes
Computers

Where Are Prefix & Postfix Used?

Postfix (RPN) Uses

Compilers: Convert infix expressions into postfix for easy evaluation.


Calculators: Many scientific calculators use postfix (e.g., Hewlett-Packard HP calculators).
Expression Evaluators: Scripting languages process arithmetic this way.

Prefix Uses

LISP Programming Language: Uses prefix notation for expressions.


Artificial Intelligence & Robotics: Uses prefix notation for logic calculations.

Summary

Infix Notation → Easy for humans, but requires parentheses & precedence rules.
Postfix Notation → No parentheses needed, efficient evaluation with stacks.
Prefix Notation → Similar to postfix but evaluated right to left.
Why Convert? → Simplifies parsing, avoids precedence issues, and is machine-friendly.
Example :

C program that converts an infix expression to a postfix expression using a stack, along with
detailed step-by-step explanations for each part.

Concepts Recap:

 Infix: Operators are between operands → A + B


 Postfix: Operators come after operands → AB+
 We use a stack to temporarily hold operators and manage precedence and associativity.

Algorithm Steps:
1. If the character is an operand, add it directly to the postfix string.
2. If the character is an operator, push it to the stack after popping operators with higher or
equal precedence.
3. If it’s '(', push to stack.
4. If it’s ')', pop and add to postfix until '(' is found.
5. After the entire input is read, pop all remaining operators from the stack.

C Program with Best Explanation:


#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> // for isalpha() and isdigit()
#include <string.h>

#define MAX 100

// Stack structure
char stack[MAX];
int top = -1;

// Push element to stack


void push(char c) {
if (top == MAX - 1) {
printf("Stack Overflow\n");
return;
}
stack[++top] = c;
}

// Pop element from stack


char pop() {
if (top == -1) {
printf("Stack Underflow\n");
return -1;
}
return stack[top--];
}
// Peek top element
char peek() {
if (top == -1) return -1;
return stack[top];
}

// Check if character is operator


int isOperator(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/' || c == '^');
}

// Return precedence of operators


int precedence(char op) {
switch (op) {
case '^': return 3;
case '*':
case '/': return 2;
case '+':
case '-': return 1;
default: return 0;
}
}

// Check associativity (left for +, -, *, / and right for ^)


int isRightAssociative(char op) {
return (op == '^');
}

// Convert infix to postfix


void infixToPostfix(char* infix, char* postfix) {
int i = 0, k = 0;
char c;

while ((c = infix[i++]) != '\0') {


// If operand, add to postfix
if (isalnum(c)) {
postfix[k++] = c;
}
// If '(', push to stack
else if (c == '(') {
push(c);
}
// If ')', pop till '('
else if (c == ')') {
while (peek() != '(') {
postfix[k++] = pop();
}
pop(); // remove '('
}
// If operator
else if (isOperator(c)) {
while (isOperator(peek()) &&
((precedence(c) < precedence(peek())) ||
(precedence(c) == precedence(peek()) &&
!isRightAssociative(c)))) {
postfix[k++] = pop();
}
push(c);
}
}

// Pop remaining operators


while (top != -1) {
postfix[k++] = pop();
}
postfix[k] = '\0'; // Null terminate postfix string
}

// Main function
int main() {
char infix[MAX], postfix[MAX];

printf("Enter infix expression: ");


scanf("%s", infix);

infixToPostfix(infix, postfix);

printf("Postfix expression: %s\n", postfix);


return 0;
}
Sample Input/Output:
Input: A+(B*C)
Output: ABC*+
Input: (A+B)*C
Output: AB+C*
Input: A+B*C-D/E
Output: ABC*+DE/-

Function Breakdown:

 push, pop, peek: Basic stack operations.


 isOperator: Checks if a character is an operator.
 precedence: Higher number = higher priority.
 isRightAssociative: Important for exponentiation (^) which is right-associative.
 infixToPostfix: Main logic. Traverses the infix expression and builds the postfix using
stack rules.

Example Expression:
Infix: A + B * C - D / E
Expected: ABC*+DE/-
Operator Precedence Table:

Operator Precedence Associativity


^ 3 Right
*/ 2 Left
+- 1 Left

Step-by-Step Dry Run:

We'll walk through the expression A+B*C-D/E.

We'll use:l

 Postfix → Output string


 Stack → To hold operators

Initial State:

 postfix = ""
 stack = []

Step 1: Read A

 It's an operand → add to postfix.


 postfix = "A"
 stack = []

Step 2: Read +

 It's an operator → push to stack.


 postfix = "A"
 stack = [+]

Step 3: Read B

 It's an operand → add to postfix.


 postfix = "AB"
 stack = [+]
Step 4: Read *

 Higher precedence than + → push to stack.


 postfix = "AB"
 stack = [+, *]

Step 5: Read C

 It's an operand → add to postfix.


 postfix = "ABC"
 stack = [+, *]

Step 6: Read -

 Compare with top of stack (*):


o - has lower precedence than *, so pop *.
o Then compare with next top (+) → same precedence → pop +.
 Push - to stack.
 postfix = "ABC*+"
 stack = [-]

Step 7: Read D

 Operand → add to postfix.


 postfix = "ABC*+D"
 stack = [-]

Step 8: Read /

 / has higher precedence than - → push it.


 postfix = "ABC*+D"
 stack = [-, /]
Step 9: Read E

 Operand → add to postfix.


 postfix = "ABC*+DE"
 stack = [-, /]

Final Step: Pop remaining stack

 Pop / → postfix = "ABC*+DE/"


 Pop - → postfix = "ABC*+DE/-"

Final Output:
Postfix: ABC*+DE/-

Diagram Table:

Char Action Stack Postfix


A Operand → Append [] A
+ Push to stack [+] A
B Operand → Append [+] AB
* Push (higher precedence) [+, *] AB
C Operand → Append [+, *] ABC
- Pop *, then + → Push - [-] ABC*+
D Operand → Append [-] ABC*+D
/ Push (higher precedence) [-, /] ABC*+D
E Operand → Append [-, /] ABC*+DE
End Pop /, - [] ABC*+DE/-

Evaluation of postfix expressions in C, using a stack-based approach, with a very detailed


explanation of the logic and each function.

What is Postfix Evaluation?


Given a postfix expression like:

Postfix: 23*54*+9-
You evaluate it left to right, using a stack:

 Push operands onto stack


 When you find an operator, pop the required number of operands (2 for binary ops),
apply the operator, and push result back on stack
 Final result is on top of the stack

Dry Run Example:

For postfix 23*54*+9-, evaluate step by step:

Expression: 23*54*+9-

Step by step:

1. Push 2
2. Push 3
3. * → 2*3 = 6 → Push 6
4. Push 5
5. Push 4
6. * → 5*4 = 20 → Push 20
7. + → 6+20 = 26 → Push 26
8. Push 9
9. - → 26-9 = 17 → Final result = 17

C Program for Postfix Evaluation


#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> // for isdigit()

#define MAX 100

int stack[MAX];
int top = -1;

// Stack push operation


void push(int value) {
if (top == MAX - 1) {
printf("Stack Overflow\n");
return;
}
stack[++top] = value;
}

// Stack pop operation


int pop() {
if (top == -1) {
printf("Stack Underflow\n");
exit(1);
}
return stack[top--];
}

// Check if character is operator


int isOperator(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/');
}

// Evaluate postfix expression


int evaluatePostfix(char* exp) {
int i = 0;
char c;

while ((c = exp[i]) != '\0') {


// If space, skip
if (c == ' ') {
i++;
continue;
}

// If operand (single digit), convert char to int and push


if (isdigit(c)) {
push(c - '0'); // ASCII to int
}
// If operator, pop two elements and apply operation
else if (isOperator(c)) {
int val2 = pop();
int val1 = pop();
int result;

switch (c) {
case '+': result = val1 + val2; break;
case '-': result = val1 - val2; break;
case '*': result = val1 * val2; break;
case '/': result = val1 / val2; break;
}

push(result);
}
else {
printf("Invalid character in expression: %c\n", c);
exit(1);
}

i++;
}

return pop(); // Final result


}

int main() {
char postfix[MAX];

printf("Enter a postfix expression (e.g., 23*54*+9-): ");


scanf("%s", postfix);
int result = evaluatePostfix(postfix);

printf("Result: %d\n", result);


return 0;
}

How Each Part Works:

push() / pop()

 Standard stack operations


 Handles overflow and underflow

isdigit(c)

 Checks if a character is a digit '0' to '9'


 Converts char to int using c - '0'

evaluatePostfix()

 Reads each character


 Pushes digits
 Applies operators by popping last two values
 Final result is left on top of stack

Sample Input/Output:

Input:

Postfix: 23*54*+9-

Output:

Result: 17
Queue ADT (Abstract Data Type)

➤ Definition:

A Queue is a linear data structure that follows the FIFO (First In, First Out) principle. The
element inserted first is removed first.

➤ Real-life Examples:

 A queue of people at a ticket counter.


 Print jobs in a printer queue.
 Task scheduling in operating systems.

➤ Basic Operations of Queue ADT:

Operation Description
enqueue(x) Insert an element x at the rear of the queue.
dequeue() Remove and return the element from the front of the queue.
peek() or front() Return the front element without removing it.
isEmpty() Checks if the queue is empty.
isFull() (for array implementation) Checks if the queue is full.

➤ Queue Terminology:

 Front: Points to the first element of the queue.


 Rear: Points to the position where the next element will be inserted.
 Size: Total number of elements currently in the queue.

Implementation of Queue using Arrays

➤ Concept:

 We use a static array to hold the queue elements.


 Maintain two indexes: front and rear.

➤ Initial Conditions:

front = -1;
rear = -1;
C Program for Queue using Arrays (Linear Queue)

#include <stdio.h>
#define MAX 5

int queue[MAX];
int front = -1, rear = -1;

void enqueue(int val) {


if (rear == MAX - 1)
printf("Queue Overflow!\n");
else {
if (front == -1) front = 0;
queue[++rear] = val;
printf("%d inserted to queue.\n", val);
}
}
void dequeue() {
if (front == -1 || front > rear)
printf("Queue Underflow!\n");
else
printf("Deleted: %d\n", queue[front++]);
}
void display() {
if (front == -1 || front > rear)
printf("Queue is Empty!\n");
else {
printf("Queue elements: ");
for (int i = front; i <= rear; i++)
printf("%d ", queue[i]);
printf("\n");
}
}
int main() {
int choice, val;

do {
printf("\n LINEAR QUEUE MENU \n");
printf("1. Enqueue\n2. Dequeue\n3. Display\n4. Exit\nEnter your
choice: ");
scanf("%d", &choice);

switch(choice) {
case 1: printf("Enter value to enqueue: ");
scanf("%d", &val);
enqueue(val);
break;
case 2: dequeue(); break;
case 3: display(); break;
case 4: printf(" Exiting...\n"); break;
default: printf(" Invalid choice!\n");
}
} while(choice != 4);

return 0;
}
Implementation of Queue using Linked List

➤ Concept:

 Use a dynamic data structure (linked list).


 No fixed size. More efficient than array for varying sizes.

➤ Structure:

struct Node {
int data;
struct Node* next;
};

➤ Maintain:

 front pointer → Points to the front node.


 rear pointer → Points to the last node.

➤ Operations:

Enqueue:

 Create a new node.


 Link it to the rear.
 Move rear to the new node.

Dequeue:

 Remove the front node.


 Move front pointer to the next node.

C program for Queue using Linked List


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

struct Node {
int data;
struct Node* next;
};

struct Node *front = NULL, *rear = NULL;

void enqueue(int val) {


struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = val;
newNode->next = NULL;

if (rear == NULL)
front = rear = newNode;
else {
rear->next = newNode;
rear = newNode;
}
printf(" %d inserted to queue.\n", val);
}

void dequeue() {
if (front == NULL)
printf(" Queue Underflow!\n");
else {
struct Node* temp = front;
printf(" Deleted: %d\n", temp->data);
front = front->next;
if (front == NULL)
rear = NULL;
free(temp);
}
}

void display() {
if (front == NULL)
printf(" Queue is Empty!\n");
else {
struct Node* temp = front;
printf(" Queue elements: ");
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
}

int main() {
int choice, val;

do {
printf("\n LINKED LIST QUEUE MENU \n");
printf("1. Enqueue\n2. Dequeue\n3. Display\n4. Exit\nEnter your
choice: ");
scanf("%d", &choice);

switch(choice) {
case 1: printf("Enter value to enqueue: ");
scanf("%d", &val);
enqueue(val);
break;
case 2: dequeue(); break;
case 3: display(); break;
case 4: printf(" Exiting...\n"); break;
default: printf(" Invalid choice!\n");
}
} while(choice != 4);

return 0;
}

Circular Queue

➤ Problem with Linear Array Queue:

 In a linear array queue, once rear == MAX-1, the queue is considered full even if front
> 0.

➤ Solution:

 Use modulo arithmetic to make the array act like a circle:

rear = (rear + 1) % MAX;

What is Circular Queue?

A Circular Queue is a linear data structure that connects the last position back to the first position
in a circular manner. It overcomes the limitation of a linear array queue where space at the front
cannot be reused after dequeuing.

➤ Initial Conditions:

front = -1;
rear = -1;

➤ isFull condition:

(front == (rear + 1) % MAX)

➤ isEmpty condition:

(front == -1)
Visual Behavior

Let’s assume MAX = 5.

Indexes:
0 1 2 3 4
+---+---+---+---+---+
| | | | | | <- Circular Array
+---+---+---+---+---+

Example Operations:

1. Initial State:

front = -1, rear = -1


Queue is empty

2. Enqueue 10:

front = 0, rear = 0
Queue: [10, _, _, _, _]

3. Enqueue 20, 30, 40:

rear moves forward


Queue: [10, 20, 30, 40, _]
front = 0, rear = 3

4. Dequeue (10 removed):

front moves to 1
Queue: [10 (X), 20, 30, 40, _]

front

5. Enqueue 50, 60:

rear moves circularly (rear = 4, then 0 using (rear+1)%MAX)


Queue: [60, 20, 30, 40, 50]
↑ ↑
front rear
6. Queue Full Condition:

(front == (rear + 1) % MAX)

When rear is just behind front circularly, the queue is full.

Circular Queue in C
#include <stdio.h>
#define MAX 5

int queue[MAX];
int front = -1, rear = -1;

void enqueue(int value) {


if ((front == (rear + 1) % MAX)) {
printf("Queue is full (Overflow).\n");
} else {
if (front == -1) {
front = 0;
}
rear = (rear + 1) % MAX;
queue[rear] = value;
printf("Inserted %d into the circular queue.\n", value);
}
}

void dequeue() {
if (front == -1) {
printf("Queue is empty (Underflow).\n");
} else {
printf("Deleted element: %d\n", queue[front]);
if (front == rear) {
// Queue has only one element
front = rear = -1;
} else {
front = (front + 1) % MAX;
}
}
}

void display() {
if (front == -1) {
printf("Circular Queue is empty.\n");
} else {
printf("Circular Queue elements are: ");
int i = front;
while (1) {
printf("%d ", queue[i]);
if (i == rear)
break;
i = (i + 1) % MAX;
}
printf("\n");
}
}

int main() {
int choice, value;

do {
printf("\nCIRCULAR QUEUE MENU\n");
printf("1. Enqueue\n");
printf("2. Dequeue\n");
printf("3. Display\n");
printf("4. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("Enter value to insert: ");
scanf("%d", &value);
enqueue(value);
break;
case 2:
dequeue();
break;
case 3:
display();
break;
case 4:
printf("Exiting the program.\n");
break;
default:
printf("Invalid choice. Please try again.\n");
}

} while (choice != 4);

return 0;
}
Summary Table of Index Changes
Operation front rear Queue Content
Initial -1 -1 [_____]
Enqueue 10 0 0 [10 _ _ _ _ ]
Enqueue 20 0 1 [10 20 _ _ _ ]
Enqueue 30 0 2 [10 20 30 _ _ ]
Dequeue 1 2 [ X 20 30 _ _ ]
Enqueue 40 1 3 [ X 20 30 40 _ ]
Enqueue 50 1 4 [ X 20 30 40 50 ]
Enqueue 60 1 0 [60 20 30 40 50 ] (wrapped)

Comparison Table

Feature Linear Queue (Array) Linked List Queue Circular Queue

Memory Fixed Dynamic Fixed

Space Utilization Poor (wastage) Good Good

Overflow Condition rear == MAX-1 No overflow (rear + 1)%MAX == front

Underflow Condition front > rear or -1 front == NULL front == -1

Insert at rear O(1) O(1) O(1)

Delete at front O(1) O(1) O(1)


Introduction to Trees: Basic Tree Concepts

What is a Tree?

A Tree is a non-linear, hierarchical data structure used to represent data with a parent-child
relationship. Unlike arrays, linked lists, stacks, and queues which are linear in nature, a tree allows
branching of data in a structured way.

A Tree is defined as a finite set of one or more nodes such that:

 There is a special node called the root.


 The remaining nodes are partitioned into disjoint subtrees, each of which is a tree.

Real-World Analogies of a Tree:

 Family Tree: Grandparent → Parent → Child


 Company Hierarchy: CEO → Managers → Employees
 File System: C:\ → Users → Documents → MyFile.txt

These analogies help us understand the parent-child, ancestor-descendant, and subtree


relationships.

Tree Structure
A (Root)
/ \
B C
/ \ \
D E F

 Root Node: A
 Internal Nodes: B, C
 Leaf Nodes: D, E, F
 Edges: A→B, A→C, B→D, B→E, C→F (5 total)
 Subtrees: Tree rooted at B or C
 Levels: A (level 0), B/C (level 1), D/E/F (level 2)

Key Characteristics of Trees

Feature Description
Non-linear Nodes are connected in a hierarchy, not a sequence.
Hierarchical Each node can have sub-nodes (children), forming a hierarchy.
Rooted Structure A single node acts as the entry point (root).
Recursive Nature Each subtree is itself a tree (enables recursive processing).
Feature Description
Directed Edges Each connection (edge) shows a direction from parent to child.

Formal Definition of Tree

A Tree T is a finite set of nodes such that:

 If T is empty, it is called a null tree.


 If T is non-empty, it consists of:
o A node r (called the root),
o Zero or more subtrees T₁, T₂, ..., Tₙ where each Tᵢ is a tree, and the root of
each Tᵢ is connected to r by a directed edge.

Recursive Nature of Trees

A tree is naturally recursive, because:

 A tree is made of a node and its subtrees, each of which is also a tree.
 This recursive property makes it easy to implement many tree operations using recursion
(like traversals).

Importance of Trees in Data Structures

Usage Area How Trees Are Used


Hierarchical data Represent file systems, HTML DOM, organization charts
Searching Binary Search Trees, AVL Trees, Red-Black Trees
Heaps Priority Queues, Min/Max Heaps
Graphs Spanning Trees in network routing
Databases B-trees, B+ trees for indexing
Compilers Abstract Syntax Trees for parsing
AI Game Trees, Decision Trees

Comparison: Trees vs. Other Structures

Feature Arrays Linked Lists Trees


Structure Linear Linear Hierarchical
Access Time O(1) O(n) O(log n) (for balanced trees)
Use-case Indexed data Sequential data Hierarchical or sorted data
Feature Arrays Linked Lists Trees
Traversal Index or pointer Next pointer Recursion, Queues, Stack

Tree as an Abstract Data Type (ADT)

 ADT for tree typically supports:


o Insertion of nodes
o Deletion of nodes
o Traversal (Inorder, Preorder, Postorder, Level Order)
o Searching for a node
o Finding height, depth, or degree

Advantages of Trees

 Reflect real-world hierarchical data.


 Support fast searching, insertion, and deletion (O(log n) in balanced trees).
 Minimize comparisons (especially in Binary Search Trees).
 Easy to extend for self-balancing, priority-based operations.

Disadvantages of Trees

 More complex to implement than arrays or linked lists.


 Performance is dependent on tree balance.
 Memory overhead due to pointers for left/right children in binary trees.

Summary Points

 Tree is a hierarchical, non-linear data structure.


 Root is the topmost node; every node (except root) has one parent.
 Trees are recursive in nature and support multiple subtrees.
 Trees are widely used in databases, compilers, file systems, and AI.

Tree Terminology
Understanding the terminology of trees is essential for learning how trees work and for building
advanced tree-based data structures like Binary Search Trees, Heaps, and Tries.

Key Terminologies in Trees

1. Node

A node is a fundamental part of a tree that contains:


 Data or a value.
 Zero or more links to other nodes (its children).

Example: In a file system, each file or folder is a node.

2. Root

 The topmost node of a tree.


 It is the only node without a parent.
 Every tree has exactly one root node.

Analogy: In a family tree, the oldest known ancestor is the root.

3. Edge

 An edge is a link between a parent and a child node.


 If there are n nodes in a tree, then there will always be n - 1 edges.

Example: In a tree with 5 nodes, there are 4 edges.

4. Parent and Child

 If node A is connected above node B, then:


o A is the parent
o B is the child
 A node can have multiple children, but only one parent.

Example:

A
/ \
B C

 A is parent of B and C.

5. Siblings

 Nodes that share the same parent are called siblings.

In the above example, B and C are siblings.

6. Leaf / Terminal Node

 A node with no children.


 It is also called an external or terminal node.
Example: In a tree representing a file system, files are usually leaves.

7. Internal Node

 Any node that is not a leaf.


 Has at least one child.

8. Ancestor and Descendant

 Ancestor: Any node above the current node (towards the root).
 Descendant: Any node below the current node (towards the leaves).

9. Subtree

 Any node and all its descendants form a subtree.


 A tree is composed of the root and its subtrees.

Every child node roots a subtree.

10. Path

 A sequence of nodes connected by edges.


 Path length = number of edges in the path.

Example: Path from root to a leaf may look like: A → B → E → G


Length = 3

11. Level of a Node

 The level of the root is 0.


 The level of a node is 1 more than its parent.

Example:

Level 0 → A
Level 1 → B, C
Level 2 → D, E, F

12. Height of a Node / Tree

 Height of a node: The longest path from that node to a leaf.


 Height of the tree: Height of the root node.
 A leaf node has height = 0.
Example:

A ← Height 2
/ \
B C ← Height 1
/
D ← Height 0

13. Depth of a Node

 Depth of a node = length of path from root to that node


 Root has depth 0.

Example:

A ← Depth 0
/ \
B C ← Depth 1
/
D ← Depth 2

14. Degree of a Node

 Number of children a node has.


 A leaf node has degree = 0.
 A binary tree node has degree ≤ 2.

15. Degree of Tree

 Maximum degree among all nodes in the tree.

16. Null Tree / Empty Tree

 A tree that has no nodes.


 Used as a base case in recursion.

17. Forest

 A collection of disjoint trees.


 If you remove the root of a tree, the remaining parts form a forest.

Example Tree with All Terminology:


A
/ \
B C
/ \ \
D E F
Node Parent Children Level Depth Degree Leaf?
A - B, C 0 0 2 No
B A D, E 1 1 2 No
C A F 1 1 1 No
D B - 2 2 0 Yes
E B - 2 2 0 Yes
F C - 2 2 0 Yes

Summary of Key Terms


 Root: Top node
 Leaf: Node with no children
 Internal Node: Node with at least one child
 Level: Distance from root
 Depth: Distance to root
 Height: Distance to farthest leaf
 Degree: Number of children

User Representation of Trees


(Also called: Tree Representation in Memory / Implementation Techniques)

When we want to use trees in programming or store them in memory, we must represent them
using appropriate data structures. There are three major ways to represent trees in user programs:

1. Parent Array Representation


Concept:

 We store the index of the parent for every node.


 Typically done using a 1D array where:
o parent[i] gives the parent of node i.
o Root’s parent is set as -1 or null.

Structure Example:

Node (Index) 0 1 2 3 4 5
Parent[] -1 0 0 1 1 2
Tree Representation:
0
/ \
1 2
/ \ \
3 4 5

Advantages:

 Simple and compact.


 Easy to find parent of any node in O(1) time.

Disadvantages:

 Not suitable for finding children of a node quickly.


 For each node, you need to traverse entire array to find its children.

2. Linked List Representation (Using Child-Sibling or General


Tree Model)

Concept:

Each node stores:

 The data/value
 A pointer to its first child
 A pointer to its next sibling

Node Structure in C (for general trees):


struct Node {
int data;
struct Node* firstChild;
struct Node* nextSibling;
};

Representation:
A
/ \
B C
/ \
D E
Will be stored as:

A -> firstChild → B
B -> firstChild → D
D -> nextSibling → E
B -> nextSibling → C

Advantages:

 Efficient for trees with arbitrary numbers of children.


 Easy to represent general trees (not just binary trees).

Disadvantages:

 Navigation and coding is a bit more complex.


 Not as direct for binary trees.

3. Binary Tree Representation (Using Structure with Left and Right


Pointers)

Concept:

Each node has:

 A left child
 A right child

Node Structure in C:
struct Node {
int data;
struct Node* left;
struct Node* right;
};

This structure is specifically for binary trees, where each node can have at most 2 children.

Tree:
10
/ \
20 30
/ \
40 50
Represented as:

10 → left = 20, right = 30


20 → left = 40, right = 50
30 → left = NULL, right = NULL

Advantages:

 Efficient and clean for binary trees.


 Easy to implement recursion (traversals, search, etc.).

Disadvantages:

 Not suitable for general trees with more than 2 children.

Comparison of Tree Representations


Feature Parent Array Child-Sibling (LL) Binary Tree Struct
Suitable for Any tree General trees Binary trees
Space Efficiency High Moderate Moderate
Access Parent O(1) O(n) O(n)
Access Children O(n) O(k) where k = num children O(1) (if binary)
Easy to Implement Yes Medium Yes (for binary only)

Bonus: Array Representation of Binary Trees (Complete


Binary Trees)
Especially used in Heaps or Complete Binary Trees

For a binary tree stored in an array arr[]:

 arr[0] is root
 For node at index i:
o Left child = 2*i + 1
o Right child = 2*i + 2
o Parent = Floor((i - 1) / 2)
Example Tree:
10
/ \
20 30
/ \
40 50

Stored as: [10, 20, 30, 40, 50]

Summary
Method Best For Key Pointers
Parent Array Simple tree with fast parent access Only parent info stored
Linked List General trees Child & sibling pointers
Binary Struct Binary Trees Left & right pointers
Array (Heap) Complete Binary Trees Calculated indices

Binary Tree: Definition and Types


What is a Binary Tree?
A Binary Tree is a hierarchical, non-linear data structure in which:

 Each node has at most two children.


 These children are referred to as:
o Left Child
o Right Child

Formal Definition:

A binary tree is a finite set of nodes that is either:

 Empty (null tree), or


 Consists of a root node and two disjoint binary trees called the left and right
subtrees.
Why Binary Trees?
 Simplifies searching, insertion, and deletion operations.
 Forms the foundation of:
o Binary Search Trees (BST)
o Heaps
o Expression Trees
o AVL Trees, etc.

Structure of a Binary Tree Node


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

Each node holds:

 data: the actual value.


 left: pointer to the left child.
 right: pointer to the right child.

Types of Binary Trees


There are multiple special types of binary trees based on structure or node arrangement.

Full Binary Tree (Proper/Strict Binary Tree)

A binary tree where every node has either 0 or 2 children.

Each internal node has exactly two children.


No node should have only one child.

Example:

1
/ \
2 3
/ \ / \
4 5 6 7
Perfect Binary Tree

A full binary tree in which all leaf nodes are at the same level.

All internal nodes have two children.


All leaves are at the same depth.

Example:

1
/ \
2 3
/ \ / \
4 5 6 7

Complete Binary Tree

All levels are completely filled except possibly the last, and the last level is filled from left to
right.

Perfect up to last level


Last level filled left to right

Example:

1
/ \
2 3
/ \ /
4 5 6

 Commonly used in heaps.

Skewed Binary Tree

A binary tree that degenerates to a linked list due to having only one child at each level.

Types:

 Left Skewed Tree: Every node has only a left child.


 Right Skewed Tree: Every node has only a right child.

Left Skewed:

1
/
2
/
3

Right Skewed:

1
\
2
\
3

Balanced Binary Tree

A binary tree is balanced if the difference in height between the left and right subtree of every
node is not more than 1.

All subtrees have balanced height


Ensures log(n) height

Examples: AVL Tree, Red-Black Tree

Degenerate (or Pathological) Tree

A tree in which each parent has only one child.

 Essentially turns into a linked list.


 Worst-case for operations in binary trees.

Summary Table of Binary Tree Types


Type Condition
Full 0 or 2 children
Perfect Full + all leaves at same level
Complete All levels full except last (filled left to right)
Skewed All nodes have only one child (either left or right)
Balanced Height difference ≤ 1 for each node
Type Condition
Degenerate Each parent has only one child

Applications of Binary Trees


Application Area Binary Tree Role
Searching Binary Search Tree
Memory mgmt Buddy memory allocation (Binary Tree)
File storage Huffman Encoding Tree
Computing Expression Tree (Math expressions)
Heap Structures Min/Max Heaps

Summary
 Binary Tree: At most 2 children per node.
 Special Types:
o Full: 0 or 2 children
o Perfect: Full + equal leaf depth
o Complete: All levels full except last
o Skewed: Like a linked list
o Balanced: Height difference ≤ 1
 Used in searching, sorting, compression, decision-making, and more.
Properties of Binary Trees
Understanding the mathematical and structural properties of binary trees is essential to estimate
memory usage, traversal time, number of nodes, and height-related constraints.
Binary Tree Traversals

What is Tree Traversal?

Traversal means visiting every node in a specific order to process (read/print) the values stored in
the binary tree.

Since trees are non-linear, we need a well-defined strategy to systematically visit all nodes.

Types of Binary Tree Traversals


Binary tree traversal is generally divided into two categories:

1. Depth-First Traversal (DFS)

 Inorder (LNR)
 Preorder (NLR)
 Postorder (LRN)

2. Breadth-First Traversal (BFS)

 Level Order (uses a queue)

Let’s now focus on the Depth-First Traversals in detail.

1. Inorder Traversal (LNR)


Order:

 Left subtree
 Node (Root)
 Right subtree

Example:

Given:

A
/ \
B C
/ \
D E
Inorder: D B E A C

2. Preorder Traversal (NLR)


Order:

 Node (Root)
 Left subtree
 Right subtree

Example:

Same tree:

A
/ \
B C
/ \
D E

Preorder: A B D E C

3. Postorder Traversal (LRN)


Order:

 Left subtree
 Right subtree
 Node (Root)

Example:

Same tree:

A
/ \
B C
/ \
D E

Postorder: D E B C A
Real-World Analogies
 Preorder: You enter a building (root), go left room to right room.
 Inorder: You explore left wing, then center, then right wing.
 Postorder: You clean up each room, then leave the building last.

Binary Search Tree (BST):


1. Definition of BST
A Binary Search Tree (BST) is a binary tree in which each node contains a key (or value) and
satisfies the following properties:

 The left subtree of a node contains only nodes with keys less than the node's key.
 The right subtree of a node contains only nodes with keys greater than the node's key.
 Both the left and right subtrees must also be binary search trees.
 No duplicate nodes are typically allowed.

Example: Inserting the values: 50, 30, 70, 20, 40, 60, 80 results in:

50
/ \
30 70
/ \ / \
20 40 60 80
2. Binary Search Tree ADT (Abstract Data Type)
ADT Definition:

An Abstract Data Type for a BST can be defined by its operations:

Data:

 A node containing key, left pointer, right pointer

Operations:

 insert(tree, key)
 delete(tree, key)
 search(tree, key)
 inorder(tree)
 preorder(tree)
 postorder(tree)

These operations hide the implementation details and expose functionality only.

3. Applications of BST
 Dictionary implementations
 Symbol tables in compilers
 Dynamic sorting and ranking systems
 Auto-complete and suggestion systems
 Memory management (free list organization)

Full Iterative Implementation of BST in C


#include <stdio.h>
#include <stdlib.h>
#define MAX 100

typedef struct Node {


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

// Stack for traversal


typedef struct Stack {
Node* items[MAX];
int top;
} Stack;

void initStack(Stack* s) { s->top = -1; }


int isEmpty(Stack* s) { return s->top == -1; }
void push(Stack* s, Node* node) { s->items[++(s->top)] = node; }
Node* pop(Stack* s) { return s->items[(s->top)--]; }
Node* peek(Stack* s) { return s->items[s->top]; }

// Create new node


Node* createNode(int key) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = key;
temp->left = temp->right = NULL;
return temp;
}

// Iterative Insertion
Node* insert(Node* root, int key) {
Node* newNode = createNode(key);
if (root == NULL) return newNode;
Node* curr = root;
Node* parent = NULL;

while (curr != NULL) {


parent = curr;
if (key < curr->data)
curr = curr->left;
else
curr = curr->right;
}
if (key < parent->data)
parent->left = newNode;
else
parent->right = newNode;

return root;
}

// Iterative Search
Node* search(Node* root, int key) {
while (root != NULL) {
if (key == root->data) return root;
else if (key < root->data) root = root->left;
else root = root->right;
}
return NULL;
}

// Inorder Traversal (LNR)


void inorder(Node* root) {
Stack s; initStack(&s);
Node* curr = root;

while (curr != NULL || !isEmpty(&s)) {


while (curr != NULL) {
push(&s, curr);
curr = curr->left;
}
curr = pop(&s);
printf("%d ", curr->data);
curr = curr->right;
}
}

// Preorder Traversal (NLR)


void preorder(Node* root) {
if (root == NULL) return;
Stack s; initStack(&s);
push(&s, root);

while (!isEmpty(&s)) {
Node* curr = pop(&s);
printf("%d ", curr->data);
if (curr->right) push(&s, curr->right);
if (curr->left) push(&s, curr->left);
}
}

// Postorder Traversal (LRN)


void postorder(Node* root) {
if (root == NULL) return;
Stack s1, s2; initStack(&s1); initStack(&s2);
push(&s1, root);

while (!isEmpty(&s1)) {
Node* curr = pop(&s1);
push(&s2, curr);
if (curr->left) push(&s1, curr->left);
if (curr->right) push(&s1, curr->right);
}
while (!isEmpty(&s2)) {
printf("%d ", pop(&s2)->data);
}
}

// Delete Node (Iterative)


Node* deleteNode(Node* root, int key) {
Node *parent = NULL, *curr = root;
while (curr != NULL && curr->data != key) {
parent = curr;
if (key < curr->data)
curr = curr->left;
else
curr = curr->right;
}

if (curr == NULL) return root;

// Case 1: One or no child


if (curr->left == NULL || curr->right == NULL) {
Node* newCurr = curr->left ? curr->left : curr->right;
if (parent == NULL) return newCurr; // root node case
if (curr == parent->left)
parent->left = newCurr;
else
parent->right = newCurr;
free(curr);
}
// Case 2: Two children
else {
Node* succParent = curr;
Node* succ = curr->right;
while (succ->left != NULL) {
succParent = succ;
succ = succ->left;
}
curr->data = succ->data;
if (succParent->left == succ)
succParent->left = succ->right;
else
succParent->right = succ->right;
free(succ);
}
return root;
}

// Menu-driven main function


int main() {
Node* root = NULL;
int choice, key;
do {
printf("\n1. Insert\n2. Search\n3. Delete\n4. Inorder\n5. Preorder\n6.
Postorder\n7. Exit\nEnter choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("Enter key to insert: ");
scanf("%d", &key);
root = insert(root, key);
break;
case 2:
printf("Enter key to search: ");
scanf("%d", &key);
if (search(root, key))
printf("Key found!\n");
else
printf("Key not found.\n");
break;
case 3:
printf("Enter key to delete: ");
scanf("%d", &key);
root = deleteNode(root, key);
break;
case 4:
printf("Inorder Traversal: ");
inorder(root);
break;
case 5:
printf("Preorder Traversal: ");
preorder(root);
break;
case 6:
printf("Postorder Traversal: ");
postorder(root);
break;
}
} while (choice != 7);
return 0;
}

Is Stack Needed for Iterative BST?


Yes, stack is necessary in iterative traversal to mimic the recursive call stack. Traversal like
inorder or postorder cannot function correctly without remembering the path (backtracking) —
this is managed by stack.

However, insertion, search, and deletion do not need stack — they use simple while loops and
pointers.

Full Recursive Implementation of BST in C


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

typedef struct Node {


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

// Create new node


Node* createNode(int key) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = key;
newNode->left = newNode->right = NULL;
return newNode;
}

// Recursive Insertion
Node* insert(Node* root, int key) {
if (root == NULL)
return createNode(key);
if (key < root->data)
root->left = insert(root->left, key);
else if (key > root->data)
root->right = insert(root->right, key);
return root;
}

// Recursive Search
Node* search(Node* root, int key) {
if (root == NULL || root->data == key)
return root;
if (key < root->data)
return search(root->left, key);
else
return search(root->right, key);
}

// Inorder Traversal (LNR)


void inorder(Node* root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
}

// Preorder Traversal (NLR)


void preorder(Node* root) {
if (root != NULL) {
printf("%d ", root->data);
preorder(root->left);
preorder(root->right);
}
}

// Postorder Traversal (LRN)


void postorder(Node* root) {
if (root != NULL) {
postorder(root->left);
postorder(root->right);
printf("%d ", root->data);

}
}

// Find minimum value node


Node* findMin(Node* root) {
while (root && root->left != NULL)
root = root->left;
return root;
}

// Recursive Delete
Node* deleteNode(Node* root, int key) {
if (root == NULL)
return root;
if (key < root->data)
root->left = deleteNode(root->left, key);
else if (key > root->data)
root->right = deleteNode(root->right, key);
else {
// Node with one or no child
if (root->left == NULL) {
Node* temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL) {
Node* temp = root->left;
free(root);
return temp;
}
// Node with two children
Node* temp = findMin(root->right);
root->data = temp->data;
root->right = deleteNode(root->right, temp->data);
}
return root;
}

int main() {
Node* root = NULL;
int choice, key;
do {
printf("\n1. Insert\n2. Search\n3. Delete\n4. Inorder\n5. Preorder\n6.
Postorder\n7. Exit\nEnter choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("Enter key to insert: ");
scanf("%d", &key);
root = insert(root, key);
break;
case 2:
printf("Enter key to search: ");
scanf("%d", &key);
if (search(root, key))
printf("Key found!\n");
else
printf("Key not found.\n");
break;
case 3:
printf("Enter key to delete: ");
scanf("%d", &key);
root = deleteNode(root, key);
break;
case 4:
printf("Inorder Traversal: ");
inorder(root);
break;
case 5:
printf("Preorder Traversal: ");
preorder(root);
break;
case 6:
printf("Postorder Traversal: ");
postorder(root);
break;
}
} while (choice != 7);
return 0;
}

Comparison
 Recursive code is easier to understand and shorter.
 Iterative is better in cases of deep trees to avoid stack overflow.
GRAPHS
1. Definition of Graph
A graph is a non-linear data structure consisting of a set of vertices (nodes) and a set of edges
(connections) that link pairs of vertices. It provides a powerful abstract model for relationships
between discrete entities.

Mathematical Representation: G= (V, E)

 V is a set of vertices or nodes. These are the fundamental entities or points within the graph.
 E is a set of edges, where each edge is a pair of vertices (u, v). An edge signifies a
relationship or connection between vertex u and vertex v.

2. Basic Concepts
a) Vertex (Node)

 Theoretical Explanation: A vertex, also known as a node, is the fundamental and


indivisible unit of a graph. It represents an entity or a point in the system being modeled.
 Representation: Vertices are typically represented as labels (e.g., A, B, C) or numbers
(e.g., 0, 1, 2) for identification and ease of manipulation in algorithms.

b) Edge

 Theoretical Explanation: An edge is a link or connection between two vertices. It


signifies a relationship or interaction between the entities represented by the connected
vertices.
 Types: Edges can be categorized based on their directionality:
o Directed (one-way): The connection flows from one vertex to another in a specific
direction (e.g., a one-way street, a "follows" relationship on social media).
o Undirected (two-way): The connection is mutual, existing in both directions
between the two vertices (e.g., a friendship on social media, a two-way road).

c) Adjacency

 Theoretical Explanation: Two vertices are considered adjacent if they are directly
connected by an edge. If an edge (u,v) exists in the graph, then u is adjacent to v, and if the
graph is undirected, v is also adjacent to u. Adjacency is a core concept for navigating and
exploring graphs.

d) Degree

 Theoretical Explanation: The degree of a vertex quantifies the number of edges


connected to it, reflecting its connectivity within the graph.
o Undirected Graph: The degree of a vertex is simply the total number of edges
connected to it. For example, if vertex 'A' is connected to 'B' and 'C', its degree is
2.
o Directed Graph: For directed graphs, we differentiate between incoming and
outgoing connections:
 In-degree: The number of edges that point towards the vertex.
 Out-degree: The number of edges that point away from the vertex.

e) Path

 Theoretical Explanation: A path in a graph is a sequence of distinct vertices such that


each consecutive pair of vertices in the sequence is connected by an edge. It represents a
traversable route through the graph.
 Example: In a graph with vertices A, B, C, D, a path could be A to B to C to D.

f) Cycle

 Theoretical Explanation: A cycle is a special type of path that starts and ends at the same
vertex, forming a closed loop. Crucially, a cycle must not repeat any intermediate vertices
or edges.
 Example: A to B to C to A forms a cycle.

g) Connected Graph

 Theoretical Explanation: A graph is deemed connected if there exists at least one path
between every possible pair of distinct vertices in the graph. In essence, it means you can
reach any vertex from any other vertex by traversing edges. If even one pair of vertices has
no path between them, the graph is disconnected.
3. Properties of Graphs
Property Description
**Order ($ V
**Size ($ E
A simple graph is a graph that contains no loops (an edge connecting a vertex to
Simple itself) and no multiple edges (more than one edge connecting the same pair of
Graph vertices). Most theoretical graph problems assume simple graphs unless otherwise
specified.
A multigraph is a graph that allows for multiple edges between the same pair of
Multigraph
vertices. For example, two cities might be connected by multiple roads.
An edge that connects a vertex to itself. This signifies a self-relationship or self-
Loop
transition for the entity represented by the vertex.
A weighted graph is a graph where each edge has an associated numeric value,
Weighted known as a "weight" or "cost." These weights can represent various metrics such
Graph as distance, time, cost, capacity, or strength of connection. For example, on a road
map, the weight could be the distance between two cities.
An unweighted graph is one where edges do not carry any specific numeric value
Unweighted or cost. In such graphs, the existence of an edge simply indicates a connection,
Graph and all connections are typically treated as having equal "cost" or significance
(often implicitly 1).

4. Types of Graphs
a) Undirected Graph

 Theoretical Explanation: In an undirected graph, the edges have no inherent direction. If


an edge exists between vertex u and vertex v, it implies a bidirectional connection; that is,
the relationship from u to v is the same as the relationship from v to u.
Example:

A ----- B
\ /
\ /
C

o Description: This diagram shows three vertices, A, B, and C. The lines between
them indicate undirected edges.
o Edges: (A-B), (A-C), (B-C). Since it's undirected, (A-B) is equivalent to (B-A).
o Real-world analogy: Friendships on Facebook are typically undirected: if Alice is
friends with Bob, Bob is automatically friends with Alice.

b) Directed Graph (Digraph)

 Theoretical Explanation: In a directed graph, each edge has a specific direction, indicated
by an arrow. An edge (u,v) means there is a connection from vertex u to vertex v, but not
necessarily vice-versa.

Visual Diagram Example:

A ----> B
^ |
| v
C <---- D
o Description: This diagram shows four vertices, A, B, C, D, with arrows on the lines
indicating the direction of the connections.
o Edges: (A to B), (B to C), (D to C), (D to A).
o Real-world analogy: Following someone on Instagram is a directed relationship:
Alice can follow Bob without Bob necessarily following Alice back. Task
dependencies in project management are also directed (Task A must complete
before Task B starts).

c) Weighted Graph

 Theoretical Explanation: A weighted graph assigns a numeric value (weight) to each


edge. This weight can represent various attributes like cost, distance, time, capacity, or
strength of connection, providing more detailed information about the relationship.
Visual Diagram Example:

A
/ \
2/ \3
/ \
B-------C
1
o Description: This diagram shows three vertices A, B, C, with numbers on the
edges. These numbers represent the 'weights' of the edges.
o Edges and Weights:
 Edge (A-B) has a weight of 2.
 Edge (A-C) has a weight of 3.
 Edge (B-C) has a weight of 1.
o Real-world analogy: In a navigation system, roads (edges) have weights
representing their length in kilometers or the estimated travel time in minutes.

d) Unweighted Graph

 Theoretical Explanation: In an unweighted graph, edges do not have any associated


numeric values. The presence of an edge simply indicates a connection, and all connections
are treated equally. These are typically used when only the existence of a link matters, not
its intensity or cost.

Visual Diagram Example:

1 --- 2
| |
4 --- 3
o Description: This is a simple square graph with four vertices (1, 2, 3, 4) and four
edges. There are no numbers on the edges.
o Edges: (1-2), (2-3), (3-4), (4-1).
o Real-world analogy: A simple network showing who is friends with whom, where
the 'strength' of friendship isn't being quantified.
e) Cyclic Graph

 Theoretical Explanation: A graph is classified as cyclic if it contains at least one cycle.


Cycles are fundamental structures in many real-world networks, representing feedback
loops, circular dependencies, or return paths.

Visual Diagram Example:

A ----- B
| |
| |
D ----- C
o Description: This graph has four vertices A, B, C, D, forming a square.
o Cycle: A to B to C to D to A is a cycle. Any starting point allows traversing back
to it without repeating edges.
o Real-world analogy: A road network where you can drive in a loop and return to
your starting point.

f) Acyclic Graph

 Theoretical Explanation: An acyclic graph is a graph that contains no cycles whatsoever.


These graphs are particularly important in various applications where dependencies must
be ordered, and circular references are undesirable.
o Directed Acyclic Graph (DAG): A common type where all edges are directed,
and no directed cycles exist. DAGs are widely used in task scheduling, compilation
processes, and representing hierarchical structures.

Visual Diagram Example (DAG):

Start --> Task A --> Task C


| |
v v
Task B --> Task D
o Description: This diagram illustrates a set of tasks with dependencies. Arrows
indicate that one task must complete before another can start.
o No Cycles: You cannot start at any task and return to it by following the arrows.
For example, you can't go from Task A back to Task A.
o Real-world analogy: A project plan where tasks have prerequisites. You can't start
building the walls before the foundation is laid.

g) Connected Graph

 Theoretical Explanation: A graph is connected if every vertex is reachable from every


other vertex. This means for any two vertices u and v in the graph, there exists at least one
path connecting u to v.

Visual Diagram Example:

1 --- 2 --- 3
|
4
o Description: All four vertices (1, 2, 3, 4) are interconnected such that you can travel
from any vertex to any other vertex. For example, to go from 1 to 4, you can follow
1 to 2 to 4.

h) Disconnected Graph

 Theoretical Explanation: A graph is disconnected if it contains at least one pair of vertices


for which no path exists. This implies that the graph is composed of two or more separate
"components" or subgraphs that are not connected to each other.

Visual Diagram Example:

A --- B X --- Y
| /
C -------- Z
o Description: This diagram shows two distinct groups of vertices. The group {A,
B, C} is connected, and the group {X, Y, Z} is connected, but there are no edges
connecting any vertex from {A, B, C} to any vertex from {X, Y, Z}.
o Disconnected: For example, there's no path from vertex A to vertex X.

i) Complete Graph (K_n)

 Theoretical Explanation: A complete graph, denoted as K_n, is an undirected graph in


which every distinct pair of vertices is connected by a unique edge. In a complete graph
with n vertices, each vertex has a degree of (n−1), and the total number of edges is given
by the formula n(n−1)2.
Visual Diagram Example (K_4):

A-----B
|\ /|
| \ / |
| X |
| / \ |
|/ \|
D-----C
o Description: This diagram shows a K_4 graph, meaning it has 4 vertices (A, B, C,
D). Every single vertex is connected directly to every other vertex.
o Edges: (A-B), (A-C), (A-D), (B-C), (B-D), (C-D). There are 4(4−1)/2=6 edges.
o Real-world analogy: A small meeting where everyone shakes hands with everyone
else.

j) Sparse vs. Dense Graph

 Theoretical Explanation: This classification relates to the number of edges relative to the
maximum possible number of edges.
o Sparse Graph: A graph with relatively few edges. The number of edges (∣E∣) is
approximately proportional to the number of vertices (∣V∣), i.e., ∣E∣approx∣V∣.
o Dense Graph: A graph with many edges. The number of edges (∣E∣) is closer to
the maximum possible number of edges, which is approximately ∣V∣2 (for a
complete graph).
 Visual Example (Conceptual):
o Sparse: Imagine a few scattered dots with only a handful of lines connecting them.
o Dense: Imagine many dots, and almost every dot has a line connecting it to every
other dot, making the diagram look very "full."
 Implication: The density of a graph often influences the choice of the most efficient
storage structure and algorithms.

k) Bipartite Graph

 Theoretical Explanation: A bipartite graph is one whose vertices can be divided into two
disjoint and independent sets, say U and W, such that every edge connects a vertex in U to
one in W. Crucially, there are no edges within U or within W.
Visual Diagram Example:

Set U: 1 --- 4 : Set W


| |
2 --- 5
|
3
o Description: Vertices {1, 2, 3} form set U, and vertices {4, 5} form set W. All
edges connect a vertex from U to a vertex from W. No vertex in U is connected to
another vertex in U, and no vertex in W is connected to another in W.
o Real-world analogy: A graph representing job applications: one set of vertices is
applicants, the other is job openings, and edges represent applications. An applicant
doesn't apply to another applicant.

l) Tree

 Theoretical Explanation: A tree is a special type of undirected graph that is both acyclic
(contains no cycles) and connected. For a graph with n vertices to be a tree, it must also
have exactly n−1 edges. Trees are fundamental hierarchical data structures.

Visual Diagram Example:

A
/ \
B C
/ \
D E
o Description: This is a classic tree structure. It's connected (you can reach any node
from any other) and has no cycles. It has 5 vertices and 4 edges (5-1).

Real-Life Examples of Graphs


Social Network

 Vertices: Individual users (e.g., Alice, Bob, Charlie).


 Edges: Represent relationships between users.
o Undirected Example (Facebook Friends): If Alice and Bob are "friends" on
Facebook, the connection is mutual. This implies an undirected edge between
Alice's vertex and Bob's vertex. The isFriendOf relationship is symmetrical.
o Directed Example (Instagram Followers): If Alice "follows" Bob on Instagram,
there's a directed edge from Alice's vertex to Bob's vertex. Bob does not necessarily
follow Alice back, making it a one-way relationship.
 Applications: Identifying communities, finding degrees of separation, personalized
content recommendations.

Maps & Navigation (Google Maps, GPS)

 Vertices: Specific geographic locations like cities, intersections, landmarks, or even


individual road segments.
 Edges: Represent the physical connections between these locations, such as roads,
highways, or pathways.
 Weighted Graph: This is a prime example of a weighted graph.
o The "weight" on an edge could represent:
 Distance: The physical length of the road segment (e.g., 5 km).
 Time: The estimated travel time along that segment, considering traffic
(e.g., 10 minutes).
 Cost: Toll fees or fuel consumption for that segment.
 Applications: Shortest path finding (e.g., finding the quickest route), optimizing delivery
routes, traffic flow analysis.

Recommendation Systems

 Vertices: Can be categorized into different types:


o Users (e.g., customer IDs)
o Products (e.g., item IDs)
o Movies, books, music, articles, etc.
 Edges: Represent interactions or relationships:
o Purchase relationships: An edge from a user to a product they bought.
o Rating relationships: A weighted edge from a user to a movie, with the weight
being the rating given (e.g., 4 stars).
o View history: An edge from a user to a product they viewed.
 Applications: "Customers who bought this also bought...", "Recommended for you,"
identifying similar users or products based on shared connections.

Neural Networks (AI/ML)

 Vertices: Represent artificial "neurons" or nodes in the network. These neurons process
information.
 Edges: Represent the connections or "synapses" between neurons.
 Weighted Graph: These are highly sophisticated weighted directed graphs.
o Weights: Each edge has a numerical weight that determines the strength and
influence of the connection. These weights are adjusted during the learning process
to optimize the network's output.
o Direction: Information flows in a specific direction (e.g., from input layer to hidden
layers to output layer).
 Applications: Image recognition, natural language processing, predictive modeling,
machine translation.

Biological Networks

 Vertices: Can represent various biological entities:


o Genes
o Proteins
o Metabolites
o Cells
 Edges: Represent interactions or regulatory relationships:
o Protein-Protein Interaction Networks: An edge exists if two proteins physically
interact.
o Gene Regulatory Networks: A directed edge from gene A to gene B means gene
A regulates the expression of gene B.
o Metabolic Pathways: Edges represent biochemical reactions transforming one
metabolite into another.
 Applications: Understanding disease mechanisms, drug discovery, designing new
biological systems.

Project Scheduling (DAG - Directed Acyclic Graph)

 Vertices: Represent individual tasks or activities within a project.


 Directed Edges: Represent dependencies between tasks. A directed edge from Task A to
Task B means Task A must be completed before Task B can begin.
 Acyclic Nature: The "acyclic" property is crucial here. If there were a cycle (e.g., Task A
depends on B, B depends on C, and C depends on A), the project could never start or finish
due to a circular dependency.
 Applications: Critical path analysis (finding the longest path of dependent tasks, which
determines the minimum project duration), resource allocation, project management
software.

GRAPH STORAGE STRUCTURES


A graph can be stored in computer memory using mainly two fundamental structures: Adjacency
Matrix and Adjacency List. The choice between these depends largely on the graph's
characteristics (e.g., density) and the operations most frequently performed.

1. Adjacency Matrix

 Definition: An adjacency matrix is a square 2D array of size V*V, where V is the


number of vertices in the graph. Each cell (i,j) in the matrix represents the potential
connection between vertex i and vertex j.
o The value at position (i,j) indicates the presence or absence of an edge from vertex
i to vertex j.
o For unweighted graphs, a value of 1 (or true) typically denotes an existing edge,
and 0 (or false) denotes no edge.
o For weighted graphs, the cell stores the weight of the edge if it exists, and 0 (or
infty, or a special null value) if no edge exists.

Structure for Unweighted Undirected Graph Example: Suppose we have a simple


undirected graph:

A --- B
\ /
\ /
C

o Vertices: A, B, C. Let's assign numerical indices: A=0, B=1, C=2.


o Edges: (A-B), (B-C), (A-C). Since it's undirected, an edge (u,v) implies (v,u).

Adjacency Matrix Representation:

A (0) B (1) C (2)


A (0) 0 1 1
B (1) 1 0 1
C (2) 1 1 0

o Explanation:
 Matrix[0][1] = 1 because there's an edge from A to B.
 Matrix[1][0] = 1 because it's undirected, so there's also
an edge from B
to A. This results in a symmetric matrix (the matrix is identical to its
transpose).
 Diagonal elements (Matrix[i][i]) are typically 0 for simple graphs,
indicating no loops (no edge from a vertex to itself).

Structure for Directed Graph (Digraph) Example: Suppose we have a directed graph with
edges: A to B, B to C.

 A ----> B ----> C
o Vertices: A, B, C. Indices: A=0, B=1, C=2.
Adjacency Matrix Representation:

A (0) B (1) C (2)


A (0) 0 1 0
B (1) 0 0 1
C (2) 0 0 0

o Explanation:
 Matrix[0][1] = 1 because there's a directed edge from A to B.
 Matrix[1][0] = 0 because there is NO directed edge from B to A.
 This matrix is generally not symmetric.

Structure for Weighted Graph Example: Consider an undirected weighted graph:

A
/ \
2/ \3
/ \
B-------C
1

o Vertices: A, B, C. Indices: A=0, B=1, C=2.

Adjacency Matrix Representation:

A (0) B (1) C (2)


A (0) 0 2 3
B (1) 2 0 1
C (2) 3 1 0

o Explanation:
 Instead of 1, the actual weight is stored. Matrix[0][1] = 2 means the edge
(A-B) has a weight of 2.
 For non-existent edges, 0 is used here, but often infty or a very large number
is used in pathfinding algorithms to signify an unreachable path.
Example 2 :

Advantages of Adjacency Matrix

Feature Benefit
Checking if an edge exists between two specific vertices (i,j) takes O(1) constant
Fast Edge
time. You simply access Matrix[i][j]. This is extremely efficient for queries
Lookup
about direct connections.
Simple It's a straightforward 2D array, making it very easy to understand, implement,
Structure and debug. Array indexing is intuitive.
Feature Benefit
When a graph has many edges (dense), an adjacency matrix is efficient because
Best for Dense
most cells will contain a '1' or a weight. The space cost of O(V 2) is justified as a
Graphs
significant portion of the matrix is utilized.
Certain graph algorithms (e.g., detecting cycles using matrix multiplication,
Matrix
finding transitive closure) can be efficiently implemented using standard matrix
Operations
operations, leveraging linear algebra concepts.

Disadvantages of Adjacency Matrix

Limitation Description
It always requires O(V2) space, regardless of the number of edges. For sparse
High Space graphs (where EllV2), this leads to a lot of wasted memory, as most of the matrix
Usage cells will be 0 (or false). For example, a graph with 1000 vertices needs
10002=1,000,000 cells, even if it has very few edges.
To find all neighbors of a specific vertex u, you need to iterate through its entire
Slow to
row (or column) in the matrix. This takes O(V) time, even if the vertex has only
Iterate
one neighbor. For example, to find all friends of Alice, you'd scan her entire row
Neighbors
in the matrix.
Typically, adjacency matrices are implemented as static arrays, meaning their size
(number of vertices) is fixed at compile time or upon initial allocation. Adding or
Fixed Size
removing vertices dynamically can be computationally expensive as it often
requires resizing and copying the entire matrix.

2. Adjacency List

 Definition: An adjacency list represents a graph as an array (or list) of linked lists (or
dynamic arrays/vectors). Each index in the main array corresponds to a vertex, and the
linked list at that index contains all the vertices directly adjacent to it.
o For each vertex u in V, an adjacency list Adj[u] stores a list of all vertices v such
that there is an edge (u,v) in the graph.
Structure for Unweighted Undirected Graph Example:

Structure for Directed Graph Example:


Structure for Weighted Graph Example:

Advantages of Adjacency List

Feature Benefit
This is its primary advantage. It only stores existing edges. The total
Efficient for Sparse space required is O(V+E) (V for the array of lists, and E for all the
Graphs elements in the lists). For sparse graphs (EllV2), this is significantly more
memory-efficient than an adjacency matrix.
To find all neighbors of a vertex u, you just iterate through its
Faster Neighbor corresponding linked list. This takes O(textdegreeofu) time, which is
Iteration much faster than O(V) for a sparse graph with a vertex of low degree.
This is crucial for traversal algorithms like BFS and DFS.
Avoids the wasted space inherent in adjacency matrices for sparse
Memory Efficient graphs, leading to better cache performance and overall memory
utilization.
It's easier to add or remove vertices and edges dynamically, as linked lists
Dynamic Size or dynamic arrays can easily grow or shrink without requiring full
reallocations of a large matrix.
Adding an edge is O(1) (just append to the list). Removing an edge is
Good for Edge
O(textdegreeofvertex) to find and remove the specific item from the list,
Addition/Removal
which is efficient for sparse graphs.
Disadvantages of Adjacency List

Limitation Description
Checking if an edge exists between two arbitrary vertices (u,v) requires
traversing the adjacency list of u to see if v is present. In the worst case,
Slower Edge Lookup
this could take O(textdegreeofu) time. For an undirected graph, it's
O(textmin(degreeofu,textdegreeofv)).
Some algorithms are naturally expressed and efficiently implemented
Harder to Implement
using matrix operations (e.g., matrix multiplication for path counting).
for Matrix-Friendly
Converting an adjacency list to a matrix, or adapting such algorithms,
Algorithms
can be more complex or less efficient.
Slightly More Managing linked lists or dynamic arrays (vectors) can be slightly more
Complex complex than simple 2D array indexing, especially in languages without
Implementation built-in dynamic list structures.

3. Comparison Table

Feature Adjacency Matrix Adjacency List


O(V2) - Fixed regardless of O(V+E) - Proportional to the number
Storage (Space
edge count. Potentially very of vertices and edges. Highly efficient
Complexity)
wasteful for sparse graphs. for sparse graphs.
Dense Graphs (where E is Sparse Graphs (where EllV2) -
close to V2) - Most cells are Minimal space wasted, and operations
Best For
utilized, so space is not wasted, like neighbor iteration are faster due to
and fast lookups are beneficial. fewer elements in lists.
O(textdegreeofu) - Requires scanning
O(1) - Direct array access
Edge Check Time (Is the list of neighbors for vertex u. In
matrix[u][v]. This is the
(u,v) an edge?) worst case (complete graph), this could
fastest for this operation.
be O(V).
O(1) - Add v to u's list (and u to v's list
O(1) - Set matrix[u][v] = 1
for undirected). Assumes constant time
Adding an Edge (and matrix[v][u] = 1 for
list insertion (e.g., at head) or average
undirected).
case for dynamic arrays.
O(textdegreeofu) - Need to find and
O(1) - Set matrix[u][v] = 0
remove v from u's list (and u from v's
Removing an Edge (and matrix[v][u] = 0 for
list for undirected). Requires
undirected).
traversing the list.
Feature Adjacency Matrix Adjacency List
Poor for large sparse graphs as
Highly efficient for large sparse graphs
Space Efficiency vast majority of matrix is
as only actual connections are stored.
zeros/false.
O(textdegreeofu) - Iterate only through
Neighbor Iteration O(V) - Must iterate through the the elements in u's adjacency list. This
(Find all neighbors of entire row/column u in the is very efficient for graph traversals
u) matrix. and algorithms that explore local
connections.
Easier, as underlying data structures
2
Dynamic Changes Harder and potentially O(V ) (linked lists, vectors) can handle
(Adding/Removing to resize and copy matrix. dynamic growth. O(V) to add a new
Vertices) Typically fixed-size. empty list; O(E) to update all lists for
vertex removal.
Slightly more complex due to
managing lists or vectors, especially
Ease of Simpler for basic graph
when considering edge weights or
Implementation operations using 2D arrays.
other attributes within the list
elements.

Conclusion: Choosing the Right Representation

The optimal choice between an Adjacency Matrix and an Adjacency List largely depends on the
specific characteristics of the graph and the most common operations to be performed on it.

 Use Adjacency Matrix when:


o The graph is dense (number of edges is close to V 2).
o You need frequent and very fast checks for the existence of an edge between any
two given vertices.
o The number of vertices is relatively small, so V 2 space is not prohibitive.
o You are performing algorithms that benefit from matrix operations.
 Use Adjacency List when:
o The graph is sparse (number of edges is much less than V2). This is the most
common scenario for real-world graphs.
o You need to frequently find all neighbors of a given vertex (e.g., for graph
traversals like BFS or DFS).
o Memory efficiency is a critical concern, especially for graphs with a large number
of vertices.
o The graph structure is expected to change dynamically (vertices or edges are
frequently added/removed).
Operations on Graphs
Graphs are powerful mathematical structures that model relationships between discrete entities. In
the context of computer science, they are non-linear data structures crucial for representing
interconnected systems. Just like any other data structure, graphs support a set of fundamental
operations that allow us to build, modify, and query their structure.

Let's explore these core operations theoretically, detailing how they are performed and illustrating
them with real-world examples from a hypothetical social network called "FriendConnect".

Assumptions for "FriendConnect" Example:


 Vertices (Nodes): Represent users in the social network (e.g., Alice, Bob, Charlie).
 Edges (Links): Represent friendships between users. For simplicity, we'll assume
friendships are undirected (if Alice is friends with Bob, Bob is also friends with Alice).
We'll consider them unweighted for these basic operations (the strength of friendship isn't
a factor).

1. Insert Vertex (Add Node)


 Theoretical Explanation:

Inserting a vertex means introducing a new independent entity into the graph.
Conceptually, you're adding a new point or "dot" to your diagram. Initially, this new vertex
is isolated; it has no connections (edges) to any existing vertices. Its degree (number of
connections) is zero.

o In an Adjacency List: You would typically find an available slot (an unused index
in an array, or simply append a new entry if using dynamic lists/maps) and create
an empty list for this new vertex, signifying it has no neighbors.
o In an Adjacency Matrix: You would add a new row and a new column, and all
entries in this new row/column would be initialized to 0 (or false), indicating no
connections.
 Time Complexity (General):
o Adjacency List: O(1) (assuming an available ID is quickly found or new space is
simply appended).
o Adjacency Matrix: O(V2) if you need to resize the matrix (copy all existing data),
or O(1) if you have pre-allocated space.
 Real-time Example (FriendConnect):

Imagine a new user, "Diana", signs up for FriendConnect.

When Diana is inserted as a vertex, she becomes part of the network, but she doesn't have
any friends yet. She exists as an isolated profile, waiting to connect with others. Her
FriendConnect profile is created, but her "friends list" is empty.
2. Delete Vertex (Remove Node)
 Theoretical Explanation:

Deleting a vertex v involves removing the entity v from the graph entirely. This also
necessitates the removal of all edges that were connected to v. Any connections that v had
with other vertices are severed.

o In an Adjacency List: You would mark the vertex as "inactive" or "deleted." Then,
you must traverse the adjacency lists of all other remaining vertices to find and
remove any references (edges) to the deleted vertex v. You also clear the adjacency
list of v itself.
o In an Adjacency Matrix: You would mark the corresponding row and column for
vertex v as invalid or clear all 1s (or weights) from them, effectively eliminating its
connections and presence.
 Time Complexity (General):
o Adjacency List: O(V+E) in the worst case, as you might need to iterate through all
vertices and their lists to remove incoming edges.
o Adjacency Matrix: O(V) (clearing a row and column).
 Real-time Example (FriendConnect):

Suppose user "Bob" decides to deactivate his FriendConnect account.

When Bob's vertex is deleted, his profile is removed. Crucially, any friendships he had
(e.g., with Alice and Charlie) are also dissolved. Alice and Charlie's friend lists would
automatically update to remove Bob, as their connection to him no longer exists.

3. Add Edge (Connect Nodes)


 Theoretical Explanation:

Adding an edge means establishing a connection between two existing vertices, say u and
v. This operation is only possible if both u and v already exist in the graph. The nature of
the graph (directed or undirected, weighted or unweighted) influences how the edge is
added.

o Undirected Edge: If it's an undirected graph, the connection is bidirectional. You


would add v to u's adjacency list and u to v's adjacency list. In an adjacency matrix,
you would set matrix[u][v] = 1 and matrix[v][u] = 1.
o Directed Edge: If it's a directed graph, the connection is one-way. For an edge (u,
v) (from u to v), you would add v to u's adjacency list only. In an adjacency matrix,
you would set matrix[u][v] = 1 only.
o Weighted Edge: For weighted graphs, instead of 1, you would store the specific
weight.
 Time Complexity (General):
o Adjacency List: O(1) (if adding to the head of the list) or O(degree) if checking for
duplicates or adding to the tail.
o Adjacency Matrix: O(1).
 Real-time Example (FriendConnect):

Let's say "Alice" and "Charlie" are both on FriendConnect but aren't friends yet.

When Alice sends a friend request to Charlie, and Charlie accepts, an edge is added
between their vertices. Now, Alice appears on Charlie's friend list, and Charlie appears on
Alice's friend list, establishing a mutual, undirected connection.

4. Delete Edge (Disconnect Nodes)


 Theoretical Explanation:

Deleting an edge means removing an existing connection between two vertices u and v.
This operation assumes the edge (u, v) already exists.

o Undirected Edge: You would remove v from u's adjacency list and u from v's
adjacency list. In an adjacency matrix, you would set matrix[u][v] = 0 and
matrix[v][u] = 0.
o Directed Edge: For an edge (u, v), you would remove v from u's adjacency list. In
an adjacency matrix, you would set matrix[u][v] = 0.
 Time Complexity (General):
o Adjacency List: O(degree) for the vertices involved (to traverse the list and find the
edge to remove).
o Adjacency Matrix: O(1).
 Real-time Example (FriendConnect):

Imagine "Alice" and "Charlie" are friends, but they have a disagreement and decide to
unfriend each other.

When Alice unfriends Charlie, the edge between their vertices is deleted. They no longer
appear on each other's friend lists, and their direct connection in the network is severed.

5. Find Vertex (Check Existence of Node)


 Theoretical Explanation:

Finding a vertex means determining if a particular entity (identified by its name or ID)
exists within the graph. This is a basic query operation.

o In an Adjacency List (with array indexing): If vertices are identified by integer IDs,
you can simply check if the ID is within the valid range and if the corresponding
slot in your vertex array (or is_active flag) is marked as active.
o In an Adjacency List (with hash map): If vertices have arbitrary names (like
strings), you would typically use a hash map to map the name to an internal integer
ID, and then perform the check as above.
o In an Adjacency Matrix: Similar to the adjacency list with array indexing, you'd
check the validity of the row/column index corresponding to the vertex.
 Time Complexity (General):
o Adjacency List (with direct ID mapping): O(1).
o Adjacency List (with hash map): Average O(1), worst case O(V) (due to hash
collisions).
o Adjacency Matrix: O(1).
 Real-time Example (FriendConnect):

A user wants to send a message to "Eve". Before sending, the system needs to verify if
"Eve" is actually a registered user (an active vertex) on FriendConnect.

The "Find Vertex" operation checks the database or internal representation of the user base.
If "Eve"'s profile exists and is active, the operation returns true, allowing the message to
be sent. Otherwise, it indicates that Eve is not found.

These fundamental operations enable the dynamic manipulation of graph structures, making
graphs incredibly versatile for modeling and solving complex problems across various domains.

Graph Traversal Techniques


Graph traversal involves systematically visiting all the vertices in a graph. There are two primary
algorithms for this: Breadth-First Search (BFS) and Depth-First Search (DFS). These algorithms
are fundamental for exploring graph structures and solving various graph-related problems.

1. Breadth-First Search (BFS)

 Theoretical Explanation: BFS explores a graph level by level. It starts at a given source
vertex, visits all its immediate neighbors, then visits all the neighbors of those neighbors,
and so on. It uses a queue data structure to keep track of the vertices to visit.
1. Start with a source vertex s. Mark s as visited and enqueue it.
2. While the queue is not empty:
 Dequeue a vertex u.
 Visit u (e.g., print its name).
 For each unvisited neighbor v of u:
 Mark v as visited and enqueue v.
 Time Complexity: O(V + E), where V is the number of vertices and E is the number of
edges. This is because each vertex and each edge is visited at most once.
 Real-time Example (FriendConnect): Imagine "Alice" wants to find all her "friends of
friends" (people two steps away) on FriendConnect to expand her network. BFS is perfect
for this.
1. Start: Alice (source vertex).
2. Level 1: BFS would first find all of Alice's direct friends (e.g., Bob, Carol).
3. Level 2: Then, it would find all the friends of Bob (excluding Alice, as she's already
visited) and all the friends of Carol (excluding Alice). These are Alice's "friends of
friends."
4. Level 3 and beyond: If Alice wanted to find friends three or more steps away, BFS
would continue to explore outwards, layer by layer, until all reachable friends were
discovered.

This is similar to how social networks suggest connections or display different degrees of
separation between users.

C Program (using Adjacency Matrix):

#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100

// Queue structure for BFS


struct Queue {
int items[MAX_VERTICES];
int front;
int rear;
};

// Function to create a queue


struct Queue* createQueue() {
struct Queue* q = (struct Queue*)malloc(sizeof(struct Queue));
q->front = -1;
q->rear = -1;
return q;
}

// Check if the queue is empty


int isEmpty(struct Queue* q) {
return q->rear == -1;
}

// Add an element to the queue


void enqueue(struct Queue* q, int value) {
if (q->rear == MAX_VERTICES - 1)
printf("\nQueue is Full!!");
else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->items[q->rear] = value;
}
}

// Remove an element from the queue


int dequeue(struct Queue* q) {
int item;
if (isEmpty(q)) {
printf("Queue is empty");
item = -1;
} else {
item = q->items[q->front];
q->front++;
if (q->front > q->rear) {
q->front = q->rear = -1;
}
}
return item;
}

// BFS function
void bfs(int graph[MAX_VERTICES][MAX_VERTICES], int numVertices, int
startVertex) {
struct Queue* q = createQueue();
int visited[MAX_VERTICES];

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


visited[i] = 0; // Initialize all vertices as not visited
}

visited[startVertex] = 1;
enqueue(q, startVertex);

printf("BFS Traversal starting from vertex %d: ", startVertex);

while (!isEmpty(q)) {
int currentVertex = dequeue(q);
printf("%d ", currentVertex);

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


// If there's an edge from currentVertex to i and i is not
visited
if (graph[currentVertex][i] == 1 && !visited[i]) {
visited[i] = 1;
enqueue(q, i);
}
}
}
printf("\n");
free(q); // Free the allocated queue memory
}

int main() {
int numVertices;
printf("Enter the number of vertices: ");
scanf("%d", &numVertices);

// Adjacency Matrix
int graph[MAX_VERTICES][MAX_VERTICES];

printf("Enter the adjacency matrix (0 or 1 for no/yes edge):\n");


for (int i = 0; i < numVertices; i++) {
printf("Enter row %d (space-separated values): ", i);
for (int j = 0; j < numVertices; j++) {
scanf("%d", &graph[i][j]);
}
}

int startVertex;
printf("Enter the starting vertex for BFS (0 to %d): ", numVertices
- 1);
scanf("%d", &startVertex);

if (startVertex < 0 || startVertex >= numVertices) {


printf("Invalid starting vertex.\n");
return 1;
}

bfs(graph, numVertices, startVertex);

return 0;
}

2. Depth-First Search (DFS)

 Theoretical Explanation: DFS explores a graph by going as deep as possible along each
branch before backtracking. It uses a stack data structure (implicitly, via recursion, or
explicitly) to keep track of the vertices to visit.
1. Start with a source vertex s. Mark s as visited.
2. Visit s (e.g., print its name).
3. For each unvisited neighbor v of s:
 Recursively call DFS on v.
 Time Complexity: O(V + E), where V is the number of vertices and E is the number of
edges. Similar to BFS, each vertex and each edge is visited at most once.
 Real-time Example (FriendConnect): Imagine "Alice" wants to find a specific friend,
"David," but she suspects David is connected to her through a long chain of mutual friends,
and she wants to trace a single path to him. DFS is suitable for this "pathfinding" or
"reachability" scenario.

1. Start: Alice.
2. Explore a Path: DFS would go from Alice to Bob, then from Bob to Frank, then
from Frank to Gina, and so on, following one continuous path as deep as possible.
3. Backtrack: If it reaches a dead end (a user with no unvisited friends) or a path that
doesn't lead to David, it "backtracks" to the last user with unvisited friends and
explores another path.
4. Find David: It continues this process until David is found or all reachable users
have been explored. This is similar to exploring a specific branch of a family tree
to find a distant relative.
C Program (using Adjacency Matrix):

#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100

int visited_dfs[MAX_VERTICES]; // Global visited array for DFS

// DFS function (recursive)


void dfs(int graph[MAX_VERTICES][MAX_VERTICES], int numVertices, int
currentVertex) {
printf("%d ", currentVertex);
visited_dfs[currentVertex] = 1; // Mark as visited

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


// If there's an edge from currentVertex to i and i is not visited
if (graph[currentVertex][i] == 1 && !visited_dfs[i]) {
dfs(graph, numVertices, i); // Recursively call DFS on the
unvisited neighbor
}
}
}

int main() {
int numVertices;
printf("Enter the number of vertices: ");
scanf("%d", &numVertices);

// Adjacency Matrix
int graph[MAX_VERTICES][MAX_VERTICES];

printf("Enter the adjacency matrix (0 or 1 for no/yes edge):\n");


for (int i = 0; i < numVertices; i++) {
printf("Enter row %d (space-separated values): ", i);
for (int j = 0; j < numVertices; j++) {
scanf("%d", &graph[i][j]);
}
}

int startVertex;
printf("Enter the starting vertex for DFS (0 to %d): ", numVertices
- 1);
scanf("%d", &startVertex);

if (startVertex < 0 || startVertex >= numVertices) {


printf("Invalid starting vertex.\n");
return 1;
}

// Initialize visited array for DFS


for (int i = 0; i < numVertices; i++) {
visited_dfs[i] = 0;
}

printf("DFS Traversal starting from vertex %d: ", startVertex);


dfs(graph, numVertices, startVertex);
printf("\n");

return 0;
}

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy