Data Structures and Algorithms Review.

Ace your homework & exams now with Quizwiz!

Growth rates and asymptotic notations. Asymptotic notation is the classification of runtime complexity that uses functions that indicate only the growth rate of a bounding function.

O notation provides a growth rate for an algorithm's upper bound. Ω notation provides a growth rate for an algorithm's lower bound. Θ notation provides a growth rate that is both an upper and lower bound.

binary search efficiency.

O(log n)

10.1 Constant time operations. A constant time operation is an operation that, for a given processor, always operates in the same amount of time, regardless of input values.

Operation Example Addition, subtraction, multiplication, and division of fixed size integer or floating point values. w = 10.4 x = 3.4 y = 2.0 z = (w - x) / y Assignment of a reference, pointer, or other fixed size data value. x = 1000 y = x a = true b = a Comparison of two fixed size data values. a = 100 b = 200 if (b > a) { ... } Read or write an array element at a particular index. x = arr[index] arr[index + 1] = x + 1

Scope is the area of code where a name is visible.

The process of searching for a name in the available namespaces is called scope resolution.

The string type in Python is a special construct known as a sequence type. A sequence type is a type that specifies a collection of objects ordered from left to right.

strings are immutable

Arithmetic operator Description

+ addition - subtraction and negation * multiplication / division // floored division % modulo (remainder) ** exponentiation

11.11 Quicksort Quicksort is a sorting algorithm that repeatedly partitions the input into low and high parts (each part unsorted), and then recursively sorts each of those parts. To partition the input, quicksort chooses a pivot to divide the data into low and high parts. The pivot can be any value within the array being sorted, commonly the value of the middle array element. Once partitioned, each partition needs to be sorted. Quicksort is typically implemented as a recursive algorithm using calls to quicksort to sort the low and high partitions. This recursive sorting process continues until a partition has one or zero elements, and thus already sorted. The quicksort algorithm's runtime is typically O(N log N)

/* This function takes last element as pivot, places the pivot element at its correct position in sorted array, and places all smaller (smaller than pivot) to left of pivot and all greater elements to right of pivot */ int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = (low-1); // index of smaller element for (int j=low; j<high; j++) { // If current element is smaller than the pivot if (arr[j] < pivot) { i++; // swap arr[i] and arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // swap arr[i+1] and arr[high] (or pivot) int temp = arr[i+1]; arr[i+1] = arr[high]; arr[high] = temp; return i+1; } /* The main function that implements QuickSort() arr[] --> Array to be sorted, low --> Starting index, high --> Ending index */ void sort(int arr[], int low, int high) { if (low < high) { /* pi is partitioning index, arr[pi] is now at right place */ int pi = partition(arr, low, high); // Recursively sort elements before // partition and after partition sort(arr, low, pi-1); sort(arr, pi+1, high); } }

11.15 Bucket sort. Bucket sort is a numerical sorting algorithm that distributes numbers into buckets, sorts each bucket with an additional sorting algorithm, and then concatenates buckets together to build the sorted result. A bucket is a container for numerical values in a specific range. Ex: All numbers in the range 0 to 50 may be stored in a bucket representing this range. Bucket sort is designed for arrays with non-negative numbers. Bucket sort first creates a list of buckets, each representing a range of numerical values. Collectively, the buckets represent the range from 0 to the maximum value in the array. For buckets and a maximum value of , each bucket represents values. Ex: For 10 buckets and a maximum value of 50, each bucket represents a range of 5 value; the first bucket will hold values ranging from 0 to 4, the second bucket 5 to 9, and so on. Each array element is placed in the appropriate bucket. The bucket index is calculated as . Then, each bucket is sorted with an additional sorting algorithm. Lastly, all buckets are concatenated together in order, and copied to the original array. O(n^2)

// Function to sort arr[] of size n using bucket sort void bucketSort(float arr[], int n) { // 1) Create n empty buckets vector<float> b[n]; // 2) Put array elements in different buckets for (int i=0; i<n; i++) { int bi = n*arr[i]; // Index in bucket b[bi].push_back(arr[i]); } // 3) Sort individual buckets for (int i=0; i<n; i++) sort(b[i].begin(), b[i].end()); // 4) Concatenate all buckets into arr[] int index = 0; for (int i = 0; i < n; i++) for (int j = 0; j < b[i].size(); j++) arr[index++] = b[i][j]; }

11.16 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. 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 is a sorting algorithm specifically for an array of integers: The algorithm processes one digit at a time starting with the least significant digit and ending with the most significant. Two steps are needed for each digit. First, all array elements are placed into buckets based on the current digit's value. Then, the array is rebuilt by removing all elements from buckets, in order from lowest bucket to highest.

