Midterm I - Data Structures

Lakukan tugas rumah & ujian kamu dengan baik sekarang menggunakan Quizwiz!

What's an object?

An object is a software bundle of related fields (variables and methods). In OOP, a program is a collection of objects that act on one another.

Declared Type and Actual Type

An object variable has a declared type and a run-time type (actual type). The declared type of variable is the type that is used in the declaration. The run-time type (actual type) is the class that creates the object. ex) The nameList variable declared below has a declared type of List and an actual (or run-time type) of ArrayList. List <String> nameList = new ArrayList <String> ( );

Whats an immutable object?

An object whose values cannot be changed Requirements: - All data fields must be private - No getters that will return a reference to a data field - No setters that will modify the data fields

What algorithm should I choose?

Apply algorithm analysis: The process of estimating the resources needed to run your algorithm in terms of 1) Computer Memory 2) Computational Time (data structures focuses on this) - You need an efficient code - If your data is very large, running time is definitely a concern

Linear Search (Sequential Search)

Goal: Searching for a target (key) in the search space Two outcomes: key is found (success)/ target is not found (failure). Runtime is O(n) [worst-case]

The LinkedListIterator class

public class LinkedList { . . . public ListIterator listIterator ( ) { return new LinkedListIterator ( ); } private class LinkedListIterator implements ListIterator { private Node position; private Node previous; . . . public LinkedListIterator ( ) { position = null; previous = null; } } . . . }

What is OOP?

Object Oriented Programming is a software design method that models the characteristics of real or abstract objects using software classes and objects. It helps design effective large scale software applications.

What are some characteristics of objects?

Objects have: A state (What the objects have) Behavior (What the objects do) Identity (What makes them unique)

"this" reference

"this" refers to the object itself. Within the class definition you can replace any mention of a data field or a method by preceding it with "this" Two reasons: To access hidden data fields To invoke the constructor from another constructor

Implementing Linked List

(holds a reference first to the first node) (has a method to get to the first element) public class LinkedList { private Node first; public LinkedList ( ) // constructor { first = null; } public Object getFirst( ) { if (first == null) throw new NoSuchElementException( ); return first.data; } }

Difference between a class and an object?

- A class is a blueprint for building an object - A class is a prototype that defines state placeholders and behavior common to all objects of its kind - Each object is a member of a single class -- there is no multiple inheritance in Java - An object is an instance of a particular class meaning it's created using the blueprint provided by the class (Person person1 = new Person ( );) - There are typically many object instances for a given class - Each object of a given class has the same built-in behavior but possibly different states - Object are instantiated (created) - To create an object in a program, we must provide a definition for objects, how they behave and what kinds of information they maintain (called a class)

Using Linked List

- A linked list consists of a number of nodes, each of which has a reference to the next node. - Adding and removing elements in the middle of a linked list is efficent - Visiting the elements of a linked list in sequential order is efficent -Random access is not efficent

Inheritance

- Allows a software developer to derive a new class from an existing one - The existing class is called the parent class/superclass/baseclass - The derived class is the child class/subclass -child class inherits characteristics of the parents. So the child class inherits the methods and data defined by the parent class. - A class can extend another class, inheriting all its data members and methods while redefining some of them and/ or adding its own. Data members are variables that have private/public/protected keywords. - Inheritance represents the is a relationship between classes.

Abstract Classes in Java

- An abstract class contains abstract methods - An abstract class can NOT be instantiated (no "new" keyword) - You can think of think of these methods as place-holders - Abstract methods are implemented by concrete subclasses - An abstract method cannot be contained in a nonabstract class - If a subclass of an abstract superclass does not implement all the abstract methods, the subclass must be defined abstract - In a nonabstract subclass extended from an abstract class, all the abstract methods must be implemented even if they aren't used - YOU CANNOT CREATE AN INSTANCE FROM AN ABSTRACT CLASS USING THE NEW OPERATOR BUT YOU CAN USE AN ABSTRACT CLASS AS A DATA TYPE!! The reason to use it is to write more "generic" and it provides a superclass that guarantees all sublcasses provide certain methods and subclasses have to implement abstract methods of its superclass

Growth rate of algorithm efficiency

- An algorithm's time requirements can be expressed as a function of (problem) input size. Algorithm growth rate increases as input increases. The growth rate can be expressed using Big O notation, which gives an upper bound on the worst-case runtime of the algorithm as a function of the size of the input. - The size of a problem depends on the particular problem: - For a search problem, the problem size is the number of elements in the search space - For a sorting problem, the problem size is the number of elements in the given list - How long an algorithm takes to grow is a function of problem size - this is called an algorithm's growth rate. - Instead of counting the exact number of primitive operations we approx runtime of an algorithm as a function of data size -- time complexity Some functions below: Constant function: f(n) = c Linear Function: f(n) = n Quadratic Function: f(n) = n^2 Log Function: f(n) = log(n) Log Linear Function: f(n) = n log n Exponential Function: f(n) = b^n

Interfaces in Java

- An interface is a reference type, similar to a class, that can contain only constants, method signatures, and nested types. - There are no method bodies, - Interfaces cannot be instantiated. - They can only be implemented by classes or extended by other interfaces. - All data fields need to be "public static final" - All methods have to be "public abstract"

Abstract and Concrete Array Type

- As with a linked list, there are two ways of looking at an array list - Concrete implementation: A partially filled array of object references - We usually don't think about the concrete implementation when using an array list (we follow the abstract point of view) Abstract view: Ordered sequence of data items, each of which can be accessed by an integer value An array list allows RANDOM access to all elements while a linked list allows sequential access to its elements Efficiency of operations for arrays and lists Operation | Array | List | ------------------------------------------------------------------ Random Access | O(1) | O(1) | Linear Traversal Step | O(1) |. O(1) | Add/Remove element| O(1). |. O(1)| ------------------------------------------------------------------ Abstract list - Ordered sequence of items that can be transverse , , sequentially - Allows for insertion and removal of elements an any , , position Abstract Array - Ordered sequence of items with random access via an , integer index

Binary Recursion

- Binary recursion occurs whenever there are two recursive calls for each non-base case - These two calls can, for example, be used to solve two similar halves of some problems The LinearSum program can be modified as: - Recursively summing the elements in the first half of the , Array - Recursively summing the elements in the second half of , the array - Adding these two sums/values together Example) A is an array, i is initialized as 0, and n is initialized as array size int BinarySum( int [ ] A, int i, int n) { if (n == 1) then return A[i]; else return BinarySum(A, i, n/2) + BinarySum(A, i + n/2, n/2); }

