470 likes | 572 Views
Linked List Containers. Linked List Example. Polynomials Interested in representing and manipulating polynomials Polynomial defined as: Y = coef_n * x exp_n + coef_n-1 * x exp_n-1 + … coef_0 * x 0 Examples: 3x 14 + 2x 8 + 1 8x 14 – 3x 10 + 10x 6. Polynomials.
E N D
Linked List Example • Polynomials • Interested in representing and manipulating polynomials • Polynomial defined as: • Y = coef_n * xexp_n +coef_n-1 * xexp_n-1 + … coef_0 * x0 • Examples: 3x14 + 2x8 + 1 8x14 – 3x10 + 10x6
Polynomials • For each component of the polynomial, need to store coefficient and exponent class Term { public: void Term(int c, int e) { coef = c; exp = e;} private: int coef; int exp; };
Polynomials • Polynomial itself implemented by a templated LinkedList of Terms class Polynomial { private: LinkedList<Term> poly; };
Polynomials • Adding Polynomials: 3x3 + 2x + 1 + 2x3 + 3x2 + 5 ================= 5x3 + 3x2 + 2x + 6
Polynomials • Adding Polynomials: • Iterate through both lists • If exponent1 == exponent2, • CoefficientSum = Coefficient1 + Coefficient2 • If CoefficentSum != 0, add term to new polynomial representing answer • Else, • Find higher exponent • Add term to new polynomial representing answer
3 3 2 1 1 0 2 3 3 2 5 0 5 3 3 2 2 1 6 0 Polynomials
Polynomials • The “Answer” polynomial is where LinkedLists have another win. There is now way beforehand to know how may terms there will be in the answer polynomial • Number of terms is anywhere from (the size of the largest list) to (the size of the largest list plus the size of the smallest list) • With an array representation, would have to overallocate to handle large answers • With LinkedLists, just grab a new Node when need it.
Circular Lists • Another improvement on LinkedLists: • Use link pointer of last node to point to the first node. • Changes to implementation: • Check to see if at last node: test “if (current -> link == first)” instead of “if (current->link == 0)” • Insertion and deletion need to preserve property that last nodes link is always set equal to first
Circular List Implementation Retrofitting our LinkedList to make circular: • Don’t want to have just a head pointer: Why? • If inserting at tail, have to traverse the whole list from head to get to tail to update the tails link • If inserting at head, need to find last node to point its link to new head - this also traverses the whole list • Circular linked lists are much more efficient if use tail (last) pointer as the pointer for the list.
Circular Linked Lists head (AKA first) tail List1 Data1 List1 Data2 List1 Data3
Circular Linked List void insertAtFront(Type data) { LinkedListNode* toAdd = new LinkedListNode(data); if (tail == 0) { // empty list, tail = toAdd; toAdd-> next = toAdd; // point to yourself } else { toAdd->next = tail->next; // point new head next to old head tail->next = toAdd; // point tail to new head } } insertAtRear() only requires adding tail = toAdd in the else statement (to update rear to new node)
Doubly Linked Lists • Biggest problem with linked lists as we’ve used so far: • Can only navigate in one direction • Requires traversals from front to a position, even if currently located right behind where want to be • Requires “trailing pointer” if want to easily get access to previous node for current node • When update current to current->next, update prev to current
Doubly Linked Lists • Work around these issues by storing both the left and right side neighbors of a linked list • Makes add, delete, and other array manipulation operators more complicated as have to preserve doubly linked property
Doubly Linked Lists • Definition: class DblListNode { friend class DblList; private: int data; DblListNode *right, *left; };
Doubly Linked Lists class DblList { public: // list manipulation private: DblListNode* head; // DblListNode* tail; // maybe use this if circular? };
LEFT LEFT LEFT LEFT DATA 10 15 29 RIGHT RIGHT RIGHT RIGHT Doubly Linked Lists Example Node 3 Node Circular Doubly Linked List
Doubly Linked List • Note that, given a pointer p, p = p->left->right = p->right->left • Going back and forth is equally easy.
L L L 10 12 11 R R Doubly Linked List void DblList::Insert(DblListNode *new, DblListNode *current) { // insert new after x new->left = current; new->right = current->right; current->right->left = new; current->right = new; } new current R
L L L 10 12 11 R R Doubly Linked List void DblList::Delete(DblListNode *toDelete) { // delete node pointed to by toDelete toDelete->left->right = toDelete->right; toDelete->right->left = toDelete->left; delete toDelete; } toDelete
Generalized Lists • Current implementation of lists: • Finite sequence of zero or more atomic elements A = (a0, a1, a2 …. an-1) • Elements of list are restricted to atoms: • Individual pieces of information • An integer, A Rectangle • Only structural property of a list is position • Given position, can easily access data • Very much like arrays
Generalized Lists • Relax the assumption of atomic elements: • Lists can now be composed of atoms or lists • Definition: A generalized list A is a finite sequence of zero or more elements a0, a1, …, an-1 where ai is either an atom or a list. Elements that are not atomic are called sublists of A.
Generalized Lists • Conventions for Generalized Lists: • List names are represented in capital letters • Atom names are lowercase letters • Definitions for Generalized Lists: Length – Number of elements (atoms or sublists) in the list If length > 0 Head – First element of the list Tail – List composed of all elements except first of list
Generalized Lists • Examples of Generalized Lists: • D = ( ) Length 0, Null List • A = (a, (b,c)) Length 2 Head = a Tail = ((b,c)) • B = (A, A, ()) Length 3 Head = A Tail = (A,()) • C = (a, C) Length 2 Head = a Tail = (C)
Generalized Lists • 2 Tricky Things Generalized Lists Allow: • Sharing of lists among lists • B = (A, A, ( )) • Just like having the number 1 held in the list twice – how do we represent efficiently and “safely”? • Recursive definitions of lists • C = (a,C) actually represents the infinite list (a, (a, (a, (a, (a, (a, (a, (a, … )))))))) • May need to concern ourselves with these when implementing generalized list operations
Generalized Lists • Need a new underlying representation: • A node in the list needs to be able to contain either an atomic piece of information or point to another list Tag = TRUE/FALSE Data/DLink Link (Next)
Generalized Lists • Tag = represents type of node • Tag = false (0) means holds atomic data • Tag = true (1) means holds list data • Data • If Tag = false, hold real data • If Tag = true, pointer to head of data list • Link • Pointer to next item in list
Generalized Lists class GenListNode { friend class GenList; private: bool tag; GenListNode *link; union { char data; // or any other type of interest GenListNode* dlink; } } class GenList { private: GenListNode *front; // using name front because // we will use the term head // as something like a function }
Generalized Lists • Union definition: • User-defined data type that, at any given time, contains only one object from its list of members (although that object can be an array or a class type). The member-list of a union represents the kinds of data the union can contain. A union requires enough storage to hold the largest member in its member-list. union NumericType { int iValue; long lValue; double dValue; };
false true false false a b c 0 0 Generalized Lists • Example representations • D = ( ) Length 0, Null List D.front = 0; • A = (a, (b,c)) Length 2 Head = a Tail = ((b,c)) A.front
false false true false a b c 0 0 true true true 0 0 Generalized Lists • B = (A, A, ( ) ) {where A is defined previously} A -> B ->
false a true 0 Generalized Lists • C = (a, C)
Generalized Lists • Recursive class definition (lists within lists): Recursive operation definitions are probably the best way to go • Recursive functions for generalized lists will have two parts: • A publicdriver which starts the recursive calls off at the right point • A privateworkhorse which does the actual recursion and work
Generalized List Copy • Copying a list: • Assume list is not recursive, no shared sublists (as these are problematic) • Essential properties of algorithm: • If data is an element, need to just copy the data => Requires a tag check to see if element or list • Otherwise, if data is a sublist, need to copy the sublist, then copy the rest of the current list => Use recursion (calling copy again) on sublist and current node link
Generalized List Copy // Driver void GenList::Copy(const GenList &rhs) { first = Copy(rhs.first); } // Workhorse GenListNode* GenList::Copy(GenListNode* p) { GenListNode* q = 0; if (p != 0) { q = new GenListNode(); q-> tag = p->tag; if (q->tag == false) q-> data = p->data; else q->dlink = Copy(p->dlink); q->link = Copy(p->link); } return q; }
Generalized List Copy • Verification of copy algorithm: • Works on empty list? Yes – returns q = 0 if empty • Works on all data list? – Yes – copies data elements since all tags will be false, then copies rest of list until next = 0 • Works on data and sublists? – Yes, copies data elements for false tags, copies sublists and returns pointer for sublists into dlink, and copies rest of original list until next = 0
Generalized List Equality • Test for Equality • Requires: • Same list structure (placement of atoms and sublists) • Same list data • Essential properties of algorithm: • Check equality of tags • If equal • If data elements, check equality for data type • If list elements, recursively check equality on sublist
Generalized List Equality int operator==(const GenList& l, const GenList& r) { return equal(l.first, r.first); } int equal(GenListNode* s, GenListNode* t) { int x; if ((!s) && (!t)) return 1; // both empty if (s && t && (s->tag == t->tag)) // data in lists, same { // type in this position // check data if not sublists if (s->tag == 0) { if (s->data == t->data) x = 1; else x = 0; } // check recursively on sublists otherwise else x = equals(s->dlink, t->dlink); // if equal so far, recurse on next nodes if (x != 0) return equals(s->link, t->link); } return 0; //otherwise return false }
Generalized List Depth • Define depth of a list l to be: • If l is empty, depth(l) = 0; • Else, for a component s, Depth(s) = 0 if s is an atom Depth(s) = 1 + max {depth(x1),…,depth(xn)} if s is the non-empty list (x1, …, xn)
Generalized List Depth Example: A = (‘a’,’b’,’c’,’d’) depth(A) = 1; B = (‘a’,(‘c’,’d’),(‘b’,(‘e’,’c’))); depth(B) = 3;
Generalized List Depth int GenList::depth() { return depth(first); } int GenList::depth(GenListNode *s) { if (s == 0) return 0; GenListNode* p = s; int m = 0; while (p != 0) { if (p->tag == true) // sublist { int n = depth(p->dlink); // check depth of sublist if (m < n) m = n; // if > than current max, set as max } p = p->link; // continue until end of list } return m + 1; // include 1 for yourself }
Generalized Lists • Previous algorithms avoided the possibility of shared lists and recursive lists • Potential Problems: • When sharing lists, changes to the shared list need to be seen in the lists that make use of it B = (A, A, ( )) If A.delete() is called, removing A’s head, the two pointers for positions 0 and 1 in B need to be updated to point to the new head of the A. *Same problem if add a node at the front of A.
Generalized Lists • Could maintain a list of all things that point to a list and ensure that all are updated when the list is changed. • Better to work around problem by adding a head node to each list that doesn’t hold data. Tag is false. Data/dlink field contains a reference count, indicating how many things are pointing to the list. Link field points to real first item in the list
false 1 0 Generalized Lists • Example representations • A header for a list with no internal elements D ->
false false false true false false false false 3 1 1 1 b a c 0 0 0 true true true 0 Generalized Lists • A = (a, (b, c)) B = (A, A, ( ) ) A -> B ->
true 0 Generalized Lists • C = (a,C) false 2 false a C ->
Generalized Lists • Why are head nodes useful? • Any changes to the front of lists don’t have to be propagated to lists that are sharing the original list. The “head” node is always present and at the same address and points to whatever the real first data component is. • Reference counts can be used for determining when memory can be freed.