// The main function to that sorts arr[] of size n using // Radix Sort static void radixsort(int arr[], int n) { //Findthemaximumnumbertoknownumberofdigits int m = getMax(arr, n); // Do counting sort for every digit. Note that // instead of passing digit number, exp is passed. // exp is 10^I where i is current digit number for (int exp = 1; m/exp > 0; exp *= 10) countSort(arr, n, exp); }

11.21 Quickselect Quickselect is an algorithm that selects the smallest element in a list. Ex: Running quickselect on the list (15, 73, 5, 88, 9) with k = 0, returns the smallest element in the list, or 5. For a list with N elements, quickselect uses quicksort's partition function to partition the list into a low partition containing the X smallest elements and a high partition containing the N-X largest elements. The smallest element is in the low partition if k is ≤ the last index in the low partition, and in the high partition otherwise. Quickselect is recursively called on the partition that contains the element. When a partition of size 1 is encountered, quickselect has found the smallest element. Quickselect partially sorts the list when selecting the smallest element.

// partition function similar to quick sort // Considers last element as pivot and adds // elements with less value to the left and // high value to the right and also changes // the pivot position to its respective position // in the final array. public static int partition (int[] arr, int low, int high) { int pivot = arr[high], pivotloc = low; for (int i = low; i <= high; i++) { // inserting elements of less value // to the left of the pivot location if(arr[i] < pivot) { int temp = arr[i]; arr[i] = arr[pivotloc]; arr[pivotloc] = temp; pivotloc++; } } // swapping pivot to the final pivot location int temp = arr[high]; arr[high] = arr[pivotloc]; arr[pivotloc] = temp; return pivotloc; } // finds the kth position (of the sorted array) // in a given unsorted array i.e this function // can be used to find both kth largest and // kth smallest element in the array. // ASSUMPTION: all elements in arr[] are distinct public static int kthSmallest(int[] arr, int low, int high, int k) { // find the partition int partition = partition(arr,low,high); // if partition value is equal to the kth position, // return value at k. if(partition == k) return arr[partition]; // if partition value is less than kth position, // search right side of the array. else if(partition < k ) return kthSmallest(arr, partition + 1, high, k ); // if partition value is more than kth position, // search left side of the array. else return kthSmallest(arr, low, partition-1, k ); }

n algorithm's runtime complexity is a function, T(N), that represents the number of constant time operations performed by the algorithm on an input of size N.

An algorithm's best case is the scenario where the algorithm does the minimum possible number of operations. An algorithm's worst case is the scenario where the algorithm does the maximum possible number of operations.

10.5 Algorithm analysis. Binary search is an algorithm that searches a sorted list for a key by first comparing the key to the middle element in the list and recursively searching half of the remaining list so long as the key is not found.

Binary search first checks the middle element of the list. If the search key is found, the algorithm returns the index. If the search key is not found, the algorithm recursively searches the remaining left sublist (if the search key was less than the middle element) or the remaining right sublist (if the search key was greater than the middle element).

6.4 Dynamic typing programmer can call the add function using two integer arguments, as in add(5, 7), which returns a value of 12. Alternatively, a programmer can pass in two string variables, as in add('Tora', 'Bora'), which would concatenate the two strings and return 'ToraBora'.

The behavior of the function depends on the argument types, a concept called polymorphism.

7.5 Class constructors. 7.6 Class interfaces. A class interface consists of the methods that a programmer calls to create, modify, or access a class instance.

The figure below shows the class interface of the RaceTime class, which consists of the __init__ constructor and the print_time() and print_pace() methods. class RaceTime: def __init__(self, start_time, end_time, distance): # ... def print_time(self): # ... def print_pace(self): # ...

6.2 Function parameters Programmers can influence a function's behavior via an input to the function known as a parameter.

The function call print_face('x') passes the char value to that parameter. The value passed to a parameter is known as an argument.

An algorithm's space complexity is a function, S(N), that represents the number of fixed-size memory units used by the algorithm for an input of size N.

The space complexity of an algorithm that duplicates a list of numbers is S(N) = N + k, where k is a constant representing memory used for things like the loop counter and list pointers.

11.2 Binary search. Binary search is a faster algorithm for searching a list if the list's elements are sorted and directly accessible (such as an array). Binary search first checks the middle element of the list. If the search key is found, the algorithm returns the matching location. If the search key is not found, the algorithm repeats the search on the remaining left sublist (if the search key was less than the middle element) or the remaining right sublist (if the search key was greater than the middle element). lookup java example