Sorting Algorithms

- Bubble Sorts - Selection Sort - Insertion Sort - Merge Sort - Quick Sort

The Linked List Iterator's set method

- Changes the data stored in the previously visited element - The set element - The set method in the LinkedListIterator class allows you to modify the value of the last node returned by the iterator. Specifically, it sets the data of the last node returned by the iterator to the specified value public void set (Object obj) { if (position == null) throw new NoSuchElementException( ); position.data = obj; } // So position.data = obj; sets the data stored in the current node to the obj parameter passed into the set() method. In other words, it replaces the existing data in the current node with the new data.

How do you design a recursive solution?

- Determine the size factor (for instance n was the size in factorial(n), array size) - Determine the base cases - The one for which you know the answer (0! = 1, sorting an array of size one is easy (base case)) - Determine the general case(s) - The one where the problems is expressed as a smaller version of itself (must converge to base case)

Quadratic function

- For a given argument/variable b, the function always returns square of n. - This function arises in algorithm analysis any time we use NESTED loops. - The outer loops performs primitive operations in linear time; for each iteration, the inner loop also performs primitive operations in linear time. - The total number of iterations of a nested loop is n * n which is equal to n^2 - For example, sorting an array in ascending/descending order using Bubble sort - Time complexity of most algorithms is quadratic

Exponential Function

- For a given argument/variable n, the function always returns b^n, where b is a base and n is a power - Common in algorithm analysis - Growth rate of exponential functions is faster than all other functions

Logarithmic Function

- For a given argument/variable n, the function always returns logarithmic value of n. - Generally it is written as f(n) = logbn, where b (subscript) is base which is often 2. - Very common - We normally appoximate the logbn to a value x. x is the number of times n is divided by b until the division results in a number less than or equal to 1. - log(3)27 is 3, since 27/3/3/3 = 1 - log(4)64 is 3, since 64/4/4/4= 1 - log(2)12 is 4, since 12/2/2/2/2 = 0.75 =< 1 - The main scenario where log time would be appropriate is binary search since we are repeatedly dividing.

Linear Function

- For a given argument/variable n, the function always returns n - The function arises in algorithm analysis any time we have to do a single basic operation over each of n elements - For example, finding min/max values in a list of values - Time complexity of linear/sequential search algorithm is linear Finding the minimum or maximum element in an unsorted array of n elements requires examining each element of the array at least once. This means that the runtime of the algorithm is proportional to the size of the input, which gives it a linear time complexity. If the array is sorted, it is possible to find the minimum or maximum element in the array in constant time complexity using two approaches: 1) For finding the maximum element: simply return the last element in the sorted array, which is the maximum element. This takes constant time since the maximum element can be accessed directly without examining the other elements. 2) For finding the minimum element: simply return the first element in the sorted array, which is the minimum element. This also takes constant time for the same reason as above. If we do not know that the array is sorted, then we would need to examine each element to determine the minimum or maximum element, which would take linear time complexity. However, if we know that the array is sorted, we can use the above approaches to find the minimum or maximum element in constant time complexity.

ListIterator Type

- Gives access to elements inside a linked list - Encapsulates a position anywhere inside the linked list - Protects the linked list while giving access A ListIterator is a Java interface that allows you to traverse a linked list in both directions, modify the list during iteration, and obtain the index of the element currently being processed. It provides additional functionality beyond the basic iterator, such as the ability to insert or remove elements at the current position. It has many methods associated with it such as: hasNext(): This method returns true if there is a next element in the list, and false otherwise. next(): This method returns the next element in the list and moves the iterator position forward. hasPrevious(): This method returns true if there is a previous element in the list, and false otherwise. previous(): This method returns the previous element in the list and moves the iterator position backward. add(): This method inserts an element into the list at the current position of the iterator. remove(): This method removes the last element returned by next() or previous() from the list. set(): This method replaces the last element returned by next() or previous() with the specified element. The methods of ListIterator are not submethods of the listIterator() method, but rather they are separate methods that are defined by the ListIterator interface and implemented by the LinkedList class. __________ In this visualization, "Head" is now an actual node in the list, and it has a reference to the first node (in this case, "apple"). Subsequent nodes in the list have references to the next node, and the "Tail" node has a null reference since it is the last node in the list. - Think of an iterator as pointing between two elements (like the cursor in a word processor points between two characters) - The listIterator method of the LinkedList class gets a list iterator LinkedList <String> employeeNames = . . . ; ListIterator<String> iterator = employeeNames.listIterator( ); Initially, the iterator points before the first element The next method moves the iterator: iterator.next( ); next throws a NoSuchElementException if you are already past the end of the list - hasNext returns true if there is a next element: if (iterator.hasNext( ) ) iterator.next( ); - The next method returns the element that the iterator is passing: while iterator.hasNext( ) { String name = iterator.next( ); Do something with name } [Behind the scenes: the for loop uses an iterator to visit all the elements] LinkedList is also doubly linked list meaning you can transverse both ways! - To move the list position backwards use: - hasPrevious (returns boolean) - Previous Adding and Removing from a LinkedList - The add method - Adds an object after the iterator - Moves the iterator position past the new element: - iterator.add("Juliet"); - The remove method - Removes and - Returns the object that was removed // Removes all names that fulfil a certain conidtion while (iterator.hasNext( ) ) { String name = iterator.next( ); if (name FULFILLS CONDITION) iterator.remove( ); }

