Midterm 2 Study Guide

अब Quizwiz के साथ अपने होमवर्क और परीक्षाओं को एस करें!

Node

Nodes are the elements of a "linked list", which is a set of data structs and a pointer pointing to other nodes in the list.

Linked List Code Sample

#include <iostream> using namespace std; struct node { int data; node *next; }; class linked_list { private: node *head,*tail; public: linked_list() { head = NULL; tail = NULL; } void add_node(int n) { node *tmp = new node; tmp->data = n; tmp->next = NULL; if(head == NULL) { head = tmp; tail = tmp; } else { tail->next = tmp; tail = tail->next; } } }; int main() { linked_list a; a.add_node(1); a.add_node(2); return 0; }

Traversing a Linked List Using Recursion

#include <iostream> using namespace std; struct node { int data; // holds an integer node *next; // pointer to the node that holds the address of the next node }; class linked_list { private: node *head,*tail; // creation of two nodes: head, tail. public: linked_list() // Constructor { head = NULL; tail = NULL; } void add_node(int n) { node *tmp = new node; //allocation of space req for a node by new operator. 'tmp' points to the space allocated tmp->data = n; // gives value to the data of 'tmp' tmp->next = NULL; set 'next' value to NULL if(head == NULL) //no linked list yet, current node 'tmp' is head of linked list { head = tmp; tail = tmp; } else //linked list exists, now add node at the end of the list { tail->next = tmp; // new node 'tmp' will go after the tail of list tail = tail->next; // changing the tail bc the new node is the new tail } } node* gethead() { return head; //print the head of the list } void display(node *head) { if(head == NULL) { cout << "NULL" << endl; //if head is null, print NULL } else { cout << head->data << endl; display(head->next); } } }; int main() { linked_list a; a.add_node(1); a.add_node(2); a.display(a.gethead()); return 0; }

Modulo/Division Hash

A common hash function uses the modulo operator %, which computes the integer remainder when dividing two numbers. Ex: For a 20 element hash table, a hash function of key % 20 will map keys to bucket indices 0 to 19. A hash table's operations of insert, remove, and search each use the hash function to determine an item's bucket. Ex: Inserting 113 first determines the bucket to be 113 % 10 = 3.

Direct Hashing

A direct hash function uses the item's key as the bucket index. Ex: If the key is 937, the index is 937. A hash table with a direct hash function is called a direct access table. Given a key, a direct access table search algorithm returns the item at index key if the bucket is not empty, and returns null (indicating item not found) if empty. Insert/Remove/Search: HashInsert(hashTable, item) { hashTable[item.key] = item } HashRemove(hashTable, item) { hashTable[item.key] = Empty } HashSearch(hashTable, key) { if (hashTable[key] is not Empty) { return hashTable[key] } else { return null } }

Hashtables

A hash table is a data structure that stores unordered items by mapping (or hashing) each item to a location in an array (or vector). Ex: Given an array with indices 0..9 to store integers from 0..500, the modulo (remainder) operator can be used to map 25 to index 5 (25 % 10 = 5), and 149 to index 9 (149 % 10 = 9). A hash table's main advantage is that searching (or inserting / removing) an item may require only O(1), in contrast to O(N) for searching a list or to O(log N) for binary search. In a hash table, an item's key is the value used to map to an index. For all items that might possibly be stored in the hash table, every key is ideally unique, so that the hash table's algorithms can search for a specific item by that key. Each hash table array element is called a bucket. A hash function computes a bucket index from the item's key.

Linear Probing

A hash table with linear probing handles a collision by starting at the key's mapped bucket, and then linearly searches subsequent buckets until an empty bucket is found. Step size is 1. Find the index, and keep incrementing by one until you find a free space. Actually, linear probing distinguishes two types of empty buckets. An empty-since-start bucket has been empty since the hash table was created. An empty-after-removal bucket had an item removed that caused the bucket to now be empty. The distinction will be important during searches, since searching only stops for empty-since-start, not for empty-after-removal.

