More Linked Lists

Embed Size (px)

Citation preview

  • 8/3/2019 More Linked Lists

    1/27

    More Linked ListsChapter 9

  • 8/3/2019 More Linked Lists

    2/27

    In some applications, it is convenient to keep access to both

    the first node and the last node in the list.

    This would work nicely for a linked-list implementation of a

    queue (but we'll see an alternative later.)

    L

    first

    mySize 5

    9 17 22 26 34

    last

    Linked List Variants (9.1)

  • 8/3/2019 More Linked Lists

    3/27

    The data part of the head node might be used to store some

    information about the list

    e.g., the number of values in the list, or the name of a set

    to which all the values in the list belong . . . (e.g., 9.2:Linked polynomials)

    first 9 17 22 26 34?

    Sometimes a (dummy) head node is used so that every

    node has a predecessor eliminates special cases for inserting and deleting.

    Traversals start at first->next

    Inserts & deletes: No special case (first node)

    Constructor: first = new Node; first ?

    Head Nodes

  • 8/3/2019 More Linked Lists

    4/27

    (If data portion of element is large, two or more lists canshare the same trailer node.)

    first 9 17 22 26 34? ?

    Sometimes a (dummy) trailer node is also used so thatevery node has a successor.

    Not very

    common

    Trailer Nodes

  • 8/3/2019 More Linked Lists

    5/27

    Each node in a circular linked list has a predecessor (and a successor), provided

    that the list is nonempty.

    insertion and deletion do not require special consideration of the first

    node.

    This is a good implementation for a linked queue or for any problem in

    last 9 17 22 26 34

    In other applications a circular linked list is used;

    instead of the last node containing a null pointer, itcontains a pointer to the first node in the list.

    For such lists,one can use a single pointer to the last node in the

    list, because then one has direct access to it and "almost-direct"

    access to the first node.

    Circular Linked List

  • 8/3/2019 More Linked Lists

    6/27

    Circularly Linked Lists

    For example, item can be inserted as follows:

    newptr = new Node(item, 0);

    if (first == 0) // list is empty

    {

    newptr->next = newptr;

    first = newptr;

    }

    else // nonempty list

    {

    newptr->next = predptr->next;

    predptr->next = newptr;

    }

    Note that a one-element circularly linked list points to itself.

  • 8/3/2019 More Linked Lists

    7/27

    Circularly Linked ListsTraversal must be modified: avoid an infinite loop by looking for the end of list as

    signalled by a null pointer.

    Like other methods, deletion must also be slightly modified.

    Deleting the last node is signalled when the node deleted points to itself.

    if (first == 0) // list is empty

    // Signal that the list is empty

    else

    {

    ptr = predptr->next; // hold node for deletion

    if (ptr == predptr) // one-node list

    first = 0;

    else // list with 2 or more nodes

    predptr->next = ptr->next;

    delete ptr;}

  • 8/3/2019 More Linked Lists

    8/27

    All of these lists, however, are uni-directional; we can

    only move from one node to its successor.

    last

    prevL

    firstmySize 5

    9 17 22 26 34

    next

    In many applications, bidirectional movement is necessary. In

    this case, each node has two pointers one to its successor

    (null if there is none) and one to its predecessor (null if there is

    none.) Such a list is commonly called a doubly-linked (or

    symmetrically-linked) list.

    e.g., 9.4 BigInt

    Doubly Linked Lists (9.4)

  • 8/3/2019 More Linked Lists

    9/27

    Linked List Variants

    L

    first

    mySize 5

    last9 17 22 26 34

    prev

    next

    doubly-linked list => bidirectional movement!!

    Each node has two pointers one to its successor (null if there is

    none) and one to its precedessor (null if there is none.)

    Doubly linked lists give one more flexibility (can move in either

    direction)BUT at significant cost :

    DOUBLEthe overhead for links

    More complex code

  • 8/3/2019 More Linked Lists

    10/27

    And of course, we could modify this doubly-linked list so thatboth lists are circular forming a doubly-linked ring.

    L

    first

    mySize 5

    last

    9 17 22 26 34

    Add a head node and we have the implementation used inSTL's list class.

    Other variations: 9.5

    Multiply-ordered lists

    Lists of lists (LISP)

    Doubly Linked Rings (9.5)

  • 8/3/2019 More Linked Lists

    11/27

    TL list Class Template

    L

    first

    mySize 5

    last

    17 22 26 349

    prev

    next

    data

    list is a sequential containeroptimized for insertion and erasure at arbitrary points in the

    sequence

    Implementation: circular doubly-linked list with head node.

    Caveat:

    Ease of use is a trade-off for the significant overhead of doubly-linked lists

    Moral:

    Be aware of what you are using and its costs/benefits

  • 8/3/2019 More Linked Lists

    12/27

    Hash Tables

    Linear search takes O(n)Binary search takes O(log n)

    Both depend on comparisons of item sought and elements in container

    Hash tablesplace data so that the location of an item is determined directly as a

    function of the item itself.

    With a good hash function, searching a hash table takes O(1) time

    that is, it is constant and does not depend on the number of items stored.

    One problem with hash tables is collisions: when more than one item maps to thesame location in the hash table.

    The hash function and data set determine the number of collisions and the strategy

    used to handle collisions affect the performance of searching for an arbitrary

    item.

  • 8/3/2019 More Linked Lists

    13/27

    Hash Tables

    Given up to 25 integers in the range 0 through 999 to be stored in a hash table.

    This hash table can be implemented as an integer array table in which each array

    element is initialized with some dummy value, such as -1.

    If we use each integeri in the set as an index, that is, if we store i in table[i], then to

    determine whether a particular integer number has been stored, we need only

    check iftable[number] is equal to number.

    The hash function then is h(i) =i

    The hash function determines the location of an item i

    in the hash table.

  • 8/3/2019 More Linked Lists

    14/27

    Hash TablesThe hash function in the previous example works perfectly because the time

    required to search the table for a given value is constant;

    only one location needs to be examined.

    This hash function then is very time efficient, but it is surely notspace-efficient.

    Only 25 of the 1000 available locations are used to store items, leaving 975 unusedlocations; only 2.5 percent of the available space is used, and so 97.5 percent is

    wasted!

    Because it is possible to store 25 values in 25 locations, we might try improving

    space utilization by using an array table with capacity 25.

    Modified hash function h(i) =i modulo 25 addresses the space problem

    // C++ syntax,

    int h(int i)

    { return i % 25;}

  • 8/3/2019 More Linked Lists

    15/27

    H

    ashT

    ablesint h(int i){ return i % 25;}always produces an integer in the range 0 through 24.52 thus is stored in table[2], since h(52) =52 % 25 =2.129, 500, 273, and 49 are stored in locations 4, 0, 23, and 24, respectively.

    INDEX VALUE

    0 500

    1 -1

    2 52

    3 -14 129

    5 -1

    23 273

    24 49

  • 8/3/2019 More Linked Lists

    16/27

    Hash Tables

    But what about placing 77? h(77) =77 % 25 =2

    Collision!!

    Other values may collide at a given position:

    for example, all integers of the form 25k+2 hash to location 2.

    Some strategy is needed to resolve such collisions:

    1. Need to be able to place an element when its mapped location is alreadyfull

    2. Need to be able to retrieve element when it's not placed directly according

    to the hash function

  • 8/3/2019 More Linked Lists

    17/27

    Collision Strategy (Storage)

    linear probing: linear search of the table from location of collision until an

    empty slot is found in which the item can be stored.

    When 77 collides with 52 at location 2, put 77 in position 3

    INDEX VALUE

    0 5001 -1

    2 52

    3 77

    4 129

    5 102

    23 273

    24 49

    To insert 102, we follow the probe sequence consisting of locations 2, 3, 4,

    and 5 to find the first available location and thus store 102 in table[5].

  • 8/3/2019 More Linked Lists

    18/27

    Collision Strategy (Storage)

    If the search reaches the bottom of the table, continue at the first location.

    123 collides with 273 at location 23, and the probe sequence 23, 24, 0, 1locates the first empty slot at position 1. 123 placed in position 1

    INDEX VALUE0 500

    1 123

    2 52

    3 77

    4 129

    5 102

    23 27324 49

  • 8/3/2019 More Linked Lists

    19/27

    Cost of Linear Probing (Searching)To determine if a specified value is in this hash table,

    apply the hash function to compute the location for this value

    1. if location is empty, value not in the table.

    2. if location contains the specified value, the search is

    successful.3. if location contains a different value, must rule out collision

    begin a circular linear search at this location and

    continue until either item is found or empty or starting

    location reached (item not in table)Items 1 & 2 take O(1) time

    But Item 3 (worst case) takes O(n)!

    Use a hash table capacity thats 1.5 - 2 times the # of items to be

    stored (c.f., the Birthday Problem on p. 484)

  • 8/3/2019 More Linked Lists

    20/27

    AnotherCollision StrategyChaining: use a hash table that is an array (or vector) of linked lists to store the

    items.

    For example, to store names "alphabetically", use an array table of 26 linked lists,

    initially empty, and the simple hash function h(name) =name[0] - A;

    that is, h(name) is 0 ifname[0] is A,

    1 ifname[0] is B, . . . ,

    25 ifname[0] is Z

    Searching such a hash table is straightforward:apply the hash function to the item sought and then use one of the search

    algorithms for linked lists.

    When a collision occurs, we simply insert the new item into the appropriate linked

    list.

  • 8/3/2019 More Linked Lists

    21/27

    Performance ofHash function

    The behavior ofthe hash function affects the frequency of collisions.

    For example, the preceding hash function is not optimal because some letters occur

    more frequently than others.

    the linked list of names beginning with S tends to be much longer than that

    containing names that begin with Z.

    clustering effect results in longer search times for S-names than for Z-names.

    Need a better hash function to distribute names more uniformly in hash table

    The hash function must not, however, be so complex that the time required to

    evaluate it makes the search time unacceptable.

  • 8/3/2019 More Linked Lists

    22/27

    Random HashingAn ideal hash function is

    ysimple to evaluate

    yscatters items throughout the hash table

    minimizing the probability of collisions.

    random hashing uses a simple random number generation technique to scatter the itemsrandomly throughout the hash table.

    The item to store is first transformed into a large random integer and then reduced modulo

    the tables capacity to determine its location:

    randomInt = ((MULTIPLIER * item) + ADDEND) % MODULUS

    location = randomInt % CAPACITY;

    random hashing can be used with any object first encoded as an integer

    For example, a name might be encoded as the sum (or average) of the ASCII codes of

    some or all of its letters.

    See p. 486

  • 8/3/2019 More Linked Lists

    23/27

    On the surface, listlooks quite simple. But it's allo/deallo-cation scheme is

    more complex than simply using new and delete operations.

    To reduce the inefficiency of using the heap manager for large numbers ofallo/deallo-cations, it does it's own memory management.

    Basically, for each list of a certain type T:

    When a node is needed:

    1. If there is a node on the free list, allocate it.(This is maintained as a linked stack in exactly the way we

    described earlier.)

    2. If the free list is empty:

    a. Call the systems heap manager to allocate a block of memory (called

    a buffer) typical size: 4K bytes.

    b. Carve it up into pieces of size required for a node of a list.When a node is deallocated:

    Push it onto the free list.When alllists of this type T have been destroyed:

    Return all buffers to the heap.

    Allo/deallo-cation in list

  • 8/3/2019 More Linked Lists

    24/27

    listiterators:

    list's iterator is "weaker" than that forvector.vector: random access iterators

    list: idirectionaliterators

    They have the following operations in common:

    ++ Move iterator to next element (likeptr = ptr-> next)

    -- Move iterator to preceding element (likeptr = ptr-> prev)

    * dereferencing operator (likeptr-> data)

    = assignment (for same type iterators)

    it1 = it2 makes it1positioned at same element as it2

    == and != (for same type iterators)

    checks whether iterators are positioned at the same element

    list Iterators

  • 8/3/2019 More Linked Lists

    25/27

    Iteratorsan abstraction of a pointer, hiding some of its details and eliminating some of its hazards.

    For example, a list::iterator is a class within the list class template that contains

    a data member node, which is a pointer to a list_node (the struct used to describe

    nodes in the list class template):

    template

    class list

    {

    public:

    class iterator // ... simplified here ...

    {

    protected:

    list_node * node; // ... and here ...

    . . .

    };

    . . .

    };

  • 8/3/2019 More Linked Lists

    26/27

    IteratorsThe iterator class overloads operator*()to return the value of the data member in the

    list_nodepointed to by the iterators node member:

    return node->data;

    Also overloads operator++ () to increment the iterator to the next node in the list

    // prefix version

    node = node->next;

    return *this;

    // postfix version

    iterator tmp = node;

    node = node->next;

    return tmp;

    and overloads operator--() similarly to decrement the iterator to the previous node in

    the list.

  • 8/3/2019 More Linked Lists

    27/27

    Iteratorstwo important iterator-valued functions:

    begin(), which returns an iterator to the first value in the list; and

    end(), which returns an iterator that points beyond the final value in the list,

    respectively.

    Iterators normally go from the first node to the last.

    One can use a reverse_iterator to go from last to first.

    list:: reverse_iterator it;

    for (it = myList.rbegin(); it != myList.rend(); it++)

    cout