The Linked List Iterator's Remove Method

- If the element to be removed is the first element, call "removeFirst" - Otherwise, the node preceding the element to be removed needs to have its next reference updated to skip the removed element - If the previous reference equals position: - This call does not immediately follow a call to next - Throw an IllegalArgumentException - It is illegal to call remove twice in a row - remove sets the previous reference to position public void remove ( ) { if (previous = = position) throw new IllegalStateException( ); if (position == first) { removeFirst(); } else { previous.next = position.next; } position = previous; }

Simplifications of Big-O

- Just keep the fastest growing term - No constants kept n^3 - 3n = O(n^3) 1+4n = O(n) 2^n + 10n + 3 = O(2^n)

Stack Data Structure and Recursion

- Last in first out data structure - Push operation adds new elements at the top - Pop operation removes the top element

Benefits of OOP

- Modularity (separating entities into separate logical units) - Data hiding (encapsulating) - Implementing of an object's private data and actions can change without affecting other objects that depend on it. - Reusing code through composition (objects containing other objects) or inheritance (objects inheriting the state and behavior of other objects) - Easier design due to modeling

The Linked List Iterator's add method

- Most complex operation - add inserts the new node after the current position - sets the successor (after) of the new node to the successor of the current position public void add(object obj) { if (position == null) { addFirst(obj); position = first; } else { Node newNode = new Node ( ); newNode.data = obj; newNode.next = position.next; position.next = newNode; position = newNode; } previous = position; } 1) It first checks if the current position of the iterator, "position", is null. If it is, it means that the iterator is not pointing to any node in the list, so it adds the new object as the first element in the list by calling the "addFirst" method, and then sets "position" to point to the first node. 2) If "position" is not null, it means that the iterator is pointing to a node in the list, so it creates a new node called "newNode" and sets its "data" field to the new object. 3) It then sets "newNode.next" to point to the node that comes after the current position ("position.next"). 4) It sets "position.next" to point to the new node ("newNode"), effectively inserting the new node into the list. 5)Finally, it sets "position" to point to the new node ("newNode"), so that the iterator is now pointing to the newly added node. 6) The code also sets "previous" to point to "position", so that it can keep track of the previous node in case it needs to remove or modify it later.1 newNode.next = position.next creates a link between the new node (newNode) and the next node in the linked list (which is the node that comes after the current position). This means that the new node is inserted after the current position. position.next = newNode creates a link between the current position node and the new node. This means that the new node becomes the next node after the current position node.

Binary Search

- Searching through a sorted list of items - Sorted list is a requirement for Binary Search - Repeatedly divides the search space (list) into two How it works: (Low index + High Index) / 2 = Middle Index - Compared middle index's number with actual number - Move accordingly Example: 5. 10. 18. 30. 45. 50 60. 65. 75. 80 Key = 30 Lowes Index = 0 Highest Index = 9 Middle Index = (0 + 9) /2 = 4 (integer division you throw away the remainder) Index 4 --> 45 30 < 45 so target is in lower half of the list New List = 5 10 18 30 Low Index = 0 High Index = 3 Middle Index: (0 + 3)/2 = 1 Index 1 --> 10 30 > 10 (target must be in the higher half of the list) New List = 18 30 Low index = 2 High Index = 3 Middle Index: (2 + 3) / 2 = 2 30 > 18 (so target must be in higher half of the list) New List: 18 High index = 3 Low Index = 3 Middle Index = (3 + 3) / 2 = 3 30 = 30, so key is found at index 3

Iterator Linked List

- Traverse through the linked list and access each element in a sequential manner. - Remove elements from the linked list safely while iterating through it. This ensures that the iteration is not affected by the removal of elements. - Add new elements to the linked list at the current position of the iterator. - Replace elements in the linked list at the current position of the iterator.

Big-O and function growth rate

- We use a convention O-notation to represent different complexity classes - The statement "f(n) is O(g(n))" means that the growth rate of f(n) is no more than the growth rate of g(n) - g(n) is the upper bound on f(n); the max number of primitve operations - We can use the big-O notation to rank functions according to the growth rate

Adding a new first element

- When a new node is added to the list - It becomes the head of the list - The old list head becomes its next node public void addFirst(Object obj) { Node newNode = new Node ( ); newNode.data = obj; newNode.next = first; first = newNode; } 1) A new Node object is created and stored in the variable newNode. 2) The data field of newNode is set to the parameter obj. 3) The next field of newNode is set to the current first node of the linked list. 4) The first field of the linked list is set to newNode, so that newNode becomes the new first node. Field of = In the context of "data" it means that data is a member variable/data-field that belongs to each instance of the node class. A member variable, also known as an instance variable or class variable, is a variable that is declared within a class but outside of any methods or constructors. Member variables are limited to the class in which they are declared. They can be accessed from any method or constructor within the class, but are not visible outside the class unless they are declared as public, protected or package-private and accessed through an object of that class.

Removing the first element

- When the first element is remove - The data of the first node are saved and later returned as the method result - The successor the first node becomes the first node of the shorter list - The old node will be garbage collected when there are no further references to it public Object removeFirst( ) { if (first == null) throw new NoSuchElementException( ); Object obj = first.data; first = first.next; return obj; }

Some more algorithmic runtime stuff

- Worst case runtime, measures the maximum number of primitive operations executed - Best casse runtime, measures the minimum number of primitve operations exeucted such as value in a list where the value is the 1st position or sorting a list where the values are already in a desired order - Average-case run time, the efficency averaged on all possible inputs

What is generics?