int binarySearch(int arr[], int l, int r, int x) { if (r >= l) { int mid = l + (r - l) / 2; // If the element is present at the // middle itself if (arr[mid] == x) return mid; // If element is smaller than mid, then // it can only be present in left subarray if (arr[mid] > x) return binarySearch(arr, l, mid - 1, x); // Else the element can only be present // in right subarray return binarySearch(arr, mid + 1, r, x); } // We reach here when element is not present // in array return -1; }

11.5 Selection sort. Selection sort is a sorting algorithm that treats the input as two parts, a sorted part and an unsorted part, and repeatedly selects the proper next value to move from the unsorted part to the end of the sorted part. O(n^2)

int n = arr.length; // One by one move boundary of unsorted subarray for (int i = 0; i < n-1; i++) { // Find the minimum element in unsorted array int min_idx = i; for (int j = i+1; j < n; j++) if (arr[j] < arr[min_idx]) min_idx = j; // Swap the found minimum element with the first // element int temp = arr[min_idx]; arr[min_idx] = arr[i]; arr[i] = temp; } }

11.9 Shell sort. ShellSort is mainly a variation of Insertion Sort. In insertion sort, we move elements only one position ahead. When an element has to be moved far ahead, many movements are involved. The idea of shellSort is to allow exchange of far items. In shellSort, we make the array h-sorted for a large value of h. We keep reducing the value of h until it becomes 1. An array is said to be h-sorted if all sublists of every h'th element is sorted. Shell sort is a sorting algorithm that treats the input as a collection of interleaved lists, and sorts each list individually with a variant of the insertion sort algorithm. Shell sort uses gap values to determine the number of interleaved lists. A gap value is a positive integer representing the distance between elements in an interleaved list. For each interleaved list, if an element is at index i, the next element is at index i + gap value. O(n^2)

int sort(int arr[]) { int n = arr.length; // Start with a big gap, then reduce the gap for (int gap = n/2; gap > 0; gap /= 2) { // Do a gapped insertion sort for this gap size. //The first gap elements a[0..gap-1] are already // in gapped order keep adding one more // element until the entire array is gap sorted for (int i = gap; i < n; i += 1) { // add a[i] to the elements that have been // gap sorted save a[i] in temp and make a // hole at position i int temp = arr[i]; // shift earlier gap-sorted elements up until // the correct location for a[i] is found int j; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) arr[j] = arr[j - gap]; // put temp (the original a[i]) in its correct // location arr[j] = temp; } } return 0; }

11.18 Overview of fast sorting algorithms. 11.20 Bubble sort Bubble sort is a sorting algorithm that iterates through a list, comparing and swapping adjacent elements if the second element is less than the first element. Bubble sort uses nested loops. Given a list with N elements, the outer i-loop iterates N times. Each iteration moves the largest element into sorted position. The inner j-loop iterates through all adjacent pairs, comparing and swapping adjacent elements as needed, except for the last i pairs that are already in the correct position,. O(n^2)

void bubbleSort(int arr[]) { int n = arr.length; for (int i = 0; i < n-1; i++) for (int j = 0; j < n-i-1; j++) if (arr[j] > arr[j+1]) { // swap arr[j+1] and arr[i] int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } }

11.13 Merge sort. Merge sort is a sorting algorithm that divides a list into two halves, recursively sorts each half, and then merges the sorted halves to produce a sorted list. The recursive partitioning continues until a list of 1 element is reached, as list of 1 element is already sorted. The merge sort algorithm uses three index variables to keep track of the elements to sort for each recursive function call. The index variable i is the index of first element in the list, and the index variable k is the index of the last element. The index variable j is used to divide the list into two halves. Elements from i to j are in the left half, and elements from j + 1 to k are in the right half. Merge sort merges the two sorted partitions into a single list by repeatedly selecting the smallest element from either the left or right partition and adding that element to a temporary merged list. Once fully merged, the elements in the temporary merged list are copied back to the original list. O(N log N)

void merge(int arr[], int l, int m, int r) { // Find sizes of two subarrays to be merged int n1 = m - l + 1; int n2 = r - m; /* Create temp arrays */ int L[] = new int[n1]; int R[] = new int[n2]; /*Copy data to temp arrays*/ for (int i = 0; i < n1; ++i) L[i] = arr[l + i]; for (int j = 0; j < n2; ++j) R[j] = arr[m + 1 + j]; /* Merge the temp arrays */ // Initial indexes of first and second subarrays int i = 0, j = 0; // Initial index of merged subarry array int k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } /* Copy remaining elements of L[] if any */ while (i < n1) { arr[k] = L[i]; i++; k++; } /* Copy remaining elements of R[] if any */ while (j < n2) { arr[k] = R[j]; j++; k++; } }

11.7 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. Insertion sort is a simple sorting algorithm that works the way we sort playing cards in our hands. O(n^2)

void sort(int arr[]) { int n = arr.length; for (int i = 1; i < n; ++i) { int key = arr[i]; int j = i - 1; /* Move elements of arr[0..i-1], that are greater than key, to one position ahead of their current position */ while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; } }

List abstract data type

A list is a common ADT for holding ordered data, having operations like append a data item, remove a data item, search whether a data item exists, and print the list. Ex: For a given list item, after "Append 7", "Append 9", and "Append 5", then "Print" will print (7, 9, 5) in that order, and "Search 8" would indicate item not found.

a list, created by surrounding a sequence of variables or literals with brackets [ ]. a list is also called a sequence,meaning that the contained elements are ordered by position in the list

A list item is often called an element of that list. The first item in the list has a position, or index, of 0

Python is case sensitive

A name, also called an identifier, is a sequence of letters (a-z, A-Z, _) and digits (0-9), and must start with a letter. Note that "_", called an underscore, is considered to be a letter.

6.8 Namespaces and scope resolution A namespace maps names to objects. The Python interpreter uses namespaces to track all of the objects in a program.

A programmer can examine the names in the current local and global namespace by using the locals() and globals() built-in functions.

Queue abstract data type

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. FIFO aka line A queue can be implemented using a linked list, an array, or a vector.

runtime error, wherein a program's syntax is correct but the program attempts an impossible operation, such as dividing by zero or multiplying strings together (like 'Hello' * 'ABC').

A runtime error halts the execution of the program. Abrupt and unintended termination of a program is often called a crash of the program.

Queue A queue is an ADT in which items are inserted at the end of the queue and removed from the front of the queue. Linked list

Deque A deque (pronounced "deck" and short for double-ended queue) is an ADT in which items can be inserted and removed at both the front and back. Linked list

Priority queue A priority queue is a queue where each item has a priority, and items with higher priority are closer to the front of the queue than items with lower priority. Heap

Dictionary (Map) A dictionary is an ADT that associates (or maps) keys with values. Hash table, binary search tree

An abstract data type (ADT) is a data type described by predefined user operations, such as "insert data at rear," without indicating how each operation is implemented.

It does not specify how data will be organized in memory and what algorithms will be used for implementing the operations. Ex: A list is a common ADT for holding ordered data, having operations like append a data item, remove a data item, search whether a data item exists, and print the list. A list ADT is commonly implemented using arrays or linked list data structures.

5.5 Membership operators he in and not in operators, known as membership operators, yield True or False if the left operand matches the value of some element in the right operand, which is always a container.

Membership operators can be used to check whether a string is a substring, or matching subset of characters, of a larger string. For example, 'abc' in '123abcd' returns True because the substring abc exists in the larger string.

Individual list elements can be accessed using an indexing expression by using brackets as in my_list[i], where i is an integer.

New elements are added to a list uing the append() list method. Elements can be removed using the pop() or remove() methods.

A tuple, usually pronounced "tuhple" or "toople", behaves similar to a list but is immutable - once created the tuple's elements can not be changed.

Note that printing a tuple always displays surrounding parentheses.

Sequence-type functions are built-in functions that operate on sequences like lists and strings. A subset of such functions is provided below.

Operation Description len(list) Find the length of the list. list1 + list2 Produce a new list by concatenating list2 to the end of list1. min(list) Find the element in list with the smallest value. max(list) Find the element in list with the largest value. sum(list) Find the sum of all elements of a list (numbers only). list.index(val) Find the index of the first element in list whose value matches val. list.count(val) Count the number of occurrences of the value val in list.

Common list ADT operations

Operation Description Example starting with list: 99, 77 Append(list, x) Inserts x at end of list Append(list, 44), list: 99, 77, 44 Prepend(list, x) Inserts x at start of list Prepend(list, 44), list: 44, 99, 77 InsertAfter(list, w, x) Inserts x after w InsertAfter(list, 99, 44), list: 99, 44, 77 Remove(list, x) Removes x Remove(list, 77), list: 99 Search(list, x) Returns item if found, else returns null Search(list, 99), returns item 99Search(list, 22), returns null Print(list) Prints list's items in order Print(list) outputs: 99, 77 PrintReverse(list) Prints list's items in reverse order PrintReverse(list) outputs: 77, 99 Sort(list) Sorts the lists items in ascending order list becomes: 77, 99 IsEmpty(list) Returns true if list has no items For list 99, 77, IsEmpty(list) returns false GetLength(list) Returns the number of items in the list GetLength(list) returns 2

ADTs in standard libraries

Programming language Library Common supported ADTs Python Python standard library list, set, dict, deque C++ Standard template library (STL) vector, list, deque, queue, stack, set, map Java Java collections framework (JCF) Collection, Set, List, Map, Queue, Deque

common queue operations. Operation Description Example starting with queue: 43, 12, 77 (front is 43)

Push(queue, x) Inserts x at end of the queue Push(queue, 56). Queue: 43, 12, 77, 56 Pop(queue) Returns and removes item at front of queue Pop(queue) returns: 43. Queue: 12, 77 Peek(queue) Returns but does not remove item at the front of the queue Peek(queue) return 43. Queue: 43, 12, 77 IsEmpty(queue) Returns true if queue has no items IsEmpty(queue) returns false. GetLength(queue) Returns the number of items in the queue GetLength(queue) returns 3.

7.1 Classes: Introduction. In a program, an objectconsists of some internal data items plus operations that can be performed on that data.

Python automatically creates certain objects, known as built-ins, like ints, strings, and functions.

9.6 Recursive exploration of all possibilities

Recursion is useful for finding all possible paths.

Bag A bag is an ADT for storing items in which the order does not matter and duplicate items are allowed. Array, linked list

Set A set is an ADT for a collection of distinct items. Binary search tree, hash table

how is an abstract data type and a data structure different from each other?

Simply put, an ADT (Abstract Data Type) is more of a logical description, while a Data Structure is concrete. Think of an ADT as a picture of the data and the operations to manipulate and change it. A Data Structure is the the real, concrete thing. It can be implemented and used within an algorithm.

Common ADTs ADT: List Description: A list is an ADT for holding ordered data. Common underlying data structures: Array, linked list

Stack A stack is an ADT in which items are only inserted on or removed from the top of a stack. Linked list

The modulo operator evaluates to the remainder of the division of the two integer operands. Here are a few examples: 23 % 10 evaluates to 3 (because 23/10 is 2 with a remainder of 3) 50 % 50 evaluates to 0 (because 50/50 is 1 with a remainder of 0) 5 % 100 evaluates to 5 (because 5/100 is 0 with a remainder of 5)

The modulo operator is a useful programming tool, used often to limit the maximum value of a variable. Consider the code cur_minutes = (minutes + 30) % 60. If minutes is 15, then (15+30)/60 is 0 and cur_minutes is set to the remainder 45. If minutes is 45, then (45+30)/60 is 1 and cur_minutes is set to the remainder 15. cur_minutes can never have a value of 60 or greater.

7.2 Classes: Grouping data. The class keyword can be used to create a user-defined type of object containing groups of related variables and functions.

The object maintains a set of attributes that determines the data and behavior of the class.

9.1 Recursive functions. A function may call other functions, including calling itself. A function that calls itself is known as a recursive function.

The recursive function has an if-else statement, where the if branch is the end of the recursion, known as the base case. The else part has the recursive calls. Such an if-else pattern is quite common in recursive functions.

Each Python object has three defining properties: value, type, and identity.

Value: A value such as "20", "abcdef", or 55. Type: The type of the object, such as integer or string. the built-in function type() prints the type of an object. Identity: A unique identifier that describes the object. The identity of an object is a unique numeric identifier, such as 1, 500, or 505534. Only one object at any time may have a particular identifier. The identity normally refers to the memory address where the object is stored. Python provides a built-in function id() that gives the value of an object's identity.

Sometimes a programmer wants to determine whether two variables are the same object. The programmer can use the identity operator, "is" to check whether two operands are bound to a single object.

dentity operators do not compare object values; rather, identity operators compare object identities to determine equivalence.

ch4 objects and variables. A common error among new programmers is to assume = means equals, as in mathematics. In contrast, = means "compute the value on the right, and then assign that value into the variable on the left."

num_kids + num_adults = num_people, or 9 = beans_count. Those statements will generate an error.

NP-complete problems are a

set of problems for which no known efficient algorithm exists. NP-complete problems have the following characteristics: No efficient algorithm has found been to solve an NP-complete problem. No one has proven that an efficient algorithm to solve an NP-complete problem is impossible. If an efficient algorithm exists for one NP-complete problem, then all NP-complete problem can be solved efficiently.

7.7 Class customization To customize a class, a programmer implements class methods with special method names that the Python interpreter recognizes. For example, to change how a class instance object is printed, the special __str__() method can be defined, as illustrated below.

then when you print(truck) it comes out in a custom way.


Related study sets

Microsoft Word Chapter 1-4 Midterm

View Set

Respiratory Emergencies and other EMT Class Notes

View Set