* What is a linked list of integers? * Can you come up with a recursive definition? - NULL is a linked list - if N is a list and I is an integer, then Node(I, N) is a list * Is the definition above complete? * Can you define the Node that constitutes the linked list? struct Node { Node(const int dd, const Node* nn): next(nn), data(dd) {} const int data; const Node* next; }; * Why is everything marked as 'const'? * What are the advantages of using 'const'? * Can you define a function 'create' that converts the contents of a file into a linked list? #include #include Node* create(char* fileName) { std::ifstream infile(fileName); if (!infile.is_open()) { std::cerr << "Failed to open " << fileName << std::endl; return NULL; } int data = 0; Node* head = NULL; while (infile >> data) { head = new Node(data, head); } return head; } * Can you add a declaration of this function to our header file, with comments? /** * This method reads all the integers in a file, and returns a linked list * containing these integers. The last integer read will be the head of the * linked list. * @param fileName the name of the file that will be read. * @return the head of the list newly created. */ Node* create(char* fileName); * Can you create a function 'traverse' that goes over the linked list, printing every element of it? void traverse(const Node *head) { if (head) { std::cout << head->data << std::endl; traverse(head->next); } } * Can you add a declaration (with comments) of this function? /** * This function goes over the list and prints every element that it contains. * @param head the first element of the list. */ void traverse(const Node* head); * Can you write a simple test case for this function? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { Node *head = create(argv[1]); traverse(head); return 0; } } * Can you write a function that traverses the list, and increments every element of it, storing these incremented elements in a new list? Node* incAll(const Node *head) { if (head) { return new Node(head->data + 1, incAll(head->next)); } else { return NULL; } } * Can you write it in one line of code? Node* incAll(const Node *head) { return head ? new Node(head->data + 1, incAll(head->next)) : NULL; } * Do you understand how the ternary operator (x?a:b) works? * Can you test it? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { Node *head = create(argv[1]); traverse(head); std::cout << "-----------------------\n"; Node *incHead = incAll(head); traverse(incHead); return 0; } } * How many lists do we have after we call incAll? * Can you write a function to report the size of a list? int size(const Node *head) { return head ? 1 + size(head->next) : 0; } * What is the complexity of this function? * Can you add a commented declaration in the header file? /** * This function reports the size of a linked list. It traverses the list to * count the number of its elements. * @param head the head of the linked list. * @return the size of the list. */ int size(const Node *head); * Can you test it? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { Node *head = create(argv[1]); traverse(head); std::cout << "-----------------------\n"; Node *incHead = incAll(head); traverse(incHead); std::cout << "Size of head = " << size(head) << std::endl; std::cout << "Size of incHead = " << size(incHead) << std::endl; return 0; } } * Can you write a function to delete every node of the list? void delList(const Node* head) { if (head) { delList(head->next); delete head; } } * Would it be correct to switch the order of the two statements within the conditional, like in the code below? E.g.: void delList(const Node* head) { if (head) { delete head; delList(head->next); } } * Can you write a function to sum up every element of the list? int sum(const Node *head) { return head ? head->data + sum(head->next) : 0; } * Can you write a function to split a list into two sublists? These sublists must be balanced, that is, the difference in size must be at most 1. void split(const Node* head, const Node** l1, const Node** l2) { if (head && head->next) { const Node* aux1; const Node* aux2; split(head->next->next, &aux1, &aux2); *l1 = new Node(head->data, aux1); *l2 = new Node(head->next->data, aux2); } else if (head) { *l1 = new Node(head->data, NULL); *l2 = NULL; } else { *l1 = NULL; *l2 = NULL; } } * Can you write code to test this function? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { Node *head = create(argv[1]); traverse(head); std::cout << "-----------------------\n"; const Node *l1; const Node *l2; split(head, &l1, &l2); traverse(l1); std::cout << "-----------------------\n"; traverse(l2); return 0; } } * Can you write code to merge two sorted linked lists? const Node* merge(const Node* l1, const Node* l2) { if (!l1) { return l2; } else if (!l2) { return l1; } else if (l1->data <= l2->data) { return new Node(l1->data, merge(l1->next, l2)); } else { return new Node(l2->data, merge(l1, l2->next)); } } * Can you sort the lists using an algorithm called mergesort? * Has anyone ever heard about mergesort? const Node* mergeSort(const Node* l) { if (!l) { return NULL; } else if (!l->next) { return l; } else { const Node *l1; const Node *l2; split(l, &l1, &l2); return merge(mergeSort(l1), mergeSort(l2)); } } * Can you write code to test this program? Try to generate a random list. int main(int argc, char** argv) { if (argc != 3) { std::cerr << "Syntax: cmd \n"; return 1; } else { const int num_ints = atoi(argv[1]); const int largest_int = atoi(argv[2]); Node *l = NULL; for (int i = 0; i < num_ints; i++) { l = new Node(rand() % largest_int, l); } traverse(l); std::cout << "-----------------------\n"; const Node *ls = mergeSort(l); traverse(ls); return 0; } } * Can you implement a tree using the same ideas seen in the list? * What is a tree? How can you define it? - NULL is a linked list - if T1 and T2 are trees and I is an integer, then Tree(I, T1, T2) is a tree struct TreeNode { TreeNode(const int dd, const TreeNode* ll, const TreeNode *rr): left(ll), right(rr), data(dd) {} const int data; const TreeNode* left; const TreeNode* right; }; * Can you create a function to insert elements in a tree? This function should ensure that all the elements at the left of the root are less than all the elements at the right of the root. const TreeNode* insert(const TreeNode* root, const int data) { if (root) { if (data < root->data) { return new TreeNode(root->data, insert(root->left, data), root->right); } else if (data > root->data) { return new TreeNode(root->data, root->left, insert(root->right, data)); } else { return root; } } else { return new TreeNode(data, NULL, NULL); } } * Can you create a documented interface for this method? /** * This function inserts a new element into the tree rooted at 'root'. Insertion * preserves the search property. That is to say, elements less than the root * data are inserted on the left of the root, and elements greater than the root * data are inserted on the right of the root. * @param root the root of the tree where the element will be inserted. * @param data the data that is about to be inserted into the tree. */ const TreeNode* insert(const TreeNode* root, const int data); * What do we need to test this method? * We need to have a way to traverse and print the elements in the tree. Can you write such a method? void inOrder(const TreeNode* root) { if (root) { inOrder(root->left); std::cout << root->data << std::endl; inOrder(root->right); } } * If we assume that elements on the left are less than the root data, and elements to the right are greater, can you prove that this method prints all the elements in the tree in sorted order? * Can you create a documented interface for this method? /** * This method traverses the tree in-order. This traversal ordering ensures * that elements on the left branch are visited before the root, and elements * on the right branch are visited after the node. * @param root the root of the tree that is about to be traversed. */ void inOrder(const TreeNode* root); * Can you write a method that creates a tree out of numbers in a file? const TreeNode* create(char* fileName) { std::ifstream infile(fileName); if (!infile.is_open()) { std::cerr << "Failed to open " << fileName << std::endl; return NULL; } int data = 0; const TreeNode* root = NULL; while (infile >> data) { root = insert(root, data); } return root; } * Can you write a harness to test 'create' and 'inOrder'? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { const TreeNode *root = create(argv[1]); inOrder(root); return 0; } } * Again, why are the elements printed always in sorted order? * Can you write a function to give you the size of a tree? int size(const TreeNode* root) { return root ? 1 + size(root->left) + size(root->right) : 0; } * Write a driver to test it: int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { const TreeNode *root = create(argv[1]); inOrder(root); std::cout << "Size = " << size(root) << std::endl; return 0; } } * How can we define the height of a tree? - The length of the longest path from root to some leaf. * Can you write a function to compute the height of a tree? int height(const TreeNode* root) { if (root) { const int leftHeight = height(root->left); const int rightHeight = height(root->right); const int maxHeight = leftHeight > rightHeight ? leftHeight : rightHeight; return 1 + maxHeight; } else { return 0; } return root ? 1 + size(root->left) + size(root->right) : 0; } * Can you write code to test this program? int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { const TreeNode *root = create(argv[1]); inOrder(root); std::cout << "Size = " << size(root) << std::endl; std::cout << "Height = " << height(root) << std::endl; return 0; } } * We could use both techniques to sort numbers: a linked list, or a tree. Which method is better? #include #include "tree.h" #include "list.h" int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Syntax: cmd \n"; return 1; } else { const int num_ints = atoi(argv[1]); const int largest_int = atoi(argv[2]); const char mode = argv[3][0]; if (mode == 'l') { // Test the list: const Node *l = NULL; for (int i = 0; i < num_ints; i++) { l = new Node(rand() % largest_int, l); } const Node *ls = mergeSort(l); } else { // Test the tree: const TreeNode *t = NULL; for (int i = 0; i < num_ints; i++) { t = insert(t, rand() % largest_int); } } return 0; } } $ time ./a.out 60000 10000 l real 0m0.179s user 0m0.161s sys 0m0.013s $ time ./a.out 60000 10000 t real 0m0.108s user 0m0.094s sys 0m0.011s * Why is the tree based technique so much faster? * Is the tree based technique always faster? // Not really. Imagine that we insert the elements already in order: #include #include "tree.h" #include "list.h" int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Syntax: cmd \n"; return 1; } else { const int num_ints = atoi(argv[1]); const int largest_int = atoi(argv[2]); const char mode = argv[3][0]; if (mode == 'l') { // Test the list: const Node *l = NULL; for (int i = 0; i < num_ints; i++) { l = new Node(i, l); } const Node *ls = mergeSort(l); } else { // Test the tree: const TreeNode *t = NULL; for (int i = 0; i < num_ints; i++) { t = insert(t, i); } } return 0; } } * How would be the relative runtime in this case? $ time ./a.out 10000 10000 t real 0m5.334s user 0m4.686s sys 0m0.634s $ time ./a.out 10000 10000 l real 0m0.048s user 0m0.028s sys 0m0.007s * Why does the tree performs so badly when the tree is already sorted?