ECS 110: Data Structures and Programming Discussion Section Notes -- Week 5 Ted Krovetz (tdkrovetz@ucdavis.edu) ALGORITHMS FROM THE BOOK ------------------------ This week we will discuss the content of sections 4.7 and 4.10 from the "Data Structures in C++" book. 1. EQUIVALENCE CLASSES Section 4.7 demonstrates the use of lists in determining equivalence classes from a set of equivalence statements. We will go over the algorithms involved to demonstrate that lists are useful for more than just stacks and queues. The information is clearly presented in the book, so I will not write notes for this part of section. 2. LISTS In class last week, Professor Rogaway discussed briefly generalized lists. A generalized list is a list where each node is either atomic or another list. Section 4.10 presents a representation of a generalized list and then presents several recursive functions which visit each node of every list. In discussion section, I will present the same material, but with slightly different implementations. 2.1 Representation In our representation, each node will keep track of its own list status. If a node is atomic, then the atom data member should be set true and the atomic data should be stored in the mem_data data member. Otherwise, the node represents a list and the mem_list data member should point to a list. next points to the next element in the current list. Thus, we could use the following data structure to represent a list_node: struct list_node { int atomic; // Is this node atomic? int mem_data; // If we're atomic data goes here list_node* mem_list; // If we're not, point to a list list_node* next; // Next element in this list }; typedef list_node* list_node_ptr; 2.2 Copying a Simple Linked-List As we saw in class, a list is recursively defined. Each element in a list can itself be a list. Quite often, the most effective way to define a function on a recursive structure is to write it recursively. But first, let's write a copy function that would work on a non-recursive linked list. In other words, let's write a copy function for the above data-structure but without taking into account nested lists. Our data structure for such a list might look like this: struct list_node { int mem_data; // Atomic data goes here list_node* next; // Next element in this list }; And our copy function might look like this (Let's use a header node to make things easier): list_node* Copy (list_node* h) { list_node* t; list_node* head; t = head = new list_node; // lets use header nodes *t = *h; while (h->next != NULL) { t->next = new list_node; *(t->next) = *(h->next); t = t->next; h = h->next; } return head; } 2.2 Copying a Recursively Linked-List How might our function change if we now consider recursively defined linked lists? Well, things would get much easier if we already had a Copy function defined for recursively linked lists. Let's write out function as if we already had a Copy function defined: list_node* Copy (list_node* h) { list_node* t; list_node* head; t = head = new list_node; // lets use header nodes *t = *h; while (h->next != NULL) { t->next = new list_node; *(t->next) = *(h->next); if ( ! (h->next)->atomic) (t->next)->mem_list = Copy((h->next)->mem_list); t = t->next; h = h->next; } return head; } Often times that is all there is to using recursion to solve your problems. By assuming that we already have a routine written that does what we want on a recursive structure, we can complete the seemingly complex task with just a few lines of code. 2.3 More examples Section 4.10 has several more examples, and given time, we will examine each.