A generic type is a class/interface that can work with different data types. When you define a generic class/interface, you use a type parameter to represent a placeholder for the actual type that will be used at runtime. example) public class GenericList<T>. When you create an instance of the "GenericList" class you specify the actual type that will be used by passing a type argument. The main benefit of using generics in Java is type safety. With generics, you can ensure that your code works with a specific type of objects. Generic Classes: A generic class is implemented exactly like a non-generic class. The only difference is that it contains a type parameter section. There can be more than one type of parameter, separated by a comma. The classes, which accept one or more parameters, ​are known as parameterized classes or parameterized types. Generics are a language feature that allow you to write code that is generic, or parameterized, over different types. For example, you can write a generic method that works on a List of any type, rather than having to write separate methods for Lists of different types. Generics use type parameters, which are specified in angle brackets (<>) and can be used to enforce type safety. By using generics, you can write reusable code that can be used in many different situations. For example, you might write a generic method that works on a List of any type, rather than having to write separate methods for Lists of different types. This can make your code more flexible and adaptable, and can save you time and effort when you need to write code that works with different types of data.

Binary Search Using Binary Recursion

A is an array, key is the element to be found, LI is initialized as 0, and HI is initialized as array size - 1 int BinarySearch(int key, int [ ] A, int LI, int HI) { if (LI > HI ) return - 1 // key doesnt exist mid = (LI + HI) /2; if (key == A[mid]) // base case return mid; else if (key < A[mid]) // recursive case I return BinarySearch (key, A, LI, mid - 1); else return Binary Search(key, A, mid + 1, HI); }

Recursion

A solution strategy that solves larger problems by reducing the problem to smaller problems of the same from as the original problem. What distinguishes recursion from the other sub-problems is that the smaller decomposes sub-problems are of the same form as the larger problems.

Designing a Stack Class

A stack is a data structure that stores data in a last-in, first out order where new items are added to the top and older items are at the bottom. Items in this structure can only be removed from the top of the stack. example: StackOfCharacters class contract: StackOfCharacters( ) - Constructs an empty stack of characters with a default capacity of 10 as an example. StackOfCharacters (capacity: int) - Constructs an empty stack of characters with a specified capacity (if the provided capacity is less than or equal too zero, the capacity is set to a defaulted 10). Empty( ): Boolean - Returns true if the stack is empty, otherwise it's false getSize; int - Returns the number of elements in the stack getCapacity( ): int - Returns the capacity of the stack push (value: Character): void - Puts the character stack at the top of the stack. pop ( ): Character- Removes the character from the top of the stack and returns it. If the stack is empty, returns null. Peek ( ): Character - Returns the character from the top of the stack (without removing it). If the stack is empty, returns null.

Java Access Modifiers

A subclass inherits all public and protected methods and data fields from its superclass. Private: Only accessible within a class Default: If you don't use a modifer, this is the access modifer. The default modifer is accessibly only within package. Protected: The proteced access modifier is accessible within package and outisde the package but through inheritance only. The protected access modifier can be applied on the data member, method and consructor. It can't be applied on the class. Public: The public access modifier is accessible everywhere (highest scope).

Why don't we need iterators with arrays?

An integer index can be used to access any array location (arrays have a fixed size) We need an iterator with a linked list because linked lists are dynamic data structures, which means that their size can change during program execution. Linked lists do not have a fixed size, and they store their elements in nodes that are linked to each other using pointers or references. In a linked list, it is possible to access the elements by their position or index, but this operation is less efficient than in an array. This is because a linked list does not provide direct access to its elements, and we have to traverse the list one node at a time to find the element at the desired position. In order to access the ith element of a linked list, we have to start at the first node and traverse i-1 nodes, following the pointers or references from each node to the next. This can take O(i) time, which is slower than the O(1) time required to access the ith element of an array.

Object Composition

An object can contain another object. This relationship is called a "has-a" relationship. For example, a Student object has a: name: string-object date of birth: a date object address: an address object The "owner" object is the aggregating object and it belongs to the aggregating class. Whilst the "subject" object is the aggregated object and it belongs to the aggregated class.

Primitive Operations

Basic computations performed by an algorithm. Examples) - Returning from a method (return x) - Arithmetic expressions/Comparison statements (1 + i, i < n) - Accessing an element of an array (arr[ i ]) - Assignment statements (i = 3) - Method calls - Each operations corresponds to a low-level instruction of constant execution time - Total number of primitive operations executed - is the running time of an algorithm - is a function of the input size

Binary Search using Binary Recursion

Binary Search is where you are searching for the index of a specific element in a sorted array. It works by halving the portion of the list that could contain the item, until you've narrowed down the possible locations to just one. The array must be sorted to apply binary search!!! A is an array, key is the element to be found, LI is initalized as 0, and HI is initalized as array size - 1 int BinarySearch (int key, int [ ] A, int LI, int HI) { if (LI > HI) then //key does not exist return -1; mid = (LI + HI ) /2 if (key == A[mid]) // base case return mid; else if (key < A [mid]) // recursive case I return BinarySearch(key, A, LI, mid -1); else return BinarySearch(key, A, mid + 1, HI); }

Constructor Chaining

Constructing an instance of a class invokes the constructors of ALL the superclasses along the inheritance chain. A constructor may: - Invoke and overloaded constructor (of its own class) - Invoke its superclass constructor (this has to be done in the first line of the constructor)

The Super Reference

Constructors are not inherited (although they have public visibility) We often wants to use the parent's constructor to set up the "parents part" of the object. The super reference can be used to refer to the parent class, and often is used to invoke the parent's constructor. The "super" keyword is used to access superclass constructors, methods, and data field. super( ) ; //invokes the default constructor of the superclass super(paramList); //invokes the constructor with parameters of the superclass (assuming if it exist) super() is used to call the default constructor of the parent class. If the parent class does not have a default constructor (i.e., a constructor with no parameters), then the super() call will result in a compilation error. On the other hand, super(paramList) is used to call a specific constructor of the parent class that takes parameters. If the parent class has multiple constructors with different parameter lists, the subclass constructor must explicitly call the desired parent constructor using super(paramList). Constructors of the superclass are NOT inherited by its subclass. They can be invoked using the super keyword within the subclass constructors.