Quadratic Probing

A hash table with quadratic probing handles a collision by starting at the key's mapped bucket, and then quadratically searches subsequent buckets until an empty bucket is found. Resolving a hash collision by using the rehashing formula (HashValue +/- i^2) % array-size

Circular List

A linked list where the tail node's next pointer points to the head of the list, instead of null. CircularListTraverse(head) { current = head do { visit current current = current->next } while (current != head) } data: "ocean"data: "cloud"data: "river"next:next:next:headVisited:ocean, cloud, rivercurrent != headcurrent == head:prev:prev:prevcurrent

Queues

A queue is an ADT in which items are inserted at the end of the queue and removed from the front of the queue. The queue push operation inserts an item at the end of the queue. The queue pop operation removes and returns the item at the front of the queue. A queue is referred to as a first-in first-out ADT. A queue can be implemented using a linked list, an array, or a vector. A queue ADT is similar to waiting in line at the grocery store. A person enters at the end of the line and exits at the front. British English actually uses the word "queue" in everyday vernacular where American English uses the word "line". Push(queue, x) : Inserts x at the end of the queue Pop(queue) : Returns and removes item at front of queue Peek(queue) : Returns but does not remove item at the front of the queue IsEmpty(queue) : Returns true if queue has no items GetLength(queue) : Returns the number of items in the queue

Stacks

A stack is an ADT in which items are only inserted on or removed from the top of a stack. The stack push operation inserts an item on the top of the stack. The stack pop operation removes and returns the item at the top of the stack. A stack is referred to as a last-in first-out ADT. A stack can be implemented using a linked list, an array, or a vector. Push(stack, x) : Inserts x on top of stack; Pop(stack) : Returns and removes item at top of stack Peek(stack) : Returns but does not remove item at top of stack IsEmpty(stack) : returns true if stack has no items GetLength(stack) : returns the number of items in the stack

Selection Sort

BigOComplexity: N^2 The idea of algorithm is quite simple. Array is imaginary divided into two parts - sorted one and unsorted one. At the beginning, sorted part is empty, while unsorted one contains whole array. At every step, algorithm finds minimal element in the unsorted part and adds it to the end of the sorted one. When unsorted part becomes empty, algorithm stops.

Selection Sort Algorithm

C++ void selectionSort(int arr[], int n) { int i, j, minIndex, tmp; for (i = 0; i < n - 1; i++) { minIndex = i; for (j = i + 1; j < n; j++) if (arr[j] < arr[minIndex]) minIndex = j; if (minIndex != i) { tmp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = tmp; } } }

Chaining

