├── README.md ├── course1_week1_challenge.cpp ├── course1_week2_challenge.cpp ├── course1_week3_challenge.cpp ├── course1_week4_challenge.cpp ├── course2_week1_challenge.cpp ├── course2_week2_challenge.cpp ├── course2_week3_challenge.cpp ├── course2_week4_challenge.cpp ├── course3_week1_challenge.cpp ├── course3_week2_challenge.cpp ├── course3_week3_challenge.cpp └── course3_week4_challenge.cpp /README.md: -------------------------------------------------------------------------------- 1 | # cs-fundamentals 2 | 3 | ## Description 4 | 5 | My works for the data challenges of the Accelerated Computer Science Fundamentals Certification on Coursera. 6 | 7 | ## UIUC [Accelerated CS Fundamentals Certification](https://www.coursera.org/specializations/cs-fundamentals) 8 | 9 | - Course 1: [Object-Oriented Data Structures in C++](https://www.coursera.org/learn/cs-fundamentals-1) 10 | - Course 2: [Ordered Data Structures](https://www.coursera.org/learn/cs-fundamentals-2) 11 | - Course 3: [Unordered Data Structures](https://www.coursera.org/learn/cs-fundamentals-3) 12 | -------------------------------------------------------------------------------- /course1_week1_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Create a class called Pair that has two public integer member variables named "a" and "b", and a public member function named sum() 2 | // that has no arguments but adds the two member variables together and returns their sum. 3 | 4 | // You should define Pair here: 5 | // (Use as many lines as you need!) 6 | class Pair { 7 | public: 8 | int a,b; 9 | int sum() {return a + b;} 10 | }; 11 | 12 | // This main() function will help you test your work. 13 | // Click Run to see what happens. 14 | // When you're sure you're finished, click Submit for grading 15 | // with our additional hidden tests. 16 | int main() { 17 | Pair p; 18 | p.a = 100; 19 | p.b = 200; 20 | if (p.a + p.b == p.sum()) { 21 | std::cout << "Success!" << std::endl; 22 | } else { 23 | std::cout << "p.sum() returns " << p.sum() << " instead of " << (p.a + p.b) << std::endl; 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /course1_week2_challenge.cpp: -------------------------------------------------------------------------------- 1 | // A class type called "Pair" has already been defined for you. You need to write a function called pairFactory that creates an instance 2 | // of Pair on the heap. Do not create the object on the stack. Then, your function needs to return a pointer to that created object. 3 | 4 | #include 5 | 6 | // This class Pair has already been defined for you. 7 | // (You may not change this definition.) 8 | class Pair { 9 | public: 10 | int first, second; 11 | void check() { 12 | first = 5; 13 | std::cout << "Congratulations! The check() method of the Pair class \n has executed. (But, this isn't enough to guarantee \n that your code is correct.)" << std::endl; 14 | } 15 | }; 16 | 17 | // Insert your declaration and implementation of the function pairFactory() 18 | // below, by replacing "..." with proper C++ code. Be sure to declare the 19 | // function type to return a pointer to a Pair. 20 | 21 | Pair * pairFactory() { 22 | // ... 23 | // (You can use as many lines as you want.) 24 | Pair* p = new Pair; 25 | return p; 26 | }; 27 | 28 | // Your function should be able to satisfy the tests below. You should try 29 | // some other things to convince yourself. If you have a bug in this problem, 30 | // the usual symptom is that after you submit, the grader will crash with a 31 | // system error. :-) 32 | int main() { 33 | Pair *p; 34 | p = pairFactory(); 35 | 36 | // This function call should work without crashing: 37 | p->check(); 38 | 39 | // Deallocating the heap memory. (Assuming it was made on the heap!) 40 | delete p; 41 | 42 | std::cout << "If you can see this text, the system hasn't crashed yet!" << std::endl; 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /course1_week3_challenge.cpp: -------------------------------------------------------------------------------- 1 | // A class called Pair has already been declared, but the constructors have not been implemented yet. Pair has two public member variables: 2 | 3 | // int *pa,*pb; 4 | 5 | // These two "pointers to int" are intended to point to heap memory locations that store integers. The remainder of the Pair class expects the 6 | // following functionality. 7 | 8 | // A single constructor Pair(int a, int b): This should set up pa and pb to point to newly allocated memory locations on the heap. The integers 9 | // at those memory locations should be assigned values according to the constructor's integer arguments a and b. 10 | // A copy constructor Pair(const Pair& other): This takes as its argument a read-only reference to another Pair. It should set up the newly 11 | // constructed Pair as a "deep copy," that is, it should create a new Pair that is equivalent to the other Pair based on dereferenced values but 12 | // does not reuse any of the same memory locations. In other words, the copy constructor should set up its own instance's member variables pa and 13 | // pb to point to newly allocated memory locations for integers on the heap; those memory locations must be new, not the same locations pointed to 14 | // by the other Pair, but the integers at these new locations should be assigned values according to the integers that the other Pair is pointing to. 15 | // A destructor ~Pair() that de-allocates all of the the heap memory that had previously been allocated for this Pair's members. 16 | // The types of these member functions have already been declared in the declaration of Pair. Now you need to provide the implementation of each of 17 | // these three member functions. 18 | 19 | /* Class Pair has already been declared 20 | * as shown in the following comments: 21 | * 22 | * class Pair { 23 | * public: 24 | * int *pa,*pb; 25 | * Pair(int, int); 26 | * Pair(const Pair &); 27 | * ~Pair(); 28 | * }; 29 | * 30 | * Implement its member functions below. 31 | */ 32 | Pair::Pair(int a, int b) { 33 | pa = new int; 34 | pb = new int; 35 | *pa = a; 36 | *pb = b; 37 | }; 38 | Pair::Pair(const Pair & obj) { 39 | pa = new int; 40 | pb = new int; 41 | *pa = *obj.pa; 42 | *pb = *obj.pb; 43 | }; 44 | Pair::~Pair() { 45 | delete pa; pa = nullptr; 46 | delete pb; pb = nullptr; 47 | }; 48 | 49 | 50 | 51 | 52 | /* Here is a main() function you can use 53 | * to check your implementation of the 54 | * class Pair member functions. 55 | */ 56 | 57 | int main() { 58 | Pair p(15,16); 59 | Pair q(p); 60 | Pair *hp = new Pair(23,42); 61 | delete hp; 62 | 63 | std::cout << "If this message is printed," 64 | << " at least the program hasn't crashed yet!\n" 65 | << "But you may want to print other diagnostic messages too." << std::endl; 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /course1_week4_challenge.cpp: -------------------------------------------------------------------------------- 1 | // A base class Pair contains a single constructor Pair(a,b) that initializes the pair with the two integer arguments a and b. A derived class 2 | // sumPair inherits the base class Pair, and specializes it with a new constructor sumPair(a,b) and a new variable sum. 3 | 4 | // Both of these classes have already been defined. 5 | 6 | // Implement the new constructor sumPair(a,b), which was declared already in class sumPair. The new constructor sumPair(a,b) should initialize the 7 | // inherited class Pair with integer values a,b and set the member variable "sum" to the sum of a and b. 8 | 9 | /* Class Pair has already been 10 | * declared and defined with the 11 | * following constructor: 12 | * 13 | * Pair(int,int) 14 | * 15 | * that stores its two arguments in 16 | * two private member variables of Pair. 17 | * 18 | * Class sumPair has also already been 19 | * defined as follows: 20 | * 21 | * class sumPair : public Pair { 22 | * public: 23 | * int sum; 24 | * sumPair(int,int); 25 | * }; 26 | * 27 | * Implement the constructor 28 | * sumPair(int,int) such that it 29 | * loads the two member variables of 30 | * the base Pair class with its 31 | * arguments, and initializes the 32 | * member variable sum with their sum. 33 | */ 34 | 35 | sumPair::sumPair(int a, int b) : sum(a+b), Pair(a, b) {} // here order of initialization list does not matter 36 | 37 | /* Below is a main() function 38 | * you can use to test your 39 | * implementation of the 40 | * sumPair constructor. 41 | */ 42 | 43 | int main() { 44 | sumPair sp(15,16); 45 | std::cout << "sp(15,16).sum =" << sp.sum << std::endl; 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /course2_week1_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Write functions that reverse the elements in a stack and in a queue. The starter code below include the STL and data structures. 2 | 3 | // A stack of integers is declared as "std::stack" and the stack's top() member function returns the integer at the top of the stack (but also 4 | // leaves it at the top of the stack). The push() method pushes a new integer onto the top of the stack and the pop() method deletes the value at the 5 | // top of the stack. 6 | 7 | // A queue of integers is declared as "std::queue" and the queue's front() member function returns the integer at the front of the queue (but also 8 | // leaves it at the front of the queue). The push() method pushes a new integer onto the back of the queue and the pop() method deletes the value at 9 | // the front of the queue. 10 | 11 | // Your job is to implement procedures that reverse the order of elements in a stack, and in a queue. The procedures print_stack() and print_queue() 12 | // are provided to help you see if your procedures work. 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | std::stack reverse_stack(std::stack s) { 19 | std::stack reversed_s; 20 | 21 | // write code here that returns a stack whose elements are 22 | // in reverse order from those in stack s 23 | while (!s.empty()) { 24 | reversed_s.push(s.top()); 25 | s.pop(); 26 | } 27 | 28 | return reversed_s; 29 | } 30 | 31 | std::queue reverse_queue(std::queue q) { 32 | std::queue reversed_q; 33 | 34 | // write code here that returns a queue whose elements are 35 | // in reverse order from those in queue q 36 | std::stack tmp; 37 | while (!q.empty()) { 38 | tmp.push(q.front()); 39 | q.pop(); 40 | } 41 | while (!tmp.empty()) { 42 | reversed_q.push(tmp.top()); 43 | tmp.pop(); 44 | } 45 | 46 | return reversed_q; 47 | } 48 | 49 | void print_stack(std::string name, std::stack s) { 50 | std::cout << "stack " << name << ": "; 51 | while (!s.empty()) { 52 | std::cout << s.top() << " "; 53 | s.pop(); 54 | } 55 | std::cout << std::endl; 56 | } 57 | 58 | void print_queue(std::string name, std::queue q) { 59 | std::cout << "queue " << name << ": "; 60 | while (!q.empty()) { 61 | std::cout << q.front() << " "; 62 | q.pop(); 63 | } 64 | std::cout << std::endl; 65 | } 66 | 67 | int main() { 68 | std::stack s, rs; 69 | std::queue q, rq; 70 | 71 | s.push(1); s.push(2); s.push(3); s.push(4); s.push(5); 72 | 73 | print_stack("s",s); 74 | 75 | rs = reverse_stack(s); 76 | 77 | print_stack("reversed s",rs); 78 | 79 | q.push(1); q.push(2); q.push(3); q.push(4); q.push(5); 80 | 81 | print_queue("q",q); 82 | 83 | rq = reverse_queue(q); 84 | 85 | print_queue("reversed q",rq); 86 | 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /course2_week2_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Your job is to implement the function "int count(Node *n)" that computes and returns the number of nodes in the binary tree that has n as its root. 2 | 3 | // When you write your code below, assume that a class type called "Node" has already been defined for you; you cannot change the class definition. 4 | // This class type has two child pointers ("left" , "right"). There is also a constructor Node() that initializes the children to nullptr and a 5 | // destructor that deallocates the subtree's memory recursively. 6 | 7 | /******************************************************** 8 | You may assume that the following Node class has already 9 | been defined for you previously: 10 | 11 | class Node { 12 | public: 13 | Node *left, *right; 14 | Node() { left = right = nullptr; } 15 | ~Node() { 16 | delete left; 17 | left = nullptr; 18 | delete right; 19 | right = nullptr; 20 | } 21 | }; 22 | 23 | You may also assume that iostream has already been included. 24 | 25 | Implement the function "int count(Node *n)" below to return 26 | an integer representing the number of nodes in the subtree 27 | of Node n (including Node n itself). 28 | 29 | *********************************************************/ 30 | #include 31 | using namespace std; 32 | 33 | int count(Node *n) { 34 | 35 | // Implement count() here. 36 | unsigned cnt = 0; 37 | queue q; 38 | q.push(n); 39 | 40 | while (!q.empty()) { 41 | Node* cur = q.front(); 42 | cnt += 1; 43 | if (cur->left != nullptr) { 44 | q.push(cur->left); 45 | } 46 | if (cur->right != nullptr) { 47 | q.push(cur->right); 48 | } 49 | q.pop(); 50 | } 51 | 52 | return cnt; 53 | } 54 | 55 | int main() { 56 | Node *n = new Node(); 57 | n->left = new Node(); 58 | n->right = new Node(); 59 | n->right->left = new Node(); 60 | n->right->right = new Node(); 61 | n->right->right->right = new Node(); 62 | 63 | // This should print a count of six nodes 64 | std::cout << count(n) << std::endl; 65 | 66 | // Deleting n is sufficient to delete the entire tree 67 | // because this will trigger the recursively-defined 68 | // destructor of the Node class. 69 | delete n; 70 | n = nullptr; 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /course2_week3_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Implement a function that computes the height of each node in a binary tree and stores it in each node of the tree. Recall that the height of a 2 | // node is the number of edges in its longest chain of descendants. 3 | 4 | // Consider a tree with root node A that has two child nodes B1 and B2. Let node B1 have no children. Let node B2 have one child C. Then the height 5 | // of A would be two, because its longest chain of descendants (A -> B2 -> C) has two edges (A -> B2 and B2 -> C). 6 | 7 | // The starter code below defines a class called "Node" that has two child pointers ("left" , "right") and an integer "height" member variable. 8 | // There is also a constructor Node() that initializes the children to nullptr and the height to -1. 9 | 10 | // Your job is to implement the procedure "computeHeight(Node *n)" that computes the height of the node n as well as the height of its children 11 | // (if any). 12 | 13 | // There is also a helper function "printTree(const Node *n)" that prints the current heights showing the tree as embedded parentheses. If a child 14 | // is nullptr, then it will appear as an empty pair of parentheses: "()". The constructor initializes the height to -1 even though a node with no 15 | // children should have a height of zero. If you see any -1 entries after you've run your computeHeight() procedure, you may have missed one or more 16 | // nodes. 17 | 18 | // The helper function printTreeVertical(const Node *n) is also available to you (although its complex definition is not shown). It displays a 19 | // verbose, vertical printout of your tree, where the root is shown at the top, and left children are shown on higher rows than right children. 20 | 21 | /* 22 | The height of a node is the number of edges in 23 | its longest chain of descendants. 24 | 25 | Implement computeHeight to compute the height 26 | of the subtree rooted at the node n. Note that 27 | this function does not return a value. You should 28 | store the calculated height in that node's own 29 | height member variable. Your function should also 30 | do the same for EVERY node in the subtree rooted 31 | at the current node. (This naturally lends itself 32 | to a recursive solution!) 33 | 34 | Assume that the following includes have already been 35 | provided. You should not need any other includes 36 | than these. 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | You have also the following class Node already defined. 44 | You cannot change this class definition, so it is 45 | shown here in a comment for your reference only: 46 | 47 | class Node { 48 | public: 49 | int height; // to be set by computeHeight() 50 | Node *left, *right; 51 | Node() { height = -1; left = right = nullptr; } 52 | ~Node() { 53 | delete left; 54 | left = nullptr; 55 | delete right; 56 | right = nullptr; 57 | } 58 | }; 59 | */ 60 | 61 | int computeNodeHeight(Node* n) { 62 | int lh, rh; 63 | if (n->left != nullptr) { 64 | lh = computeNodeHeight(n->left) + 1; 65 | } else { 66 | lh = 0; 67 | } 68 | if (n->right != nullptr) { 69 | rh = computeNodeHeight(n->right) + 1; 70 | } else { 71 | rh = 0; 72 | } 73 | int curHeight = std::max(lh, rh); 74 | n->height = curHeight; 75 | return curHeight; 76 | } 77 | 78 | void computeHeight(Node *n) { 79 | 80 | // Implement computeHeight() here. 81 | computeNodeHeight(n); 82 | } 83 | 84 | // This function prints the tree in a nested linear format. 85 | void printTree(const Node *n) { 86 | if (!n) return; 87 | std::cout << n->height << "("; 88 | printTree(n->left); 89 | std::cout << ")("; 90 | printTree(n->right); 91 | std::cout << ")"; 92 | } 93 | 94 | // The printTreeVertical function gives you a verbose, 95 | // vertical printout of the tree, where the leftmost nodes 96 | // are displayed highest. This function has already been 97 | // defined in some hidden code. 98 | // It has this function prototype: void printTreeVertical(const Node* n); 99 | 100 | // This main() function is for your personal testing with 101 | // the Run button. When you're ready, click Submit to have 102 | // your work tested and graded. 103 | int main() { 104 | Node *n = new Node(); 105 | n->left = new Node(); 106 | n->right = new Node(); 107 | n->right->left = new Node(); 108 | n->right->right = new Node(); 109 | n->right->right->right = new Node(); 110 | 111 | computeHeight(n); 112 | 113 | printTree(n); 114 | std::cout << std::endl << std::endl; 115 | printTreeVertical(n); 116 | 117 | // The Node destructor will recursively 118 | // delete its children nodes. 119 | delete n; 120 | n = nullptr; 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /course2_week4_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Implement downHeap(Node *n) for a min heap data structure, that is here implemented as a node-based binary tree with an integer member variable 2 | // "value" and left and right child pointers. (Unlike the video lesson which implemented downHeap on an array implementation of a complete binary tree, 3 | // the binary tree in this challenge problem is not stored as an array and is not necessarily complete; any node might have only a left child, only 4 | // a right child, both, or neither.) 5 | 6 | // The starter code below defines a class called "Node" that has two child pointers ("left" , "right") and an integer "value" member variable. There 7 | // is also a constructor Node(int val) that initializes the children to nullptr and the node's value to val. 8 | 9 | // Your job is to implement the procedure "downHeap(Node *n)" . The procedure should assume that n->left is the root of a min heap subtree (or nullptr) 10 | // and the same for n->right. The value at Node *n (specifically n->value) might not be less than the values in its left subtree and in its right 11 | // subtree, and so the tree with Node *n as its root might not be a min heap (even though its left subtree and right subtree are both min heaps). 12 | // Your code should modify the tree rooted at Node *n so it is a min heap. You do not need to balance the tree or turn it into a complete tree. 13 | // You only need to ensure that the tree satisfies the min heap property: 14 | 15 | // For a min heap, it is okay for a node's value to be equal to one or both of its children, but the node's value must not be greater than either of 16 | // its children. You should not perform swaps with nodes of equal value, as this does needless work. 17 | 18 | // Again, as is implied by the above description, for this exercise you may assume that only the root node violates the min heap property at the 19 | // beginning, if any node does (as the left and right subtrees are already valid heaps). This means it's possible to implement the downHeap function 20 | // as O(\log n)O(logn). If your algorithm has a running time worse than O(\log n)O(logn), it is probably incorrect. The significance of this 21 | // O(\log n)O(logn) algorithm is that it can be used as an efficient tool in the O(n)O(n)-time algorithm that corrects a new heap in multiple steps 22 | // from the bottom up, as described in lecture. 23 | 24 | /* 25 | 26 | Below, please implement the downHeap procedure for 27 | a min heap data structure, which we will represent 28 | as node-based tree for this exercise (not an array). 29 | 30 | Suppose you are given a tree where the left and right 31 | subtrees are min heaps, but the root node in particular 32 | might not maintain the min heap property. Your code 33 | should modify the tree rooted at Node* n so it is a 34 | min heap. This means you need to satisfy the min heap 35 | property: it is okay for a node's value to be equal to 36 | one or both of its children, but the node's value must 37 | not be greater than either of its children. 38 | 39 | Tips: 40 | Unlike the video lessons which demonstrated downHeap 41 | implemented with an array implementation, this assignment 42 | uses an ordinary binary tree with left and right child 43 | pointers. This ordinary binary tree might not be complete 44 | or balanced: any node might have only one child or the 45 | other, or both, or neither. (You do not have to try to 46 | balance the tree or turn it into a complete tree.) 47 | 48 | If the root node exists, and if it has a left or right 49 | child, and if one of the children has a smaller value 50 | than the root node, then you should swap the root node 51 | with the smaller child to move the large value down 52 | into the tree. (This may need to be done recursively.) 53 | Be careful to check whether pointers are null before 54 | dereferencing them, as always; that includes using "->" 55 | to access a class member through a pointer. In addition, 56 | you must be careful not to accidentally compare "left" 57 | and "right" pointers directly if you really intend to 58 | compare the stored values "left->value" and "right->value". 59 | 60 | Assume that these headers have already been included 61 | for you: 62 | 63 | #include 64 | #include 65 | 66 | You have the following class Node already defined. 67 | You cannot change this class definition, so it is 68 | shown here in a comment for your reference only: 69 | 70 | class Node { 71 | public: 72 | int value; 73 | Node *left, *right; 74 | Node(int val = 0) { value = val; left = right = nullptr; } 75 | ~Node() { 76 | delete left; 77 | left = nullptr; 78 | delete right; 79 | right = nullptr; 80 | } 81 | }; 82 | 83 | This function has also previously been defined for you: 84 | 85 | void printTreeVertical(const Node* n); 86 | 87 | You can use it to print a verbose, vertical diagram of 88 | a tree rooted at n. In this vertical format, a left child 89 | is shown above a right child in the same column. If no 90 | child exists, [null] is displayed. 91 | 92 | */ 93 | #include 94 | 95 | using namespace std; 96 | 97 | // void swap(Node *p, Node *c, bool isLeft) { 98 | // if (isLeft) { 99 | // Node* tmp = p->right; 100 | // p->left = c->left; 101 | // p->right = c->right; 102 | // c->left = p; 103 | // c->right = tmp; 104 | // } else { 105 | // Node* tmp = p->left; 106 | // p->left = c->left; 107 | // p->right = c->right; 108 | // c->right = p; 109 | // c->left = tmp; 110 | // } 111 | // } 112 | 113 | void swap(Node *p, Node *c) { 114 | int tmp = p->value; 115 | p->value = c->value; 116 | c->value = tmp; 117 | } 118 | 119 | void downHeap(Node *n) { 120 | 121 | // Implement downHeap() here. 122 | while ((n->left != nullptr) || (n->right != nullptr)) { 123 | if ((n->left != nullptr) && (n->right != nullptr)) { 124 | if ((n->value <= n->left->value) && (n->value <= n->right->value)) { 125 | return; 126 | } else if (n->left->value <= n->right->value) { 127 | swap(n, n->left); 128 | n = n->left; 129 | } else { 130 | swap(n, n->right); 131 | n = n->right; 132 | } 133 | } else if (n->left != nullptr) { 134 | if (n->value <= n->left->value) { 135 | return; 136 | } 137 | swap(n, n->left); 138 | n = n->left; 139 | } else { 140 | if (n->value <= n->right->value) { 141 | return; 142 | } 143 | swap(n, n->right); 144 | n = n->right; 145 | } 146 | } 147 | } 148 | 149 | // You can also use this compact printing function for debugging. 150 | void printTree(Node *n) { 151 | if (!n) return; 152 | std::cout << n->value << "("; 153 | printTree(n->left); 154 | std::cout << ")("; 155 | printTree(n->right); 156 | std::cout << ")"; 157 | } 158 | 159 | int main() { 160 | Node *n = new Node(100); 161 | n->left = new Node(1); 162 | n->right = new Node(2); 163 | n->right->left = new Node(3); 164 | n->right->right = new Node(4); 165 | n->right->right->right = new Node(5); 166 | 167 | cout << "original: " << &n << endl; 168 | cout << "original subject: " << n << endl; 169 | downHeap(n); 170 | 171 | std::cout << "Compact printout:" << std::endl; 172 | printTree(n); 173 | std::cout << std::endl << "Vertical printout:" << std::endl; 174 | printTreeVertical(n); 175 | 176 | delete n; 177 | n = nullptr; 178 | 179 | return 0; 180 | } 181 | -------------------------------------------------------------------------------- /course3_week1_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Suppose we have a simple hash table that consists of a vector of integers. We can preallocate the table to have a specific size and fill the values 2 | // with -1 to begin with, to mark those elements empty. For simplicity, this table will actually be a hash set rather than a hash map; in other words, 3 | // rather than mapping unique keys to values, we simply have a collection of unique keys, so it is not a dictionary ADT. Such a table merely answers 4 | // the question of whether any given integer is part of the set (can be found) or is not part of the set (cannot be found). 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int insert(int value, std::vector &table) { 13 | // Code to insert value into a hashed location in table 14 | // where table is a vector of length 1000. 15 | // Returns the number of collisions encountered when 16 | // trying to insert value into table. 17 | unsigned cnt = 0; 18 | int hashValue = 0; 19 | 20 | if (value < 1000) { 21 | hashValue = value; 22 | } 23 | else { 24 | int temp = value; 25 | int lsd1 = temp % 10; 26 | temp -= lsd1; 27 | temp /= 10; 28 | int lsd2 = temp % 10; 29 | temp -= lsd2; 30 | temp /= 10; 31 | int lsd3 = temp % 10; 32 | temp -= lsd3; 33 | temp /= 10; 34 | hashValue = lsd1 + (lsd2 * 10) + (lsd3 * 100); 35 | 36 | } 37 | 38 | while (table[hashValue] != -1) { 39 | hashValue != 999 ? hashValue++ : hashValue = 0; 40 | cnt++; 41 | } 42 | table[hashValue] = value; 43 | return cnt; 44 | } 45 | 46 | int main() { 47 | // Prepare some random but distinct values 48 | constexpr int NUM_VALUES = 500; 49 | std::vector value(NUM_VALUES); 50 | int prev_value = 0; 51 | for (int i = 0; i < NUM_VALUES; i++) { 52 | prev_value += rand()%25 + 1; // 1 to 25 53 | value[i] = prev_value; 54 | } 55 | 56 | // Create hash table of size 1000 initialized with -1 57 | std::vector table(1000,-1); 58 | 59 | // Insert values and track the maximum number of collisions 60 | int max_hit = 0, max_value = -1; 61 | for (int i = 0; i < NUM_VALUES; i++) { 62 | int hit = insert(value[i],table); 63 | if (hit > max_hit) { 64 | max_hit = hit; 65 | max_value = value[i]; 66 | } 67 | } 68 | 69 | std::cout << "Inserting value " << max_value << " experienced " << max_hit << " collisions." << std::endl < 27 | 28 | class DisjointSets { 29 | public: 30 | int s[256]; 31 | 32 | DisjointSets() { for (int i = 0; i < 256; i++) s[i] = -1; } 33 | 34 | int find(int i); 35 | }; 36 | 37 | /* Modify the find() method below to 38 | * implement path compression so that 39 | * element i and all of its ancestors 40 | * in the up-tree refer directly to the 41 | * root after that initial call to find() 42 | * returns. 43 | */ 44 | 45 | int DisjointSets::find(int i) { 46 | if ( s[i] < 0 ) { 47 | return i; 48 | } else { 49 | int root = find(s[i]); // return index 50 | // modify cur value 51 | s[i] = root; 52 | return root; 53 | } 54 | } 55 | 56 | int main() { 57 | DisjointSets d; 58 | 59 | d.s[1] = 3; 60 | d.s[3] = 5; 61 | d.s[5] = 7; 62 | d.s[7] = -1; 63 | 64 | std::cout << "d.find(3) = " << d.find(3) << std::endl; 65 | std::cout << "d.s(1) = " << d.s[1] << std::endl; 66 | std::cout << "d.s(3) = " << d.s[3] << std::endl; 67 | std::cout << "d.s(5) = " << d.s[5] << std::endl; 68 | std::cout << "d.s(7) = " << d.s[7] << std::endl; 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /course3_week3_challenge.cpp: -------------------------------------------------------------------------------- 1 | // Suppose you are given a undirected graph specified as a list of edges. In this challenge problem, we'll use a simplified disjoint sets data 2 | // structure to count how many "connected components" the graph has, and whether each one contains a cycle or not. 3 | 4 | #include 5 | #include 6 | 7 | // You are provided this version of a DisjointSets class. 8 | // See below for the tasks to complete. 9 | // (Please note: You may not edit the primary class definition here.) 10 | class DisjointSets { 11 | public: 12 | // We'll statically allocate space for at most 256 nodes. 13 | // (We could easily make this extensible by using STL containers 14 | // instead of static arrays.) 15 | static constexpr int MAX_NODES = 256; 16 | 17 | // For a given vertex of index i, leader[i] is -1 if that vertex "leads" 18 | // the set, and otherwise, leader[i] is the vertex index that refers back 19 | // to the eventual leader, recursively. (See the function "find_leader".) 20 | // In this problem we'll interpret sets to represent connected components, 21 | // once the sets have been unioned as much as possible. 22 | int leader[MAX_NODES]; 23 | 24 | // For a given vertex of index i, has_cycle[i] should be "true" if that 25 | // vertex is part of a connected component that has a cycle, and otherwise 26 | // "false". (However, this is only required to be accurate for a current 27 | // set leader, so that the function query_cycle can return the correct 28 | // value.) 29 | bool has_cycle[MAX_NODES]; 30 | 31 | // The number of components found. 32 | int num_components; 33 | 34 | DisjointSets() { 35 | // Initialize leaders to -1 36 | for (int i = 0; i < MAX_NODES; i++) leader[i] = -1; 37 | // Initialize cycle detection to false 38 | for (int i = 0; i < MAX_NODES; i++) has_cycle[i] = false; 39 | // The components will need to be counted. 40 | num_components = 0; 41 | } 42 | 43 | // If the leader for vertex i is set to -1, then report vertex i as its 44 | // own leader. Otherwise, keep looking for the leader recursively. 45 | int find_leader(int i) { 46 | if (leader[i] < 0) return i; 47 | else return find_leader(leader[i]); 48 | } 49 | 50 | // query_cycle(i) returns true if vertex i is part of a connected component 51 | // that has a cycle. Otherwise, it returns false. This relies on the 52 | // has_cycle array being maintained correctly for leader vertices. 53 | bool query_cycle(int i) { 54 | int root_i = find_leader(i); 55 | return has_cycle[root_i]; 56 | } 57 | 58 | // Please see the descriptions of the next two functions below. 59 | // (Do not edit these functions here; edit them below.) 60 | void dsunion(int i, int j); 61 | void count_comps(int n); 62 | }; 63 | 64 | // TASK 1: 65 | // dsunion performs disjoint set union. The reported leader of vertex j 66 | // will become the leader of vertex i as well. 67 | // Assuming it is only called once per pair of vertices i and j, 68 | // it can detect when a set is including an edge that completes a cycle. 69 | // This is evident when a vertex is assigned a leader that is the same 70 | // as the one it was already assigned previously. 71 | // Also, if you join two sets where either set already was known to 72 | // have a cycle, then the joined set still has a cycle. 73 | // Modify the implementation of dsunion below to properly adjust the 74 | // has_cycle array so that query_cycle(root_j) accurately reports 75 | // whether the connected component of root_j contains a cycle. 76 | void DisjointSets::dsunion(int i, int j) { 77 | bool i_had_cycle = query_cycle(i); 78 | bool j_had_cycle = query_cycle(j); 79 | int root_i = find_leader(i); 80 | int root_j = find_leader(j); 81 | if (root_i != root_j) { 82 | leader[root_i] = root_j; 83 | root_i = root_j; 84 | } 85 | else { 86 | // A cycle is detected when dsunion is performed on an edge 87 | // where both vertices already report the same set leader. 88 | // TODO: Your work here! Update has_cycle accordingly. 89 | has_cycle[root_i] = true; 90 | } 91 | 92 | // Also, if either one of the original sets was known to have a cycle 93 | // already, then the newly joined set still has a cycle. 94 | // TODO: Your work here! 95 | bool is_cycle = i_had_cycle || j_had_cycle || has_cycle[root_i]; 96 | has_cycle[root_i] = is_cycle; 97 | } 98 | 99 | // TASK 2: 100 | // count_comps should count how many connected components there are in 101 | // the graph, and it should set the num_components member variable 102 | // to that value. The input n is the number of vertices in the graph. 103 | // (Remember, the vertices are numbered with indices 0 through n-1.) 104 | void DisjointSets::count_comps(int n) { 105 | 106 | // Insert code here to count the number of connected components 107 | // and store it in the "num_components" member variable. 108 | // Hint: If you've already performed set union on all the apparent edges, 109 | // what information can you get from the leaders now? 110 | 111 | // TODO: Your work here! 112 | std::unordered_set s; 113 | for (int i = 0; i < MAX_NODES; i++) { 114 | if (leader[i] > -1) { 115 | int root = find_leader(i); 116 | s.insert(root); 117 | } 118 | } 119 | num_components = s.size(); 120 | } 121 | 122 | int main() { 123 | 124 | constexpr int NUM_EDGES = 9; 125 | constexpr int NUM_VERTS = 8; 126 | 127 | int edges[NUM_EDGES][2] = {{0,1},{1,2},{3,4},{4,5},{5,6},{6,7},{7,3},{3,5},{4,6}}; 128 | 129 | DisjointSets d; 130 | 131 | // The union operations below should also maintain information 132 | // about whether leaders are part of connected components that 133 | // contain cycles. (See TASK 1 above where dsunion is defined.) 134 | for (int i = 0; i < NUM_EDGES; i++) 135 | d.dsunion(edges[i][0],edges[i][1]); 136 | 137 | // The count_comps call below should count the number of components. 138 | // (See TASK 2 above where count_comps is defined.) 139 | d.count_comps(NUM_VERTS); 140 | 141 | std::cout << "For edge list: "; 142 | for (int i = 0; i < NUM_EDGES; i++) { 143 | std::cout << "(" << edges[i][0] << "," 144 | << edges[i][1] << ")" 145 | // This avoids displaying a comma at the end of the list. 146 | << ((i < NUM_EDGES-1) ? "," : "\n"); 147 | } 148 | 149 | std::cout << "You counted num_components: " << d.num_components << std::endl; 150 | 151 | // The output for the above set of edges should be: 152 | // You counted num_components: 2 153 | 154 | std::cout << "Cycle reported for these vertices (if any):" << std::endl; 155 | for (int i=0; i 28 | #include 29 | 30 | // Note: You must not change the definition of DisjointSets here. 31 | class DisjointSets { 32 | public: 33 | int s[256]; 34 | int distance[256]; 35 | 36 | DisjointSets() { 37 | for (int i = 0; i < 256; i++) s[i] = distance[i] = -1; 38 | } 39 | 40 | int find(int i) { return s[i] < 0 ? i : find(s[i]); } 41 | 42 | void dsunion(int i, int j) { 43 | int root_i = find(i); 44 | int root_j = find(j); 45 | if (root_i != root_j) { 46 | s[root_i] = root_j; 47 | } 48 | } 49 | 50 | void bfs(int i, int n, int m, int edges[][2]); 51 | }; 52 | 53 | 54 | /* Below are two conditions that need to be programmed 55 | * to allow this procedure to perform a breadth first 56 | * traversal and mark the edge distance of the graph's 57 | * vertices from vertex i. 58 | */ 59 | 60 | void DisjointSets::bfs(int i, int n, int m, int edges[][2]) { 61 | 62 | distance[i] = 0; 63 | 64 | // no need to iterate more than m times 65 | // but loop terminates when no new 66 | // vertices added to the frontier. 67 | 68 | for (int d = 1; d < m; d++) { 69 | 70 | // f is the index of the first 71 | // vertex added to the frontier 72 | int f = -1; 73 | 74 | // rooti is the name of the set 75 | // holding all of the vertices 76 | // that have already been assigned 77 | // distances 78 | 79 | int rooti = find(i); 80 | 81 | // loop through all of the edges 82 | // (this could be much more efficient 83 | // if an adjacency list was used 84 | // instead of a simple edge list) 85 | 86 | for (int j = 0; j < m; j++) { 87 | 88 | // root0 and root1 are the names of 89 | // the sets that the edge's two 90 | // vertices belong to 91 | 92 | int root0 = find(edges[j][0]); 93 | int root1 = find(edges[j][1]); 94 | 95 | if ( root0 == rooti && root1 != rooti) { 96 | 97 | // add the [1] vertex of the edge 98 | // to the frontier, either by 99 | // setting f to that vertex if it 100 | // is the first frontier vertex 101 | // found so far, or by unioning 102 | // it with the f vertex that was 103 | // already found. 104 | 105 | if (f == -1) 106 | f = edges[j][1]; 107 | else 108 | dsunion(f,edges[j][1]); 109 | 110 | // set the distance of this frontier 111 | // vertex to d 112 | 113 | distance[edges[j][1]] = d; 114 | 115 | } else if ( root1 == i && root0 != rooti) { 116 | if (f == -1) 117 | f = edges[j][0]; 118 | else 119 | dsunion(f,edges[j][0]); 120 | distance[edges[j][0]] = d; 121 | } 122 | } 123 | 124 | // if no vertices added to the frontier 125 | // then we have run out of vertices and 126 | // are done, otherwise union the frontier 127 | // set with the set of vertices that have 128 | // already been processed. 129 | 130 | if (f == -1) 131 | break; 132 | else 133 | dsunion(f,i); 134 | } 135 | } 136 | 137 | int main() { 138 | 139 | int edges[8][2] = {{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,3}}; 140 | 141 | DisjointSets d; 142 | 143 | d.bfs(3,8,8,edges); 144 | 145 | for (int i = 0; i < 8; i++) 146 | std::cout << "Distance to vertex " << i 147 | << " is " << d.distance[i] << std::endl; 148 | 149 | // Should print 150 | // Distance to vertex 0 is 3 151 | // Distance to vertex 1 is 2 152 | // Distance to vertex 2 is 1 153 | // Distance to vertex 3 is 0 154 | // Distance to vertex 4 is 1 155 | // Distance to vertex 5 is 2 156 | // Distance to vertex 6 is 2 157 | // Distance to vertex 7 is 1 158 | 159 | 160 | return 0; 161 | } 162 | --------------------------------------------------------------------------------