Constant Function

For a given argument/variable n, the function ALWAYS returns a constant value. It is independent of variable n It is commonly used to approximate the total number of primitive operations in an algorithm Most common constant function is g(n) = 1 In algorithm analysis, a constant function refers to an algorithm that takes a fixed amount of time to execute, regardless of the size of the input. The runtime of a constant function is independent of the input size, which means that it has a growth rate of O(1). Examples of algorithms that have a constant runtime include simple arithmetic operations, such as adding two numbers or multiplying two numbers, and accessing elements in an array by index.

Log Linear Function

For a given argument/variable n, the function always returns: n log n - Generally it is written as f(n) = n log(b)n, where b is a base often 2. - Also common in algorithm analysis - Growth rate of a log linear function is faster as compared to linear and log functions (faster growth = slower runtime) Another example of an algorithm with O(n log n) time complexity is the Quicksort algorithm. Quicksort works by partitioning the input array into two sub-arrays around a pivot element, and then recursively sorting the sub-arrays. The partitioning operation takes O(n) time, while the recursive sorting operations take O(log n) time. Therefore, the overall time complexity of Quicksort is also O(n log n).

sorting algorithms

Goal: Generally, to arrange a list of elements in some order List of Numbers: 10 20 50 30 40 60 25 (Unsorted List) 10 20 25 30 40 50 60 (Sorted list, ascending) 60 50 40 30 25 20 10 (Sorted list, descending)

Interfaces vs. Abstract Classes

In an interface, the data must be constants (and be public static final, and all methods must be public abstract instance methods and there are no constructors) An abstract class can have all types of data (no restrictions with variables or methods. Also constructors are invoked by subclasses through constructor chaining but abstract classes cannot be instantiated using the new operator).

Big O Notation more summarized

In general, the time complexity analysis of an algorithm involves looking at the total number of basic operations that an algorithm performs as a function of the input size. The basic operations can be anything that takes a constant amount of time to execute, such as arithmetic operations, comparisons, and assignments. For example, for an algorithm with a single loop that iterates n times, the total number of iterations would be n, and the time complexity would be O(n). For an algorithm with two nested loops, where the outer loop iterates n times and the inner loop iterates m times for each iteration of the outer loop, the total number of iterations would be n * m, and the time complexity would be O(n * m).

Binary Search Pseudocode

Input: A sorted array A of n numbers, search target v Output: position i where A[ i ] = v, -1 if not found low <-- 0, high < -- n -1 while low <= high do mid <-- (low + high) /2 if A[mid] == v then return mid else if A[mid] < v then low <-- mid + 1 else high ,<-- mid - 1 return -1 [ HUMBLE THE HIGH GIVE THANKS TO THE LOW AND GIVE IT A 1 ]

What are the primitive operations of a data structure?

Insertion - Adding a new element to the data structure Traversing - Accessing each data element exactly once so that certain items in the data may be processed. Traversal can support other operations (editing, deletion) Searching - Finding the location of the data element (also known as "the key") within the data structure Deletion - Removing a data element from the structure Sorting - Arranging the data elements in a logical order (ex: ascending/descending) Merging = Combing data elements from two or more separate data structures into one, new data structure.

Overriding

Is redefining a method of a superclass in a subclass The overriden method has to have the same name, parameter list, and trturn type as the method in the superclass Before the method signature you put @Override

Wrapper Classes

Java provides class wrappers for all of primitive data types. A wrapper class is a class whose object wraps or contrains a primitive data type. When we create an object to a wrapper class, it contains a field and in this field, we store a primitive data type. - Character - Byte - Short - Integer - Long - Float - Double - Boolean A primitive type value can be automatically converted to an object using a wrapper class and vice versa depending on context. example) Integer intObject = new Integer(5); // this is the same thing as Integer intObject = 5; // converting a primitive value to a wrapper object is called boxing // the reverse is called unboxing

Implementing Linked List (Node) (This list will not use a type parameter so we store raw object values and insert casts when retrieving them)

Node: Stores an object and a reference to the next node Methods of linked list class and iterator class have frequest access to the Node instance variables public class LinkedList { . . . public Object data; public Node next; } }

Efficiency of Recursion

Not very efficient - Involves much more operations than necessary (time complexity) - Uses run-time stack, which involves pushing and popping lots of data - Both time/space complexity of recursive functions may be considerably higher than their iterative alternatives

Casting and Instance Of Operator

Person p = new Student (. . .); ^ This example is implict casting. It is done automatically because an instance of the class Student is an stance of the class Person. another example) Person s = new Student (. . .); Student s = p; This will cause a complier error because it uses the declared type of variable p. Since Person is not a student, it cannot preform the assignment. To fix this we need explicit casting, this is because p references a Student object but the compiler doesnt know this: Person s = new Student (...); Student s = (Student) p; //(knowing that Person p = New Student(...)

What is the point of a wrapper class?

Providing a way to encapsulate primitive data types as objects: In Java, primitive data types are not objects, which means they cannot be passed as arguments to methods or stored in collections. Wrapper classes provide a way to encapsulate primitive values as objects, allowing them to be used in situations where objects are required. Providing a way to implement generic types: Java's generic types require objects, not primitives, as type parameters. By using the wrapper classes, we can create generic types that work with primitive values. For example, we can create a list of integers using the "ArrayList<Integer>" class.

Polymorphism

Refers to the ability to process objects differently depending on their data type or class. More specifically, it is the ability to redefine methods for derived classes. Dynamic binding: A method can be implemented in several classes along the inheritance chain. A variable has two types associated with it: Declared Type - The type listed in the variable declaration Actual Type - The type of object that variable references - The method invoked by a variable at runtime is determined by its actual type. However, the complier only determines the appropriateness of method calls based on the declared type. ex) Animal myAnimal = new Dog(); // The declared type of the variable myAnimal is Animal, but the actual type of the object that is assigned to it at runtime is Dog.

