--- title: "Data Structures and Algorithms - CSL210" tags : "SEM4, OC" --- # Data Structures and Algorithms - CSL210 mansiaradke@gmail.com 9823335046 Classes Mon - 10am Sat - 2pm Thur - 8am B slot - Exam Text Books Required Data Structures and Program Design in C - Robert Kruse No Hard coding Save program files as .c files and not .cpp <limits.h> - `INT_MIN` # Functions Name, parameter list, return type, body/defination Function prototype must be declared ## Recursion Return address, Local Variable, Parameters, Return Value Key Notes - Base Case - Making sure you are dealing with progressively smaller or simplers sub problems - How to parameterize the function is an important aspect Recursion vs Iteration Iteration is faster as it doesn't increase the stack space (overhead) and needs to store the activation record at each call ### Tail Recursion When the recursion calling statement is the last statement Stacks (All local variables, parameters, etc) overwrite themselves so the stack size does not increase with the number of recursive calls In this case iteration and recursion achieve the same level of effeiciency Modern computers do this optimization on their own, so tail recursion turns into iterative 1) Interative or Tail Recursive 2) Now tail recursive and dubplication of task -> accumulators 3) Otherwise recursion # Binary Search and Variation ## Binary Search - Iterative ```c= #include <stdio.h> //Binary Search int BinSearch(int arr[], int n, int x); int main() { int arr[] = {1,2,3,4,5}; int n = *(&arr + 1) - arr; int x = 3; printf("Index - %d", BinSearch(arr, n, x)); return 0; } int BinSearch(int arr[], int n, int x) { int retval = -1; int left = 0; int right = n; int found = 0; while ((right >= left) && (!found)) { int mid = (right+left)/2; if (x == arr[mid]) { retval = mid; found = 1; } else { if (x < arr[mid]) right = mid - 1; else left = mid + 1; } } return retval; } ``` ## Binary Search using Recurrsive Function ```c= int RecurBinSearch(int arr[], int left, int right, int x) { int retval; if (right < left) retval = -1; else { int mid = (right+left)/2; if (x == arr[mid]) retval = mid; else if (x < arr[mid]) retval = RecurBinSearch(arr, left, mid - 1, x); else retval = RecurBinSearch(arr, mid + 1, right, x); } return retval; } ``` ## Binary Search - Iterative, Almost Sorted Array Function (issue) ```c= int BinSearch(int arr[], int n, int x) { int retval = -1; int right = n, left = 0; int found = 0; while ((right >= left) && (!found)) { int mid = (right+left)/2; if (x == arr[mid]) { retval = mid; found = 1; } else if ((mid - 1 >= left) && (arr[mid - 1] == x)) { retval = (mid - 1); found = 1; } else if ((mid + 1 <= right) && (arr[mid + 1] == x)) { retval = mid + 1; found = 1; } else if (x < arr[mid]) right = mid - 2; else left = mid + 2; } return retval; } ``` ## Binary Search - Recursive, Almost sorted array Function ```c= int RecurBinSearch(int arr[], int left, int right, int x) { int retval; if (right < left) retval = -1; else { int mid = (right+left)/2; if (x == arr[mid]) retval = mid; else { if ((mid - 1 >= left) && (arr[mid - 1] == x)) retval = (mid-1); else { if ((mid + 1 <= right) && arr[mid + 1] == x) retval = mid+1; else if (x < arr[mid]) retval = RecurBinSearch(arr, left, mid - 2, x); else retval = RecurBinSearch(arr, mid + 2, right, x); } } } return retval; } ``` # Sorting Techniques ## Bubble Sort ```c= void BubSort(int arr[], int n) { int i = 0; int swap = 1; while ((i < n-1) && swap) { swap = 0; for (int j = 0; j < n-i-1; j++) if (arr[j]>arr[j+1]) { arr[j] += arr[j+1]; arr[j+1] = arr[j] - arr[j+1]; arr[j] = arr[j] - arr[j+1]; swap = 1; } } } ``` Working - ``` 6 1 4 5 2 3 //Input array 1 6 4 5 2 3 1 4 6 5 2 3 1 4 5 6 2 3 1 4 5 2 6 3 1 4 5 2 3 6 //End of first iteration (5 comparisons) 1 4 5 2 3 6 1 4 5 2 3 6 1 4 2 5 3 6 1 4 2 3 5 6 //End of second iteration (4 comparisions) ... ``` Flag - used to avoid unnecessary iterations Number of steps at max will be $\frac{n(n+1)}{2}$ Time Complexity = O(n^2^) Requires 2 loop ## Selection Sort ```c= void SelSort(int arr[], int n) { int i = 0; for (i = 0; i < n-1; i++) { int min = i; for (int j = i+1; j < n; j++) if (arr[j] < arr[min]) min = j; if (i!=min) { arr[i] += arr[min]; arr[min] = arr[i] - arr[min]; arr[i] = arr[i] - arr[min]; } } } ``` Though the time complexity of both Bubble sort and selection sort is the same, the swapping of elements is dont more effciently in Selection Sort compared to Bubble Sort ## Insertion sort ```c= void InsSort(int arr[], int n) { for (int i = 1; i < n; i++) { int key = arr[i]; int j = i-1; while((j >= 0) && (arr[j]>key)) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; } } ``` Working - ``` 7 5 1 2 4 6 8 3 //Input array 7 5 1 2 4 6 8 3 //Key = 7, already sorted can be skipped 5 7 1 2 4 6 8 3 //Key = 5 1 5 7 2 4 6 8 3 //Key = 1, the array before the next key is completely sorted 1 2 5 7 4 6 8 3 //Key = 2 1 2 4 5 7 6 8 3 //Key = 4 1 2 4 5 6 7 8 3 //Key = 6 1 2 4 5 6 7 8 3 //Key = 8 1 2 3 4 5 6 7 8 //Key = 3 ``` ## Quick Sort Select a pivot and partition the array keeping greater elements on one side and smaller in on the other, and calling this function recursively Time Complexity - O(nlog n) Space Complexity - O(n) Inplace Sorter Preferable for sorting Arrays over Merge Sort vice versa for linked list ```c= void QukSort(int arr[], int left, int right) { if (left < right) { int part = split(arr, left, right); if (part >= 0) { QukSort(arr, left, part); QukSort(arr, part+1, right); } } } int split(int arr[], int left, int right) { int retval = -1; if (left < right) { int pivot = left; int i = left; int j = right; while (i < j) { while ((arr[i] < arr[pivot]) && (i < right)) { i++; } while ((arr[j] > arr[pivot]) && (j > 0)) { j--; } if (i < j) { arr[i] = arr[j] + arr[i]; arr[j] = arr[i] - arr[j]; arr[i] = arr[i] - arr[j]; } } retval = j; } return retval; } ``` ## Merge Sort Merge Sort involves dividing the array into smaller portions and then merging two arrays at a time in a sorted manner Time Complexity - O(nlog n) Space Complexity - O(n) Not inplace sort Recursive Merging of sorted arrays ```c= void merge_sort(int arr[], int temp[], int start, int end) { int arr_size = end - start; if (arr_size == 2) { if (arr[start] > arr[start + 1]) { int hold = arr[start]; arr[start] = arr[start + 1]; arr[start + 1] = hold; } } else if ((arr_size != 1) && (arr_size != 0)) { int mid = (end + start)/2; merge_sort(arr, temp, start, mid); merge_sort(arr, temp, mid, end); int i = start; int j = mid; int k = start; while ((i < mid) && (j < end)) { if (arr[i]>arr[j]) { temp[k] = arr[j]; j++; } else { temp[k] = arr[i]; i++; } k++; } while (i < mid) { temp[k] = arr[i]; i++; k++; } while(j < end) { temp[k] = arr[j]; j++; k++; } for (i = start; i < end; i++) { arr[i] = temp[i]; } } } ``` Counting number of Inversions ```c= int merge_sort(int arr[], int temp[], int start, int end) { int ret_val = 0; int arr_size = end - start; //printf("%d, %d\n", start, end); if (arr_size == 1) ret_val = 0; else if (arr_size == 0) ret_val = 0; else if (arr_size == 2) { if (arr[start] > arr[start + 1]) { ret_val = 1; //printf("%d, %d\n", arr[start], arr[start + 1]); int hold = arr[start]; arr[start] = arr[start + 1]; arr[start + 1] = hold; } else ret_val = 0; } else { int mid = (end + start)/2; //printf("%d", mid); ret_val = merge_sort(arr, temp, start, mid) + merge_sort(arr, temp, mid, end); int i = start; int j = mid; int k = start; while ((i < mid) && (j < end)) { if (arr[i]>arr[j]) { //printf("%d, %d\n", arr[i], arr[j]); ret_val = ret_val + (mid - i); temp[k] = arr[j]; j++; } else { temp[k] = arr[i]; i++; } k++; } while (i < mid) { temp[k] = arr[i]; i++; k++; } while(j < end) { temp[k] = arr[j]; j++; k++; } for (i = start; i < end; i++) { arr[i] = temp[i]; } } return ret_val; } ``` ## Heap Sort Insertion Sort - O(n^2^) - Sorts in place Merge Sort - O(nlog n) - Takes extra memory Heap Sort - O(nlog n) - Sorts in place Heap can mean - A data structure - Memory Area (garbage collection) Heap sort refers to Data Structure heap A heap is alist in which each entry contains a key, and, for all positions k in the list, the key at position is at least as large as the keys in positions 2k and 2k+1, provided these positions exist in the list The binary heap is a data structure that is an array object which we can view as a merely complete binary tree Array A which represents a heap has 2 attributes - A.length - Length of Array - A.heapsize - how many elements in the heap are stored within array A A[1...A.length] - may contain numbers A[1...A.heapsize] - Valid numbers $0 \leq \text{A.heapsize} \leq \text{A.length}$ ``` parent(i) return [i/2] left(i) return 2i right(i) reuturn 2i+1 ``` ```c= int left(int k) { return ((2*k) + 1); } int right(int k) { return ((2*k) + 2); } void max_heap(int arr[], int n, int i) { int largest = i; int l = left(i); int r = right(i); if (l < n && arr[l] > arr[largest]) largest = l; if (r < n && arr[r] > arr[largest]) largest = r; if (largest != i) { arr[i] += arr[largest]; arr[largest] = arr[i] - arr[largest]; arr[i] = arr[i] - arr[largest]; max_heap(arr, n, largest); } } ``` # Pointers Pointers hold the address of variables & - Referencing Operator Pointer takes the same amount of memory The parameter passing mechanism in C language is pass by value except for arrays Arrays are passed by reference ## Pointer Operations - Variable which contains the address of another variable `int *p` Here we can say - `p` is a pointer to an integer - `*p` is an integer `*` - dereferencing operator All pointers require the same amount of memory `&` - Referencing operator Valid - Assignment of poinsters of the same type - Adding or subtracting a pointer and an integer - Assignming and comparing to zero - Subtracting two pointers - Comparing two pointers Invalid - Adding 2 pointers - Multiply - Divide - Shift - Mask - Add a float or double to a pointer - Assign one pointer to another type without a cast Array of strings ```c= void print(char* C) { while (*C != '\0') printf("%c", (*C++)); printf("\n"); } void check(char *C[SIZE], int i) { printf("%s", C[i]); } int main() { char *C[] = {"Hi", "Hello", "Apple", "Bottle"}; int n = sizeof(C)/sizeof(C[0]); int i = 0, j = 0 ; printf("%c\n\n", *(*(C+i)+j)); print(*(C+1)); check(C, 3); return 0; } ``` # Stack FILO Basic Stack Requirements ```c= typedef int stack_entry_type; typedef struct stack { stack_entry_type list[SIZE]; int top; }STACK; void InitStack(STACK *s) { s->top = -1; } int IsStackFull(STACK s) { return (s.top == SIZE - 1); } int IsStackEmpty(STACK s) { return (s.top == -1); } void push(stack_entry_type ent, STACK *s) { if(!IsStackFull(*s)) { s->list[(s->top) + 1] = ent; s->top = s->top + 1; } } stack_entry_type pop(STACK *s) { stack_entry_type ent; if (!IsStackEmpty(*s)) { ent = s->list[s->top]; s->top = s->top - 1; } else ent = '\0'; return ent; } ``` # Queue FIFO dequeue operation - Removing enqueue operation - Adding ```c= typedef int queue_entry_type; typedef struct queue { queue_entry_type list[SIZE]; int front; int rear; int count; }QUEUE; void InitQueue(QUEUE *q) { q->front = 0; q->rear = -1; q->count = 0; } int IsQueueFull(QUEUE q) { return (q.count >= SIZE); } int IsQueueEmpty(QUEUE q) { return (q.count == 0); } int QueueSize(QUEUE q) { return q.count; } int enqueue(queue_entry_type ent, QUEUE *q) { int retval = 1; if(!IsQueueFull(*q)) { q->count++; q->rear = (q->rear+1)%SIZE; q->list[q->rear] = ent; } else { retval = 0; printf("Queue is Full"); } return retval; } void clearQueue(QUEUE *q) { q->count = 0; q->front = q->rear + 1; } int dequeue(QUEUE *q) { queue_entry_type ent = -1; if (!IsQueueEmpty(*q)) { q->count--; ent = q->list[q->front]; q->front = (q->front + 1)%SIZE; } else printf("Queue is Empty"); return ent; } void traverseQueue(QUEUE q) { int i; printf("\n"); if (!IsQueueEmpty(q)) for (i = q.front; i-q.front < q.count; i++) printf(" %d", q.list[i%SIZE]); else printf("\nQueue is empty"); } queue_entry_type QueueFront(QUEUE q) { return q.front; } ``` # Linked List Arrays are suitable for - - Inserting/Deleting an element at the end - Randomely accessing any element - Searching the list for a particular value Linked Lists are suitable for - - Inserting an element - Deleting an element - Applications where sequential access is required - In situations where the number of elements cannot be predicted beforehand Types of linked lists (Links) - Linear Single-Linked List - Circular Linked List - Doubly Linked List ## List Basic ```c= typedef int listentry; typedef struct node { listentry entry; struct node *next; }NODE; ``` ### Create Node ```c= NODE * CreateNode(listentry ent) { NODE *p; p = (NODE *)malloc(sizeof(NODE)); p->entry = ent; p->next = NULL; return p; } ``` ### Insert Node at Begining ```c= NODE * InsertBegin(NODE *header, listentry ent) { NODE *p; p = CreateNode(ent); if (header == NULL) header = p; else { p->next = header; header = p; } return header; } ``` ### Insert Node at End ```c= NODE * InsertEnd(NODE * header, listentry ent) { NODE *p, *curr; p = CreateNode(ent); if (header == NULL) header = p; else { curr = header; while (curr->next != NULL) { curr = curr->next; } curr->next = p; } return header; } ``` ### Insert At ```c= NODE* InsertAt(NODE* header, listentry ent, int pos) { NODE* p; p = CreateNode(ent); if (header == NULL) if (pos > 0) printf("\nThe List is smaller than the mentioned position\n"); else header = p; else { if (pos == 0) { p->next = header; header = p; } else { NODE* curr = header; int i = 1; while((i < pos)&&(curr != NULL)) { curr = curr->next; i++; } if (curr != NULL) { p->next = curr->next; curr->next = p; } else printf("\nThe List is smaller than the mesntioned position\n"); } } return header; } ``` ### Remove Node ```c= void RemoveNode(NODE* header, int nn) { NODE* curr = header; if (nn == 0) { *header = *curr->next; } else { for (int i = 0; i < nn-1; i++) { curr = curr->next; if (curr == NULL) { i = nn; printf("\nERROR - Position larger than list size\n\n"); } } if (curr != NULL) { NODE* del_node = curr->next; curr->next = del_node->next; free(del_node); } } ``` ### Print List ```c= void printlist(NODE* header) { if (header == NULL) printf("Empty List"); else { while (header != NULL) { printf(" %d ->", header->entry); header = header->next; } printf(" NULL\n"); } } ``` ### Length of List ```c= int length(NODE* header) { int i = 0; while (header != NULL) { i++; header = header->next; } return i; } ``` ### Reverse List ```python= NODE* Reverse(NODE* header) { NODE *prev, *curr, *next; if (header != NULL) { curr = header; prev = NULL; next = curr->next; while(curr != NULL) { curr->next = prev; prev = curr; curr = next; if (curr != NULL) { next = curr->next; } } header = prev; } return header; } ``` ### List as Stack ```c= NODE *Stack_PUSH(NODE* header, listentry ent) { return (InsertBegin(header, ent)); } NODE* Stack_POP(NODE* header, listentry* out) { NODE *p; *out = -1; if (header == NULL) printf("NULL - Popping an empty Stack\n"); else { p = header; header = header->next; *out = p->entry; //printlist(header); free(p); } return header; } int main() { NODE *Stack = NULL; for (int i = 0; i < 10; i++) Stack = Stack_PUSH(Stack, i); printlist(Stack); listentry a = 0; for (int i = 0; i < 11; i++) { Stack = Stack_POP(Stack, &a); printf("Popped element - %d\n", a); } return 0; } ``` Alternate ```c= typedef int listentry; typedef int BOOL; typedef struct node { listentry entry; struct node *next; }NODE; typedef struct List_Stack { NODE *top; }ListStack; void Stack_INIT(ListStack *stack) { stack->top = NULL; } BOOL Stack_EMPTY(ListStack *stack) { BOOL ret = 0; if (stack->top == NULL) ret = 1; return ret; } void Stack_PUSH(ListStack* stack, listentry ent) { NODE* p; p = (NODE*)malloc(sizeof(NODE)); if (p == NULL) printf("Exhausted Memory\n"); else { p->entry = ent; p->next = stack->top; stack->top = p; } } listentry Stack_POP(ListStack* stack) { NODE *p; listentry ret = -1; if (stack->top == NULL) printf("NULL - Popping an empty Stack\n"); else { p = stack->top; stack->top = p->next; ret = p->entry; free(p); } return ret; } ``` ### List as Queue ```c= #include <stdio.h> #include <stdlib.h> typedef int listentry; typedef int BOOL; typedef struct node { listentry entry; struct node *next; }NODE; NODE * CreateNode(listentry ent) { NODE *p; p = (NODE *)malloc(sizeof(NODE)); p->entry = ent; p->next = NULL; return p; } NODE * InsertBegin(NODE *header, listentry ent) { NODE *p; p = CreateNode(ent); if (header == NULL) header = p; else { p->next = header; header = p; } return header; } void RemoveNode(NODE* header, int nn) { NODE* curr = header; if (nn == 0) { if (header != NULL) curr = header; header = NULL; free(curr); } else { for (int i = 0; i < nn-1; i++) { curr = curr->next; if (curr == NULL) { i = nn; printf("\nERROR - Position larger than list size\n\n"); } } if (curr != NULL) { NODE* del_node = curr->next; curr->next = del_node->next; free(del_node); } } } NODE* Queue_ENQUEUE(NODE* queue, listentry ent) { return InsertBegin(queue, ent); } BOOL Queue_EMPTY(NODE* queue) { BOOL ret = 0; if (queue == NULL) ret = 1; return ret; } listentry Queue_DEQUEUE(NODE* queue) { listentry ret = -1; printf("\n%d", Queue_EMPTY(queue)); if (Queue_EMPTY(queue)) printf("ERROR - Popping an Empty Queue"); else { NODE* curr = queue; int n = 0; while (curr->next != NULL) { curr = curr->next; n++; } ret = curr->entry; RemoveNode(queue, n); printf(" Hi "); } return ret; } void printlist(NODE* header) { if (header == NULL) printf("Empty List"); else { while (header != NULL) { printf(" %d ->", header->entry); header = header->next; } printf(" NULL\n"); } } int main() { NODE *Queue; for (int i = 0; i < 10; i++) { Queue = Queue_ENQUEUE(Queue, i); printlist(Queue); } for (int i = 0; i < 11; i++) { printf("Popped element - %d\n", Queue_DEQUEUE(Queue)); //RemoveNode(Queue, 0); printlist(Queue); } RemoveNode(Queue, 0); printlist(Queue); printf("\n\n%d", Queue_EMPTY(Queue)); return 0; } ``` # Binary Tree At any level i maximum number of nodes is 2^i^ Total number of nodes in a full tree with height h, n = 2^h^-1 h = log(n+1) - h determines the running time ## Search Start at root and search the tree level by level until you find the number - O(n) Binary Search Tree - The value stored at a node is greater than the value stored at its left child and less than the value stored at the right child This property causes the Time complexity to be O(log n) ### Traversal Preorder - Root L~c~ R~c~ Inorder - L~c~ Root R~C~ Postorder - L~c~ R~c~ Root ![](https://i.imgur.com/1nqF3l1.png) - Inorder Traversal - AEHJMTY - Preorder Traversal - JEAHTMY - Postorder Traversal - AHEMYTJ ![](https://i.imgur.com/DLVwDoq.png) ![](https://i.imgur.com/MqPXadm.png) Basics ```c= typedef int BOOL; typedef int key_type; typedef char info_type; typedef struct node { info_type data; struct node *left; struct node *right; }NODE; NODE *NewNode (int data) { NODE *node = (NODE *) malloc (sizeof (NODE)); node->data = data; node->left = NULL; node->right = NULL; return (node); } ``` Printing ```c= void PrintPostorder(NODE* node) { if (node == NULL) return; PrintPostorder(node->left); PrintPostorder(node->right); printf("%d ", node->data); } void PrintInorder(NODE* node) { if (node == NULL) return; PrintPostorder(node->left); printf("%d ", node->data); PrintPostorder(node->right); } void PrintPreorder(NODE* node) { if (node == NULL) return; printf("%d ", node->data); PrintPostorder(node->left); PrintPostorder(node->right); } ``` ```c= #include <stdio.h> #include <stdlib.h> typedef int BOOL; typedef int key_type; typedef char info_type; typedef struct item_tag { key_type key; info_type info; }Item_type; typedef struct Tree_node_tag { Item_type item; struct Tree_node_tag *left; struct Tree_node_tag *right; }Tree_Node_type; typedef struct Tree_Tag { Tree_Node_type *root; }Tree_type; void tree_INIT(Tree_type *tree) { tree->root = NULL; } BOOL tree_EMPTY(Tree_type *root) { BOOL ret = 0; if (root == NULL) ret = 1; return ret; } int getLeafCount(Tree_Node_type *root) { if(root == NULL) { return 0; } else { if(root->left == NULL && root->right==NULL) { return 1; } else { return ((getLeafCount(root->left)) + (getLeafCount(root->right))); } } } void Visit(Tree_Node_type *root) { printf("\n %d %c", root->item.key, root->item.info); } Tree_Node_type * Treesearch(Tree_Node_type *root, key_type target) { Tree_Node_type *temp = NULL; if (root != NULL) { if(target == root->item.key) { temp = root; //return(root) ; } else { if (target < root->item.key) { temp = Treesearch(root->left, target); } else { temp = Treesearch(root->right, target); } } } else { temp = NULL; } return(temp); } Tree_Node_type * insert(Tree_Node_type *root, Item_type item) { Tree_Node_type *p; p = (Tree_Node_type *) malloc(sizeof(Tree_Node_type)); if(p == NULL) { printf("Malloc not successful"); printf("Exhausted Memory\n"); } else { p->item = item; p->right = NULL; p->left = NULL; if (root == NULL) { root = p; } else { if(p->item.key < root->item.key) { root->left = insert(root->left, p->item); } else { root->right = insert(root->right, p->item); } } } return root; } int main() { Tree_Node_type *tree; for (Item_type i = 0; i < 10; i++) insert(tree, i); return 0; } ```