--- title: Algorithms & Data Structures description: date: 2025-02-17T08:52:10+00:00 draft: false tags: - c - programming author: TrudeEH showToc: true --- ## Time Complexity The amount of steps required for an algorithm to execute. ### Big O Notation Maximum time for `n` input size. (Upper bound - worst case) ![Big-O Complexity Chart](bigO.png) ### Omega Notation Minimum time for `n` input size. (Lower bound) - `Ω(n²)` - `Ω(n log n)` - `Ω(n)` - `Ω(log n)` - `Ω(1)` > If both are the same, use `θ` ## Searching Algorithms |Algorithm|Average Time Complexity|RAM| |---|---|---| |Linear Search|O(n) \| Ω(1)|0| |Binary Search|O(log n) \| Ω(1)|0| ### Linear Search Check every element until `n` is found. ```C #include int main(void) { int n = 1; // number to find int numbers[] = {20, 500, 10, 5, 100, 1, 50}; const int numbersLength = 7; for (int i = 0; i < numbersLength; i++) { if (numbers[i] == n) { printf("Found n\n"); return 0; } } printf("Not found\n"); return 1; } ``` ### Binary Search ```C #include int main() { int c; int n = 10 // Number of elements in array int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8 ,9} int search = 2; // Number to find int first = 0; int last = n - 1; int middle = (first+last)/2; // Average while (first <= last) { if (array[middle] < search) first = middle + 1; else if (array[middle] == search) { printf("%d found at location %d.\n", search, middle+1); break; } else last = middle - 1; middle = (first + last)/2; } if (first > last) printf("Not found! %d isn't present in the list.\n", search); return 0; } ``` ## Sorting Algorithms |Algorithm|Average Time Complexity|RAM|Use If| |---|---|---|---| |Selection Sort|O(n²) \| Ω(n²)|1 var|Never| |Bubble Sort|O(n²) \| Ω(n)|0|| |Insertion Sort|O(n²) (Faster if the data is kind of sorted)|0|| |Merge Sort|O(n log n) \| Ω(n log n)|More RAM|Large Arrays| |Quick Sort|O(n log n)|Less RAM|Small arrays| ## Data Structures ### Queue (Concept) First In, First Out - enqueue - Add an item to the end of the line. - dequeue - Remove an item from the beginning of the line. ### Stack (Concept) Last In, First Out The last item added will be used first, so the first ones added might never be used. - push - Add an item to the top of the stack - pop - Remove an item from the top of the stack ### Arrays |Search|O(log n)| |---|---| |Multiple data types|No| |Resizable|No| |RAM Usage|Low| ```C double prices[] = {5.0, 3.2, 13.0}; double values[5]; //Initialize an empty array, that can hold 5 items. printf("%lf€", prices[0]); printf("%d", sizeof(prices)); //3 ``` #### 2D Arrays ```C int numbers[2][3] = { {1, 2, 3}, {4, 5, 6} }; numbers[0][1]; //2 numbers[1][2]; //6 numbers[0][1] = 10; //Set [0][1] to 10. // Note: Calculate the rows and columns automatically. int rows = sizeof(numbers)/sizeof(numbers[0]); int columns = sizeof(numbers[0])/sizeof(numbers[0][0]); ``` #### Array of Strings ```C char cars[][10] = {"Mustang", "Corvette", "Camaro"}; //cars[0] = "Tesla"; ERROR! String arrays don't support item assignment. strcpy(cars[0], "Tesla"); //Does the same, but must be included. ``` ### Resize Array #### Manually ```C #include int *list = malloc(3 * sizeof(int)); // Works like an array list[0] = 1; list[1] = 2; list[2] = 3; //Same as *list = 1; *(list + 1) = 2; *(list + 2) = 3; // ... // Add 1 extra byte to the array int *tmp = malloc(4 * sizeof(int)); if (tmp == NULL) { free(list); return 1; } for (int i = 0; i < 3; i++){ tmp[i] = list[i]; } tmp[3] = 4; free(list); list = tmp; // ... free(list); return 0; ``` #### Realloc ```C int *list = malloc(3 * sizeof(int)); list[0] = 1; list[1] = 2; list[2] = 3; // ... int *tmp = realloc(list, 4 * sizeof(int)); //tmp is only needed to verify if realloc was successful, if realloc was assigned to int and failed, the 3 bytes from the list would be lost as list = NULL. if (tmp == NULL) { free(list); return 1; } list = tmp; list[3] = 4; // ... free(list); return 0; ``` ### Linked Lists | Linked List | TC | | --------------------- | ------ | | Add element unordered | O(1) | | Add element ordered | O(n) | | Search | O(n) | | Insert | O(n) | | Multiple data types | Yes | | Resizable | Yes | | RAM Usage | Medium | Each element has its value, and a pointer to the next element. The last element's pointer is `NULL`. (NULL == 0x0) ```Plain [N] -> [N] -> [N] -> [N] ... ``` ```C typedef struct node { int number; // Value struct node *next; // Pointer to the next node } node; ``` #### Unordered Linked List Example Both code blocks are unsafe (if there's no memory, the program won't free) ```C typedef struct node { int number; struct node *next; } node; int main(void) { node *list = NULL; node *n = malloc(sizeof(node)); // n points at the first node of the linked list. (*n).number = 1; // First element = 1. //Or n->number = 1; // Same. n->next = NULL; // Point to NULL so it won't point to a garbage value. list = n; node *n = malloc(sizeof(node)); // n points at the first node of the linked list. n->number = 2; n->next = list; list = n; } ``` ```C #include #include typedef struct node { int number; struct node *next; } node; int main(void) { node *list = NULL; int numbers[] = {1, 2, 3}; // Create linked list with the elements in the array. for (int i = 0; i < 3; i++) { node *n = malloc(sizeof(node)); if (n == NULL) return 1; n->number = number; n->next = NULL; n->next = list; list = n; } // Print all elements from linked list. node *ptr = list; while (ptr != NULL) { printf("%i\\n", ptr->number); ptr = ptr->next; // Borrow the value in the list->n, which has the next node address. } // Free the list's memory. ptr = list; while (ptr != NULL) { node *next = ptr->next; free(ptr); ptr = next; } } ``` ### Trees | Balanced | Unbalanced | TC | | ------------------- | ---------- | ---- | | Add element | O(n) | O(n) | | Search | O(log n) | O(n) | | Insert | O(log n) | O(n) | | Multiple data types | Yes | Yes | | Resizable | Yes | Yes | | RAM Usage | High | High | If organized correctly, allows for binary search. Each node has 2 pointers. ```Plain [N] - root / parent / \\ [N] [N] - parent and children / \\ / \\ [N] [N] [N] [N] - leaf / child ``` ```C typedef struct treenode { int value; struct treenode *left; struct treenode *right; } treenode; treenode *createnode(int value) { treenode* result = malloc(sizeof(treenode)); if (result != NULL) { result->left = NULL; result->right = NULL; result->value = value; } return result; } void printtabs(int numtabs){ for (int i=0; i < numtabs; i++){ printf("\\t"); } } void printtree_rec(treenode *root, int level){ if (root == NULL) { printtabs(level); printf("---------\\n"); return; } // Preordered printtabs(level); printf("value = %d\\n", root->value); printtabs(level); printf("left\\n"); printtree_rec(root->left, level + 1); printtabs(level); printf("right\\n"); printtree_rec(root->right, level + 1); printtabs(level); printf("done\\n"); } void printtree(treenode *root) { printtree_rec(root, 0); } int main(void){ treenode *n1 = createnode(10); treenode *n2 = createnode(11); treenode *n3 = createnode(12); treenode *n4 = createnode(13); treenode *n5 = createnode(14); // n1 will be the root n1->left = n2; n1->right = n3; n3->left = n4; n4->right = n5; printtree(n1); free(n1); free(n2); free(n3); free(n4); free(n5); } ``` ### Hash Tables | Average | Worst | Best | | ------------------- | --------- | --------- | | Add element | O(1) | O(n) | | Search | O(1) | O(n) | | Insert | O(1) | O(n) | | Multiple data types | Yes | Yes | | Resizable | Yes | Yes | | RAM Usage | Very High | Very High | An array, in which every element is a linked list. The array is used to 'choose' a linked list, and the linked list stores a dynamic number of elements. ```C [0] -> [N] -> [N] -> [N] [1] -> [N] -> [N] [2] -> [N] [3] -> [N] -> [N] [4] -> [N] -> [N] -> [N] [5] -> [N] -> [N] 0-5 - Array element N - Linked list node ``` ### Trie | Trie | Average | Worst | | ------------------- | -------------- | -------------- | | Add element | O(1) | O(1) | | Search | O(1) | O(1) | | Insert | O(1) | O(1) | | Multiple data types | Yes | Yes | | Resizable | Yes | Yes | | RAM Usage | Extremely High | Extremely High | Tree, but every node is an array. Useful to store words. Every element in an array is linked to the next letter. ![Trie](trie.png) ```C #include #include #include #include #define ALPHABET_SIZE 26 struct TrieNode { struct TrieNode *children[ALPHABET_SIZE]; bool isendofword; }; typedef struct TrieNode TrieNode; TrieNode *createNode() { TrieNode *node = (TrieNode *)malloc(sizeof(TrieNode)); node->isendofword = false; for (int i = 0; i < ALPHABET_SIZE; i++) { node->children[i] = NULL; } return node; } void insert(TrieNode *root, const char *key) { TrieNode *current = root; for (int i = 0; i < strlen(key); i++) { int index = key[i] - 'a'; if (current->children[index] == NULL) { current->children[index] = createNode(); } current = current->children[index]; } current->isendofword = true; } bool search(TrieNode *root, const char *key) { TrieNode *current = root; for (int i = 0; i < strlen(key); i++) { int index = key[i] - 'a'; if (current->children[index] == NULL) { return false; } current = current->children[index]; } return (current != NULL && current->isendofword); } bool isempty(TrieNode *root) { for (int i = 0; i < ALPHABET_SIZE; i++) { if (root->children[i] != NULL) { return false; } } return true; } TrieNode *deleteHelper(TrieNode *root, const char *key, int depth) { if (root == NULL) { return NULL; } if (depth == strlen(key)) { if (root->isendofword) { root->isendofword = false; } if (isempty(root)) { free(root); root = NULL; } return root; } int index = key[depth] - 'a'; root->children[index] = deleteHelper(root->children[index], key, depth + 1); if (isempty(root) && !root->isendofword) { free(root); root = NULL; } return root; } void deletekey(TrieNode *root, const char *key) { deleteHelper(root, key, 0); } int main() { TrieNode *root = createNode(); insert(root, "example"); insert(root, "word"); printf("%s\n", search(root, "word") ? "Found" : "Not Found"); printf("%s\n", search(root, "example") ? "Found" : "Not Found"); printf("%s\n", search(root, "trude") ? "Found" : "Not Found"); deletekey(root, "word"); printf("%s\n", search(root, "word") ? "Found" : "Not Found"); return 0; } ```