Class Abstraction (Abstract class vs interface)

Separates the implementation of a class from its use. It's created from abstract classes and interfaces. An abstract class (extends) is a class that cannot be instantiated. It serves as a base class for other classes. It contains abstract methods which are methods that are declared but not defined and concrete methods, fully defined methods. Abstract methods aren't defined because each subclass might implement them differently. An interface (implements) is a collection of abstract methods and constants that define a contract for the behavior of a class. It specifies what a class should do, but not how it should do it. A class can implement one or more interfaces, and it must provide an implementation for all the methods declared in the interface.

LinkedList class

Specific type of elements in angle brackets: LinkedList <Product> Package: java.util This package has a ton of linked list elements such as: LinkedList<String> 1st= new LinkedList<String>( ); //an empty list 1st.addLast("Harry")// adds an elements to the end of the list 1st.addFirst("Sally")//Adds an element at the beginning of the list 1st.getFirst( ) // Gets the element stored in the beginning of the list 1st.getLast( ) String removed = 1st.removeFirst( ); // Removes the first element of the list and returns it. Removed is "Sally" and 1st is [Harry]. Use removeLast to remove the last element ListIterator<String> iter = 1st.listIterator( ) // provides an iterator for visiting all list elements

Stacks + Queues

Stack: Collection of items with "last in, first out" retrieval Queue: Collection of items with "first in, first out" retrieval Stacks (LIFO) - Allows insertion and removal of elements only at one end (top of the stack) - New items are added to the top of the stack - Items are removed at the top of the stack - Addition = Push - Removal = Pop - Stack of books Queue (FIFO) - Add items to one end of the queue (the tail) - Remove items from the other end of the queue (the head) - Queue stores items in the first in, first out (FIFO) - Items are removed in the same order in which they have been added - Think of people joining the tail of the queue (back of the line) and wait until they reach the head of the queue (front of the line) - Add (adding an element to the tail of the queue) - Remove (removing an element from the head of the queue) - Peek (get the head element without removing it) The linkedlis class also implements the queue interface, and you can use it when a queue is required " Queue <String> q = new LinkedList<String> ( ); "

Components of Pseudo-Code

Standard mathematical symbols are used ( <-- is the same as an "==" which is an assignment operator) An equal sign "=" is the same as "==" in Java Decision structures (if-then-else logic) If a condition then true - action [else false-actions] Loops (Repetition) - While loops do actions - Use indentation to indicate what actions should be included in the loop - example) while counter < 9 do for loops for variable increment-definition do actions for counter <--0; counter < 5; counter <-- counter + 2 do print "Hi" end for Do Loops do actions while condition do print"Hi" counter <-- counter + 1 while counter < 5 Method declarations: Return_type. method_name (parameter_list) method_body example) integer sum (integer num1, integer num2) start result <-- num1 + num2 end Method returns - return value for example) return result Comments // Single line comments go here Arrays A[i] represents the i-th cell in the array A The cells of an array in psedu-code are consistent with Java meaning it goes from A[0] to A[n-1]

Methods of the ListIterator Interface

String s = iter.next ( ); - Assume that iter points to the beginning of the list [Sally] before calling next. After the call, s is "sally" and the iterator points to the end. iter.hasNext( ) - Returns false because the iterator is at the end of the collection. ______________________________________ if (iter.hasPrevious( ) ) { s = iter.previous( ); } //hasPrevious returns true because the ierator is not at the begining of the list ________________________________________ iter.add("Diana"); // Adds an element before the iterator position. The list is now [Diana, Sally] ---------------------------------------------------------- iter.next( ); iter.remove( ); //remove removes the last element returned by next or previous. The list is again [Diana]

Selection Sort

