ECS 110: Data Structures and Programming Discussion Section Notes -- Week 3 Ted Krovetz (tdkrovetz@ucdavis.edu) LAST OF THE C++ --------------- 1. CLASS TEMPLATES Last week we used a typedef to isolate the type of data manipulated by our stack. By defining in a single place the type of data our stack contains, it is possible to adapt easily our stack for different uses. Simply changing the typedef and recompiling produces an entirely new type of stack. What would happen if you wanted to have two stacks containing different types? In our old model, we would have to duplicate the code for each type. However, C++ lets you write classes for generic types, allowing you to use the same class definition for instantiations of multiple data types. That is, you write your class for some place-holder type T, and then each time you need your class to work on a different data type, you simply inform the compiler to substitute your data type for the generic T. This is sometimes called "parametric polymorphism" because you can cause a type definition to change behaviour by use of a parameter upon instantiation. Let's look at the Stack example of last week: #include template class Stack { public: Stack(); ~Stack(); void Push(TYPE); void Pop(); TYPE Top(); int Empty(); friend ostream& operator<< (ostream& os, Stack& s); private: struct node_type { TYPE elem; node_type* prev; }; node_type* top; }; template Stack::Stack() { top = NULL; } template Stack::~Stack() { while ( ! Empty()) Pop(); } template void Stack::Push(TYPE new_value) { node_type* temp = new node_type; temp->prev = top; temp->elem = new_value; top = temp; } template void Stack::Pop() { if ( ! Empty()) { node_type* temp = top->prev; delete top; top = temp; } } template TYPE Stack::Top() { return (top->elem); } template int Stack::Empty() { return (top == NULL); } template ostream& operator<< (ostream& os, Stack& s) { if (s.Empty()) os << "The stack is empty"; else { Stack::node_type* temp = s.top; while (temp != NULL) { os << temp->elem << " "; temp = temp->prev; } } os << endl; return os; } int main() { Stack s; int num; cout << "How many would you like to push? "; cin >> num; for (int i = 1; i <= num; i++) { s.Push(i); cout << s; } while ( ! s.Empty()) { cout << "The top of the stack is " << s.Top() << endl; s.Pop(); } cout << s; return 0; } This program behaves identically to last week's example. The only coding changes need to accommodate the parameterization is to change all references from elem_type to TYPE and change all type references from Stack to Stack. You will not be required to use templates in your programs. Many compilers don't support them properly. This brief introduction is being provided so that you can gain a reading knowledge of template use. Your textbook, "Fundamentals of Data Structures in C++," uses templates often, and misunderstanding their use could easily get in the way of understanding parts of the book. 2. DICTIONARIES Sometimes it is useful to store information somewhere and retrieve it at a later time. A data structure that supports the ADD, REMOVE, and LOOKUP of data is called a dictionary. A dictionary might have a public interface something like this. template class Dictionary { public: void Add(const T&); void Remove(const T&); int Lookup(const T&); private: // What goes here? }; What implementations are possible for this ADT? What considerations are important in selecting the implementation? In section we will discuss the pros and cons of different implementations: Arrays, sorted arrays, linked lists, sorted linked lists, binary trees, balanced binary trees, and skip-lists. (And any other implementations you can think of.) So, if you're reading this before section, think about why certain ways of organizing data might be better for certain tasks. 3. SKIP-LISTS One particularly clever implementation for a dictionary involves the use of skip-lists. A skip-list is a linked list of sorted data. Each node in the list contains the data plus _at_least_ one forward pointer. This forward pointer points to the next node in the list. Additionally, each node has zero or more forward pointers which point more than one node forward in the list. Because of the nature of these extra pointers, searching can be made to be very fast. O(log N) fast. A graphic example will be given in section. Article: 284 of ucd.class.ecs110 Path: news.ucdavis.edu!NewsWatcher!user From: tdkrovetz@ucdavis.edu (Ted Krovetz) Newsgroups: ucd.class.ecs110 Subject: Section 3 Lecture Notes Date: Sun, 15 Oct 1995 23:06:29 -0700 Organization: None Lines: 186 Message-ID: NNTP-Posting-Host: dcn79.dcn.davis.ca.us X-Newsreader: Value-Added NewsWatcher 2.0b27.1+ ECS 110: Data Structures and Programming Discussion Section Notes -- Week 3 Ted Krovetz (tdkrovetz@ucdavis.edu) LAST OF THE C++ --------------- 1. CLASS TEMPLATES Last week we used a typedef to isolate the type of data manipulated by our stack. By defining in a single place the type of data our stack contains, it is possible to adapt easily our stack for different uses. Simply changing the typedef and recompiling produces an entirely new type of stack. What would happen if you wanted to have two stacks containing different types? In our old model, we would have to duplicate the code for each type. However, C++ lets you write classes for generic types, allowing you to use the same class definition for instantiations of multiple data types. That is, you write your class for some place-holder type T, and then each time you need your class to work on a different data type, you simply inform the compiler to substitute your data type for the generic T. This is sometimes called "parametric polymorphism" because you can cause a type definition to change behaviour by use of a parameter upon instantiation. Let's look at the Stack example of last week: #include template class Stack { public: Stack(); ~Stack(); void Push(TYPE); void Pop(); TYPE Top(); int Empty(); friend ostream& operator<< (ostream& os, Stack& s); private: struct node_type { TYPE elem; node_type* prev; }; node_type* top; }; template Stack::Stack() { top = NULL; } template Stack::~Stack() { while ( ! Empty()) Pop(); } template void Stack::Push(TYPE new_value) { node_type* temp = new node_type; temp->prev = top; temp->elem = new_value; top = temp; } template void Stack::Pop() { if ( ! Empty()) { node_type* temp = top->prev; delete top; top = temp; } } template TYPE Stack::Top() { return (top->elem); } template int Stack::Empty() { return (top == NULL); } template ostream& operator<< (ostream& os, Stack& s) { if (s.Empty()) os << "The stack is empty"; else { Stack::node_type* temp = s.top; while (temp != NULL) { os << temp->elem << " "; temp = temp->prev; } } os << endl; return os; } int main() { Stack s; int num; cout << "How many would you like to push? "; cin >> num; for (int i = 1; i <= num; i++) { s.Push(i); cout << s; } while ( ! s.Empty()) { cout << "The top of the stack is " << s.Top() << endl; s.Pop(); } cout << s; return 0; } This program behaves identically to last week's example. The only coding changes need to accommodate the parameterization is to change all references from elem_type to TYPE and change all type references from Stack to Stack. You will not be required to use templates in your programs. Many compilers don't support them properly. This brief introduction is being provided so that you can gain a reading knowledge of template use. Your textbook, "Fundamentals of Data Structures in C++," uses templates often, and misunderstanding their use could easily get in the way of understanding parts of the book. 2. DICTIONARIES Sometimes it is useful to store information somewhere and retrieve it at a later time. A data structure that supports the ADD, REMOVE, and LOOKUP of data is called a dictionary. A dictionary might have a public interface something like this. template class Dictionary { public: void Add(const T&); void Remove(const T&); int Lookup(const T&); private: // What goes here? }; What implementations are possible for this ADT? What considerations are important in selecting the implementation? In section we will discuss the pros and cons of different implementations: Arrays, sorted arrays, linked lists, sorted linked lists, binary trees, balanced binary trees, and skip-lists. (And any other implementations you can think of.) So, if you're reading this before section, think about why certain ways of organizing data might be better for certain tasks. 3. SKIP-LISTS One particularly clever implementation for a dictionary involves the use of skip-lists. A skip-list is a linked list of sorted data. Each node in the list contains the data plus _at_least_ one forward pointer. This forward pointer points to the next node in the list. Additionally, each node has zero or more forward pointers which point more than one node forward in the list. Because of the nature of these extra pointers, searching can be made to be very fast. O(log N) fast. A graphic example will be given in section.