Chaining is a collision resolution technique where each bucket has a list of items (so bucket 5's list would become 55, 75).

Quicksort

Expects that we partition the array, i looks for higher, j looks for lower, then swaps. Worst Case: N^2 - if we're already in a sorted array. Best Case: Nlog(n) - happens when we have a perfect pivot.

Inserting with Linear Probing

HashInsert(hashTable, item) { // Hash function determines initial bucket bucket = Hash(item.key) bucketsProbed = 0 while (bucketsProbed < N) { // Insert item in next empty bucket if (hashTable[bucket] is Empty) { hashTable[bucket] = item return true } // Increment bucket index bucket = (bucket + 1) % N // Increment number of buckets probed ++bucketsProbed } return false }

Chaining Algorithm

HashInsert(hashTable, item) { if (HashSearch(hashTable, item->key) == null) { bucketList = hashTable[Hash(item->key)] node = Allocate new linked list node node->next = null node->data = item ListAppend(bucketList, node) } } HashRemove(hashTable, item) { bucketList = hashTable[Hash(item->key)] itemNode = ListSearch(bucketList, item->key) if (itemNode is not null) { ListRemove(bucketList, itemNode) } } HashSearch(hashTable, key) { bucketList = hashTable[Hash(key)] itemNode = ListSearch(bucketList, key) if (itemNode is not null) return itemNode->data else return null }

Linked List: Insert at Head

INSERT AT HEAD So, the steps to be followed are as follows: Make a new node, Point the 'next' of the new node to the 'head' of the linked list. Mark new node as 'head'. void front(int n) { node *tmp = new node; tmp -> data = n; tmp -> next = head; head = tmp; } The code is very simple to understand. We just made a new node first - node *tmp = new node; tmp->next=head - In this line, we have followed the second step which is to point the 'next' of the new node to the head of the linked list. And in the last line, we are making the new node 'head' as per the third step - head = tmp;

Insertion Sort

Insertion sort is a sorting algorithm that treats the input as two parts, a sorted part and an unsorted part, and repeatedly inserts the next value from the unsorted part into the correct location in the sorted part. Big0Complexity: O(N^2) Best Case: O(N) for (i = 1; i < numbersSize; ++i) { j = i // Insert numbers[i] into sorted part // stopping once numbers[i] in correct position while (j > 0 && numbers[j] < numbers[j - 1]) { // Swap numbers[j] and numbers[j - 1] temp = numbers[j] numbers[j] = numbers[j - 1] numbers[j - 1] = temp --j } }

Array v. Linked List

Linked lists are easily re-sizable, but they are hard to traverse. Linked lists are not consecutively assigned in memory. An array can be accessed via index number, and the memory of an array is consecutively assigned. An array's size cannot be changed.

Insertion Sorting Algorithm Doubly

ListInsertionSortDoublyLinked(list) { curNode = list->head->next while (curNode != null) { nextNode = curNode->next searchNode = curNode->prev while (searchNode != null and searchNode->data > curNode->data) searchNode = searchNode->prev // Remove and re-insert curNode ListRemove(list, curNode) if (searchNode == null) { curNode->prev = null ListPrepend(list, curNode) } else ListInsertAfter(list, searchNode, curNode) // Advance to next node curNode = nextNode } }

Insertion Sorting Algorithm

ListInsertionSortSinglyLinked(list) { beforeCurrent = list->head curNode = list->head->next while (curNode != null) { next = curNode->next position = ListFindInsertionPosition(list, curNode->data) if (position == beforeCurrent) beforeCurrent = curNode else { ListRemoveAfter(list, beforeCurrent) if (position == null) ListPrepend(list, curNode) else ListInsertAfter(list, position, curNode) } curNode = next } }

Search Linked List

ListSearch(list, key) { curNode = list->head while (curNode is not null) { if (curNode->data == key) { return curNode } curNode = curNode->next } return null }

Traversing a Linked List

Loop that can traverse from the beginning to the end of a Linked List. #include <iostream> using namespace std; struct node { int data; node *next; }; class linked_list { private: node *head,*tail; public: linked_list() { head = NULL; tail = NULL; } void add_node(int n) { node *tmp = new node; tmp->data = n; tmp->next = NULL; if(head == NULL) { head = tmp; tail = tmp; } else { tail->next = tmp; tail = tail->next; } } void display() { node *tmp; tmp = head; while (tmp != NULL) { cout << tmp->data << endl; tmp = tmp->next; } } }; int main() { linked_list a; a.add_node(1); a.add_node(2); a.display(); return 0; }

Merge Sort Algorithm

Merge(numbers, i, j, k) { mergedSize = k - i + 1 // Size of merged partition mergePos = 0 // Position to insert merged number leftPos = 0 // Position of elements in left partition rightPos = 0 // Position of elements in right partition mergedNumbers = new int[mergedSize] // Dynamically allocates temporary array // for merged numbers leftPos = i // Initialize left partition position rightPos = j + 1 // Initialize right partition position // Add smallest element from left or right partition to merged numbers while (leftPos <= j && rightPos <= k) { if (numbers[leftPos] <= numbers[rightPos]) { mergedNumbers[mergePos] = numbers[leftPos] ++leftPos } else { mergedNumbers[mergePos] = numbers[rightPos] ++rightPos } ++mergePos } // If left partition is not empty, add remaining elements to merged numbers while (leftPos <= j) { mergedNumbers[mergePos] = numbers[leftPos] ++leftPos ++mergePos } // If right partition is not empty, add remaining elements to merged numbers while (rightPos <= k) { mergedNumbers[mergePos] = numbers[rightPos] ++rightPos ++mergePos } // Copy merge number back to numbers for (mergePos = 0; mergePos < mergedSize; ++mergePos) { numbers[i + mergePos] = mergedNumbers[mergePos] } } MergeSort(numbers, i, k) { j = 0 if (i < k) { j = (i + k) / 2 // Find the midpoint in the partition // Recursively sort left and right partitions MergeSort(numbers, i, j) MergeSort(numbers, j + 1, k) // Merge left and right partition in sorted order Merge(numbers, i, j, k) } } main() { numbers = { 10, 2, 78, 4, 45, 32, 7, 11 } NUMBERS_SIZE = 8 i = 0 print("UNSORTED: ") for(i = 0; i < NUMBERS_SIZE; ++i) { print(numbers[i] + " ") } printLine() MergeSort(numbers, 0, NUMBERS_SIZE - 1) print("SORTED: ") for(i = 0; i < NUMBERS_SIZE; ++i) { print(numbers[i] + " ") } printLine() }

Templates

Multiple types template <typename T> T sum( T x, T y) { return x + y; } int main() { cout << "Sum : " << sum(3, 5) << endl; cout << "Sum : " << sum(3.0, 5.2) << endl; cout << "Sum : " << sum(3.24234, 5.24144) << endl; return 0; } CLASS TEMPLATE: #include <iostream> using namespace std; template <class T> class Student { T marks1; T marks2; public: Student( T m1, T m2 ) { marks1 = m1; marks2 = m2; } T totalMarks() { return marks1 + marks2; } }; int main() { Student<int> s1 ( 45, 50 ); Student<float> s2 ( 47.5, 56.4 ); cout << "Total marks of student1 : " << s1.totalMarks() << endl; cout << "Total marks of student2 : " << s2.totalMarks() << endl; return 0; }

Open Addressing

Open addressing is a collision resolution technique where collisions are resolved by looking for an empty bucket elsewhere in the table (so 75 might be stored in bucket 6). Such techniques are discussed later in this material. Quadratic Probing Linear Probing Rehashing

Converting Singly Linked List to Doubly Linked List

Previous Pointers. Every node has both a next and a previous pointer. Simplifies traversals, complicates inserts.

Quicksort Algorithm

Quicksort algorithm. Partition(numbers, i, k) { l = 0 h = 0 midpoint = 0 pivot = 0 temp = 0 done = false // Pick middle element as pivot midpoint = i + (k - i) / 2 pivot = numbers[midpoint] l = i h = k while (!done) { // Increment l while numbers[l] < pivot while (numbers[l] < pivot) { ++l } // Decrement h while pivot < numbers[h] while (pivot < numbers[h]) { --h } // If there are zero or one elements remaining, // all numbers are partitioned. Return h if (l >= h) { done = true } else { // Swap numbers[l] and numbers[h], // update l and h temp = numbers[l] numbers[l] = numbers[h] numbers[h] = temp ++l --h } } return h } Quicksort(numbers, i, k) { j = 0 // Base case: If there are 1 or zero elements to sort, // partition is already sorted if (i >= k) { return } // Partition the data within the array. Value j returned // from partitioning is location of last element in low partition. j = Partition(numbers, i, k) // Recursively sort low partition (i to j) and // high partition (j + 1 to k) Quicksort(numbers, i, j) Quicksort(numbers, j + 1, k) } main() { numbers[] = { 10, 2, 78, 4, 45, 32, 7, 11 } NUMBERS_SIZE = 8 i = 0 print("UNSORTED: ") for(i = 0; i < NUMBERS_SIZE; ++i) { print(numbers[i] + " ") } printLine() // Initial call to quicksort Quicksort(numbers, 0, NUMBERS_SIZE - 1) print("SORTED: ") for(i = 0; i < NUMBERS_SIZE; ++i) { print(numbers[i] + " ") } printLine() }

Counting/Radix Sort

Radix sort is a sorting algorithm designed specifically for integers. The algorithm makes use of a concept called buckets and is a type of bucket sort. O(N) Any array of integer values can be subdivided into buckets by using the integer values' digits. A bucket is a collection of integer values that all share a particular digit value. Ex: Values 57, 97, 77, and 17 all have a 7 as the 1's digit, and would all be placed into bucket 7 when subdividing by the 1's digit.

Radix Sort Algorithm

RadixSort(array, arraySize) { buckets = create array of 10 buckets // Find the max length, in number of digits maxDigits = RadixGetMaxLength(array, arraySize) // Start with the least significant digit pow10 = 1 for (digitIndex = 0; digitIndex < maxDigits; digitIndex++) { for (i = 0; i < arraySize; i++) { bucketIndex = abs(array[i] / pow10) % 10 Append array[i] to buckets[bucketIndex] } arrayIndex = 0 for (i = 0; i < 10; i++) { for (j = 0; j < buckets[i].size(); j++) array[arrayIndex++] = buckets[i][j] } pow10 = 10 * pow10 Clear all buckets } }

Double Hashing

The process of using two hash functions to determine where to store the data. Double hashing is an open-addressing collision resolution technique that uses 2 different hash functions to compute bucket indices. Using hash functions h1 and h2, a key's index in the table is computed with the formula (h1(key)+i∗h2(key))mod(tablesize). Inserting a key uses the formula, starting with i = 0, to repeatedly search hash table buckets until an empty bucket is found. Each time an empty bucket is not found, i is incremented by 1.

Linked List: Insert at Tail

So, the steps to add the end if a linked list are: Make a new node Point the last node of the linked list to the new node void add_node(int n) { node *tmp = new node; tmp->data = n; tmp->next = NULL; if(head == NULL) { head = tmp; tail = tmp; } else { tail->next = tmp; tail = tail->next; } }

Merge Sort

Split in half, then compare each element, split until you get to one element. Typically going to be log(n). When we merge, we need a temporary array to merge into that stores it already sorted, then we copy back. Big0Complexity Nlog(n)

Linked List: Insert Inbetween

To insert a node in between a linked list, we need to first break the existing link and then create two new links. 1. Make a new node 2. Point the 'next' of the new node to the node 'b' (the node after which we have to insert the new node). Till now, two nodes are pointing the same node 'b', the node 'a' and the new node. 3. Point the 'next' of 'a' to the new node. void after(node *a, int value) { node* p = new node; p->data = value; p->next = a->next; a->next = p;

Linked Lists

a linear collection of data elements, called nodes, each pointing to the next node by means of a pointer. Can be used to implement other data structures. When the data is dynamic in nature and number of data can't be predicted or the number of data keeps changing during program execution. Linked lists are very useful in this type of situations.

Joining Two Linked Lists (concatenate)

void concatenate(struct node *a,struct node *b) { if (a->next == NULL) a->next = b; else concatenate(a->next,b); } The steps to join two lists (say 'a' and 'b') are as follows: Traverse over the linked list 'a' until the element next to the node is not NULL. If the element next to the current element is NULL (a->next == NULL) then change the element next to it to 'b' (a->next = b). Here, we are traversing over the article using recursion. We are firstly checking if the next node (a->next) is NULL or not. If it is NULL, then we are just changing its value from NULL to 'b' (a->next = b) and if it is not then we are calling the 'concatenate' function again with the next element to traverse over the list.


संबंधित स्टडी सेट्स

BSC2085L - Appendicular Skeleton

View Set

115 PrepU Chapter 31: Assessment of Immune Function

View Set

Chapter 13 - Central Nervous System Stimulants and Related Drugs

View Set

(3) HIS: Philippine Health Policies and Initiatives

View Set

HEALTH INSURANCE PORTABILITY AND ACCOUNTABILITY ACT (HIPPA)

View Set