Suppose we want to sort an array in ascending order: Locate the smallest element in the array; swap it with element at index 0, then locate the next smallest element in array; swap it with element at index 1. Continue until all elements are arranged in order. void selectionSort( int List [ ] ) { int temp; int min; int size = List.length; for (i = 0 ; i < size; i++) : min = i; for (j = i + 1; j < size; j++) { if (List[ j ] < List [min]) min = j; } temp = List[min]; List[min] = List[ i ]; //Swap List[ i ] = temp; } } Selection sort is more efficient than bubble sort but they poth have O(n^2)) time complexity

How do you express an algorithm?

Using psuedo-code as it provides the right balance as it's programming language independent. It's a mixture of natural langauge and programming langauge expressions.

Quick Sort

The fastest sorting algorithm which uses O(nLog(n) time complexity and uses partitioning as its main idea. It;s very efficient divide and conquer sorting algorithm and it is based on partitioning of the array of data elements into smaller arrays. Given an array of n elements (example integers) - If the array contains only one element, return Else - Pick one element to use as "pivot" - Partition elements into two sub-arrays: - Elements less than or equal to pivot - Elements greater than pivot - Partitioning loops through, swapping elements below/above pivots - Recursively QuickSort two sub-arrays - Return results __________________________________________ STEPS 1. While data [i_index] <= data[pivot] i++ i_index 2. While data[j_index] > data[pivot] --j_index 3. If i_index < j_index swap data[i_index] and data[j_index] 4. While (j_index > i_index), go to step 1. 5. Swap data[j_index] and data[pivot_index] __________________________________________ ex) ( * = partitioning element, i = i index element, j = j index element) __________________________________________ 40* 20i 10. 80. 60. 50. 7 30 100j while(data[i_index] <= data[pivot] ++ i_index [while (20 < = 40) --> i++] ___________________________________________ 40*. 20. 10i 80. 60. 50. 7. 30. 100j while(data[i_index] <= data[pivot] ++ i_index [while (10 <= 40)] ++i_index ___________________________________________ 40*. 20. 10 80i. 60. 50. 7. 30. 100j while(data[i_index] <= data[pivot] ++ i_index [80 <= 40] while(data[j_index] > data[pivot] -- j_index while(100 > 40) -- j_index if i_index < j_index swap (data[i_index] and data[j_index] [if 3 < 7 swap(80 and 30)] 40*. 20. 10 30i. 60. 50. 7. 80j. 100 BACK TO STEP ONE ___________________________________________ 40*. 20. 10 30i. 60. 50. 7. 80j. 100 While data[i_index] <= data[pivot] ++i_index While (30 <= 40) ++i_index ___________________________________________ 40*. 20. 10 30. 60i. 50. 7. 80j. 100 While data[j_index] > data[pivot] -- j_index [While 60 > 40] -- j_index ___________________________________________ 40*. 20. 10 30. 60i. 50. 7j. 80 . 100 if i_index < j _index (if 4 < 6) swap data[i_index] and data[j_index] (swap 60 and 7) 40*. 20. 10 30. 7i. 50. 60j. 80 . 100 BACK TO STEP 1 ___________________________________________ 40*. 20. 10 30. 7i. 50. 60j. 80 . 100 While data[i_index] <= data[pivot] [while (7 <= 60)] ++i_index ___________________________________________ 40*. 20. 10 30. 7. 50i. 60j. 80 . 100 While data[j_index] > data[pivot] [60 > 40] --j_index 40*. 20. 10 30. 7. 50ij. 60. 80 . 100 ___________________________________________ While data[j_index] > data[pivot] [50 > 40] --j index 40*. 20. 10 30. 7j. 50i 60. 80 . 100 if i_index < j_index 6 < 4 swap data[i_index] and data[j_index] NO SWAP BACK TO STEP 1 ___________________________________________ 5. Swap data[j_index] and data[pivot_index] [Swap 7 and 40] 7. 20. 10 30. 40j*. 50i 60. 80 . 100 NOW PARTITITION IS AT 40 7 20 10 30. [40*] 50 60 80 100 <= data[pivot] >data[pivot] Quicksort

Recursive Sorting - Merge Sort

The general problem of sorting is to take as input an unordered array X[ ]. and gives as output the same set of data but in increasing order. Input: 3 5 2 7 10 8 20 15 14 3 -1 2 -5 Output: -5 -1 2 2 3 3 5 7 8 10 14 15 20 1. Make a recursive call to the sorting function to sort the first half of the input array, 2. Make another recursive call to sort the 2nd half of the input array. 1. Finally, merge the two sorted halves into a single fully sorted array. How to merge two sorted arrays? Say array Y and array Z are two sorted arrays to be merged into a single sorted array. Call the first element of an array "the head" While both Y and Z are non-empty, repeat the following steps. Compare the two heads Y and Z. Remove the smaller heead and put it next in the output. Now either Y or Z is empty. Move the non-empty array to end of the output and stop. The output is now fully sorted. Time complexity worst case: O(nLog(n))

toString( ) methood

The toString() method in Java returns a string representation of an object. When an object is printed to the console, concatenated with other strings, or passed as an argument to a method that expects a string, the toString( ) method is automatically called to convert the object to a string. @Override public String toString( ) { return "Person{name='" + name + "', age=" + age + "}"; }

How can you analyse the running time of an algorithm?

Two main approaches: 1) Experimental 2) Theoretical The experimental approach will require the programmer to run the program on different inputs and record/compare time. For instance in Java: long StartTime = System.currenTimeMillis(); [PROGRAM] long endTime = System.currentTimeMillis(); long totalTime = endTime - startTime; (this is not the ideal approach because you need to execute it on multiple different set of inputs)

Abstract Data Types

Two ways of looking at a linked list: - To think of the concrete implementation of such a list Sequence of node objects with links between them - Think of the abstract concept in linked list - A --> B --> C --> D - Ordered sequence of data items that can be traversed , with an iterator (let the asterick (*) be an iterator A*BCD

The beauty of the theoretical runtime approach

Theoretical approach aims at analyzing the algorithm without implementing it. Evaluating the running time of an algorithm independently of the hardware and operating systems environment. The theoretical approach generates a mathematical function of approximation of the running time of the algorithm in terms of function size n (f(n)).

Inheritance vs Composition

There are two ways of reusing existing class: Composition vs Inheritance Composition exhibits a "has-a" relationship (a car "has a" motor) Inheritance represents the "is a" relationship between data type (a dog "is a" animal) inheritance allows a subclass to inherit methods and properties from a superclass, while composition allows a class to include objects of other classes as its instance variables.

Big-O: Functions Ranking

This is going to go from the best run time to the worst: Constant time, log time, linear time, log linear time, quadratic time, cubic time, exponential time

Linear Recursion

This is the simplest form of recursion where each method is defined so that it makes at most one recursive call each time it is invoked (like the factorial example) Example: Summing the elements of an array We can solve this summation problem using linear recursion by observing that the sum of all n integers in an array is Arr is: Equal to Arr[0], if n = 1 The sum of n - 1 integers in Arr plus the last element int LinearSum (int [ ] Arr, n) { if n = 1 then return Arr[0]; else return Arr[n-1] + LinearSum( Arr, n-1) }

Sample Program

This program will insert strings into a list, iterate through the list (adding and removing elements) and print the list import java.util.LinkedList; import java.util.ListIterator; public class ListTester { public static void main (String [ ] args) { LinkedList < String > staff = new LinkedList<String> ( ); staff.addLast("Diana"); staff.addLast("Harry"); staff.addLast("Romeo"); staff.addLast("Tom"); // 🛎 = iterator position // in the comments indicate the iterator position ListIterator<String> iterator = staff.listIterator( );// 🛎 DHRT /** In Java, the code ListIterator<String> iterator = staff.listIterator(); creates a ListIterator object that can be used to iterate over the elements in a List of String objects called staff.*/ iterator.next( ) ; // D 🛎 HRT iterator.next( ); // DH 🛎 RT iterator.add("Juliet"); DHJ|RT // This method adds the element at the current position of the iterator, and then moves the iterator to the new element.// iterator.add("Nina") // DHJN 🛎 RT iterator.next( ); // DHJNR 🛎 T // Remove last traversed element iterator.remove( ) ; // DHJN 🛎 T // Prints all elements for (String name : staff) System.out.print(name + " ") System.out.println(); // Expected "Diana Harry Juliet Nina Tom" } }

Linear Recursion: Run-Time Analysis

Time complexity of linear recursion is proportional to problem size In terms of Big-O notation time complexity of a linearly recursive function/algorithm is O(n)

Bubble Sort

Traverse a collection of elements - Move from the front to the end - "Bubble" the largest value to the end using pair-wise comparisons and swapping Ex) [42 77] 35 12 101 5 42. [35. 77] 12 101 5 42. 35. 12 [77 101] 5 42. 35. 12. 77 [5. 101] // Largest value correctly placed Notice how this only is the largest value correctly placed, all other values still need to be ordered so this process is repeated Algorithim: void bubbleSort( int List [ ]) { int temp; int size = List.length; for (i = 0; i < size - 1; i++) { for (j= 0; j <size - (i+ 1); j++) { if (List[j] > List [j+1]) //Swap temp = List[j]; List [j] = List[j + 1]; List[j +1] = temp; } } } } //Time complexity at worst case is O(n^2). The best case is O(n)

Insertion Sort: Informal

We divide the list into two parts: Sorted and Unsorted Initially: * The sorted part contains the first element (at index 0) * The unsorted part contains the element from index 1 to , N-1 - Then we move element from index 1 to an appropriate position in the sorted part, keeping the order intact. - Then, we move element from index 2 to an appropriate position in the sorted part, keeping order intact. And so on... - Finally, we move element from index N - 1 to an appropriate position in the sorted part, keeping order intact. Code: void insertionSort( int List [ ] ) { int temp; int size = List.length; for (int i = 1; i < size; i++) { int j = i; temp = List[ i ]; while (j > 0 && temp < List[ j - 1]) { List[ j ] = List[ j -1]; //right shifting j-- ; } List[ j ] = temp; } } //Time complexity of insertion sort is O(n^2)

The Basic Idea of Recursion

We have a bigger problem whose solution is difficult to find. We divide and decompose the problem into smaller (sub) problems. We keep on decomposing until we reach to the smallest sub-problem (base case) for which the solution is easy to find. Then go back in reverse order and build upon the solutions of the sub-problems. Recursion can be applied when the solution of a problem depends on the solutions to smaller instances of the same problem.

What's the purpose of a data structure?

We need a data structure to organize data. This is to facilitate efficent: Storage of data Retrieval of data Manipulation of data

Removing the First Element

When the first element is removed: - The data of the first node are saved and later returned as the method result - The successor of the first node becomes the first node of the short list - The old node will be garbage collected when there are no further references to it <3 public Object removeFirst ( ) { if (first == null) throw new NoSuchElementException( ); Object obj = first.data; first = first.next; return obj; }

Binary Search O(n)

Worst case is O ( log n ) Best Case is O(1) // when key is in the middle of array

Do Linked lists take more storage space than arrays of the same size?

Yes for two reasons. You need to store the node references and each node is a separate object.

Data Structure

a particular way of organizing and storing data such as an array, table, etc. example: Tree, Queue, Stack, Linked List

Recursive paradigm

if (test for simple case) { // Compute a simple solution without using recursion } else { // Break the problem down into subproblems of the same form. Solve each of the subproblems by calling this function recursively. Reassemble the solutions to the subproblems into a solution for the whole. } this is a template for writing recursive functions. You can apply this technique as long as the following conditions are met: 1. You must be able to identify simple cases for which the answer is easily determined 2. You must be able to identify a recursive decomposition that allows you to break any complex instance of the problem into simpler problems of the same form.

Factorial Recursion

int factorial (int n) { if (n == 0) // base case return 1; else // general recursive case return n *factorial (n-1); } __________________________________________ factorial(4) = 4 x factorial (3)) = 4 x (3 x factorial(2)) = 4 x 3 x 2 x factorial(1) = 4 x 3 x 2 x 1 x 1 =. 4 x 3 x 2 x (1 x 1) = 4 x 3 x (2 x 1) = 4 x (3 x 2) = ( 4 x 6) = 24 ----------------------------------- Base Case = Case for which the answer is known (and can be expressed without recursion) General Recursive Case = Is the case that represents the relationship between the main method call and the method calls for the sub-problems Each successive recursive call should bring you closer to a situation in which your answer is known __________________________________________

The Linked List Iterator's next method

position: Reference to the last visted node Also store a reference to the last reference before that - next method: position reference is advanced to position.next old position is remembered in previous If the iterator points before the element of the list, then the old position is null and position must be sent to first public Object next ( ) { if (!hasNext ( ) ) throw new NoSuchElementException ( ); previous = position; // Remember for remove if (position == null) position = first; else position = position.next; return position.data; } The Linked List Iterator's hasNext method - The next method should only be called when the iterator is not at the end of the list - The iterator is at the end - If the list is empty (first == null) - If there is no element after the current position (position.next == null) public boolean hasNext ( ) { if (position == null) return first != null; else return position.next != null; }

Linear Recursion Example: Reversing an Array

void reverseArray (int [ ] Arr, i , j ) { if (i < j) { int temp = Arr[ i ] Arr[ i ] = Arr [ j ] ; Arr[ j ] = temp; reverseArray (Arr, i + 1, j -1) } // there is no base case because the base case does nothing }


Set pelajaran terkait

Chapter 6 - Life Insurance Premiums, Proceeds and Beneficiaries

View Set

NUR 211 Exam 3 Practice Questions

View Set

Chapter 12 Quiz and Homework Questions

View Set