Both singly-linked and doubly-linked lists are established techniques, having been used in programs for over 40 years. They are discussed, for example, by Knuth [46, Sections 2.2.3-2.2.5]. Even the SEList data structure seems to be a well-known data structures exercise. The SEList is sometimes referred to as an unrolled linked list [67].
Another way to save space in a doubly-linked list is to use 
so-called XOR-lists.
In an XOR-list, each node, 
 , contains only one
pointer, called
, contains only one
pointer, called 
 , that holds the bitwise exclusive-or of
, that holds the bitwise exclusive-or of 
 and
and 
 .  The list itself needs to store two pointers, one to the
.  The list itself needs to store two pointers, one to the 
 node and one to
node and one to 
 (the first node, or
 (the first node, or 
 if the list is
empty). This technique uses the fact that, if we have pointers to
 if the list is
empty). This technique uses the fact that, if we have pointers to 
 and
and 
 , then we can extract
, then we can extract 
 using the formula
 using the formula
 
^ computes the bitwise exclusive-or of its two arguments.)
This technique complicates the code a little and is not possible in
some languages, like Java and Python, that have garbage collection but gives a
doubly-linked list implementation that requires only one pointer per node.
See Sinha's magazine article [68] for a detailed discussion of
XOR-lists.
 ,
, 
 ,
,
  
 , and
, and 
 ?
?
 , that returns
  the second-last element of an SLList.  Do this without using the
  member variable,
, that returns
  the second-last element of an SLList.  Do this without using the
  member variable, 
 , that keeps track of the size of the list.
, that keeps track of the size of the list.
 ,
, 
 ,
,
  
 and
 and 
 on an SLList.  Each of these operations
  should run in
 on an SLList.  Each of these operations
  should run in 
 time.
 time.
 that reverses the
  order of elements in an SLList.  This method should run in
 that reverses the
  order of elements in an SLList.  This method should run in 
 time, should not use recursion, should not use any secondary data
  structures, and should not create any new nodes.
  time, should not use recursion, should not use any secondary data
  structures, and should not create any new nodes.
 .
  These methods walk through the list and count the number of nodes to
  see if this matches the value,
.
  These methods walk through the list and count the number of nodes to
  see if this matches the value, 
 , stored in the list.  These methods
  return nothing, but throw an exception if the size they compute does
  not match the value of
, stored in the list.  These methods
  return nothing, but throw an exception if the size they compute does
  not match the value of 
 .
.
 operation that creates a
  node,
 operation that creates a
  node, 
 , and adds it in a DLList just before the node
, and adds it in a DLList just before the node 
 .  Do not
  refer to this chapter.  Even if your code does not exactly match the
  code given in this book it may still be correct.  Test it and see if
  it works.
.  Do not
  refer to this chapter.  Even if your code does not exactly match the
  code given in this book it may still be correct.  Test it and see if
  it works.
The next few exercises involve performing manipulations on DLLists.
You should complete them without allocating any new nodes or temporary
arrays.  They can all be done only by changing the 
 and
 and 
 values of existing nodes.
values of existing nodes.
 that returns
 that returns 
 if the
  list is a palindrome,
  i.e., the element at position
 if the
  list is a palindrome,
  i.e., the element at position 
 is equal to
  the element at position
 is equal to
  the element at position 
 for all
 for all 
 .
  Your code should run in
.
  Your code should run in 
 time.
 time.
 that ``rotates'' a DLList so that list
  item
 that ``rotates'' a DLList so that list
  item 
 becomes list item
 becomes list item 
 .  This method should
  run in
.  This method should
  run in 
 time and should not modify any nodes in
  the list.
 time and should not modify any nodes in
  the list.
 , that truncates a DLList at position
, that truncates a DLList at position
  
 .  After executing this method, the size of the list will be
.  After executing this method, the size of the list will be 
 and
  it should contain only the elements at indices
 and
  it should contain only the elements at indices 
 .  The
  return value is another DLList that contains the elements at indices
.  The
  return value is another DLList that contains the elements at indices
  
 .  This method should run in
.  This method should run in 
 time.
  time.
 , that takes as an argument
  a DLList,
, that takes as an argument
  a DLList, 
 , empties it and appends its contents, in order,
  to the receiver.  For example, if
, empties it and appends its contents, in order,
  to the receiver.  For example, if 
 contains
 contains  and
 and 
 contains
  contains  , then after calling
, then after calling 
 ,
, 
 will contain
 will contain
  
 and
 and 
 will be empty.
 will be empty.
 that removes all the elements with odd-numbered
  indices from a DLList and return a DLList containing these elements.
  For example, if
 that removes all the elements with odd-numbered
  indices from a DLList and return a DLList containing these elements.
  For example, if 
 , contains the elements
, contains the elements 
 , then after
  calling
, then after
  calling 
 ,
, 
 should contain
 should contain  and a list containing
 and a list containing
   should be returned.
 should be returned.
 , that reverses the order of elements in
  a DLList.
, that reverses the order of elements in
  a DLList.  
 .
       This method takes the first node from
.
       This method takes the first node from 
 and appends it to the the
       receiving list.  This is equivalent to
 and appends it to the the
       receiving list.  This is equivalent to 
 ,
       except that it should not create a new node.
,
       except that it should not create a new node.
 , that takes two
       sorted lists
, that takes two
       sorted lists 
 and
 and 
 , merges them, and returns a new sorted
       list containing the result.  This causes
, merges them, and returns a new sorted
       list containing the result.  This causes 
 and
 and 
 to be emptied
       in the proces.  For example, if
 to be emptied
       in the proces.  For example, if 
 contains
 contains  and
 and 
 contains
 contains
        , then this method returns a new list containing
, then this method returns a new list containing 
 .
.
 that sorts the elements
       contained in the list using the merge sort algorithm.
       This recursive algorithm works in the following way:
 that sorts the elements
       contained in the list using the merge sort algorithm.
       This recursive algorithm works in the following way:
       
 method, split the list
            into two lists of approximately equal length,
 method, split the list
            into two lists of approximately equal length, 
 and
 and 
 ;
;
 ;
;
 ; and, finally,
; and, finally,
 and
 and 
 into a single sorted list.
 into a single sorted list.
       
The next few exercises are more advanced and require a clear understanding of what happens to the minimum value stored in a Stack or Queue as items are added and removed.
 ,
,
  
 , and
, and 
 , as well as the
, as well as the 
 operation, which
  returns the minimum value currently stored in the data structure.
  All operations should run in constant time.
 operation, which
  returns the minimum value currently stored in the data structure.
  All operations should run in constant time.
 ,
,
  
 , and
, and 
 , as well as the
, as well as the 
 operation, which
  returns the minimum value currently stored in the data structure.
  All operations should run in constant amortized time.
 operation, which
  returns the minimum value currently stored in the data structure.
  All operations should run in constant amortized time.
 ,
,
  
 
 
 ,
, 
 and
 and 
 , and the
, and the
  
 operation, which returns the minimum value currently stored in
  the data structure.  All operations should run in constant amortized
  time.
 operation, which returns the minimum value currently stored in
  the data structure.  All operations should run in constant amortized
  time.The next exercises are designed to test the reader's understanding of the implementation and analysis of the space-efficient SEList:
 and
 and 
 ), then these
  operations run in constant amortized time, independent of the value
  of
), then these
  operations run in constant amortized time, independent of the value
  of 
 .
.
 .
.
^, to
  swap the values of two 
 variables without using a third variable.
 variables without using a third variable.opendatastructures.org