Data Structures & Algorithms
CS 305 & 503

Quiz 1, 20 October 2010


  1. Is it possible for a sequence of linked-sequence actions (not linked-sequence operations) to specify the behavior of a stack or a linked list but not a queue? If so, give an example of a short sequence; if not, explain why.


    It is, as described by the linked-sequence actions

    (put, 0, 0, true, undefined)  // stack, queue, or linked list behavior.
    (put, 0, 1, true, undefined   // stack or linked list, but not a queue behavior.
    

    The answers, in no particular order and approximately verbatim:

    1. Yes, it is possible for a sequence of linked-sequence actions to specify the behavior of a stack or a linked list but not a queue.1

      1: How?

    2. Yes. For stack and linked list we can add or remove value from the end of list at one end. For queue can not add/remove value from same end.
    3. Yes. The behavior of a stack and linked list is last in first out.2 The queue is first in first out.

       stack	         queue
      s.push(1)	s.push(1)3
      s.push(2)	s.push(2)
      

      2: Linked lists can show LIFO behavior, but they can also show lots of other behavior too.

      3: These aren’t linked sequence actions.

    4. Yes.4

      4: How?

    5. No, it5 would be able to specify the behavior of them all, it6 would just take loner with queues because of their properties.7 It8 would have to go all the way to the end which was the first value added.

      5: Which it? The action sequence?

      6: Is this the same it as the previous one

      7: Which properties?

      8: Which it is this?

    6. No, this is not possible because any linked sequence consists of data that can have values removed or inserted at any point within the sequence,9 and with how stack behaves which is (LIFO), a linked sequence will only be able to see what went in last.10

      9: What about stacks and queues, which are linked sequences?

      10: Yes? How does that prevent an action sequence from behaving like a stack or a linked list but not a queue?

    7. Yes, it is possible for a sequence of linked sequence actions to specify the behavior of a stack or linked list but not a queue. Such actions11 that can take place is within a stack.12 One example is to add/remove elements within a sequence. You can add these elements in not out the index values. It would not be queue if it is a stack in which it would be first in first out where the first element/value stored within the stack, if it is removed then it would take the bottom/first value out of that stack. Therefore it is possible for a sequence to do so if it cannot be a queue.

      11: Which actions?

      12: Or a linked list perhaps?

    8. No, for e.g. Stack follows the rule of LIFO, where as Queue follows the rule of FIFO. So when we perform a sequence of linked-sequence actions, if the node is popped,13 stack will pop the last value of node, where as Queue will remove the first value. But in linked sequence action, it is not possible14 because once you add/push a value it goes to the last one.15 So it is not possible to specify the behavior if it’s a stack or queue since the value is added at the end.16

      13: Pop isn’t a linked-sequence action

      14: What’s not possible

      15: Last one what?

      16: What about linked lists?

    9. Yes, because simple operations like adding and removing are different in stacks and linked lists than a queue.17 s.pop(5)18

      17: Different how? And can an action sequence demonstrate that difference?

      18: Pop isn’t an action.

    10. Yes, it’s possible for a sequence of linked sequence actions to specify the behavior. To specify the behaviors, we can check the index values.
      • Stack accepts at the index zero to put and remove values.
      • Linked list accepts when the index is greater than or equal zero and smaller than or equal n (put values).
      • Queue only accepts the index is zero when putting values and the index is equal to n when removing values.19

      19: Presumably the action sequence would exploit these properties somehow.

    11. Yes. There is a linked-sequence actions when performed gives the behavior of linked list and stack but not queue.

      Example of actions add and remove. We add elements in ADT at index values keeping the ADT constraints in mind.

      Test case if we add 3 elements and remove them all the three ADT works.20 But if want to delete element from index reference Queue doesn’t satisfy i = 0 case, which the other two satisfy.

      Sequence action

      add (index 0, val 4)21
      remove (index 0) 
      

      These actions work for stack and linked list but not for queue.

      20: Using the same action sequence?

      21: Are these linked sequence actions?

    12. Yes. The set of actions applicable to a stack and a queue are not all the same, but both are subsets of the actions you can perform on a linked list.22 Where T is a variable of some arbitrary type.

      put(0, T) success23
      put(0, T) success24
      get(0, T) success 
      get(0, T) success
      see(0, T) fail
      

      22: But the question wants to distinguish between stacks and queues.

      23: Is this a linked-sequence action?

      24: Why do you need any more than this?

    13. Regarding a stack and a queue, a sequence of actions could very well determine which data type is being used.25 For example, if the values 1, 2, 3 are added in that order, and then removed, their order in the output would differentiate between the two.

      This is because a stack operates on LIFO (last in first out) and a queue operates on FIFO (first in first out). So basically if a sequence comes out the same as it was entered, you’re dealing with a queue, but if it’s backwards it’s a stack.26

      25: That may be, but that’s not what the question is about.

      26: Right, but where’s the linked list part?

    14. I am not aware that a sequence of linked-sequence operations can specify behavior of a stack or a linked list and be excluded from a queue. All three data structures are interrelated: a list allows access to any value, but loses time for transversing.27 A stack will allow access to the last value added, while a queue (usually) allows access to the first value added. Specific actions may apply to one only due to these specific behaviors.

      27: Is that relevant?

    15. Yes.

      1) put(0, 1)
      2) put(0, 2)
      3) get(0) // 2
      4) get(0) // 1
      

      This can be linked list as that can insert or remove from index 0.

      A stack can only put or get at index 0. Which is fine in this example.

      However a queue can only get from index 0 when it is the index of the only value in the queue. In the case of action 3, get(0), this is not true.

    16. (put, 0, 10, true, undefined)
      (put, 0, 10, true, undefined)
      

      These operations can only be preformed on stack or linked list, but not on a queue.

    17. No, it is not possible for a sequence of linked-sequence actions to specify the behavior of a stack or linked list, but not a queue. Because stacks are the most restrictive of these three ADTs, if the sequence could specify the behavior of a stack then it could also specify the behavior of a queue.28

      28: That’s backward; because stacks are more restrictive than queues, they can’t do things that queues can.

  2. A linked list implemented with dynamically allocated nodes can insert a value just after the head in the same amount of time that it can insert a value just before the tail. What kind of links are used? Justify your answer.


    It has to be a doubly-linked list. Inserting a value just before the tail in a singly-linked list requires traversing from the head to the tail, taking time proportional to the number of elements in the list, while a doubly-linked list can insert the value just before the tail in time independent of the number of values in the list, assuming there’s a pointer to the tail.

    The answers, in no particular order and approximately verbatim:

    1. A doubly linked list would be used as they are capable of traversing the lists in both directions. It is also capable of peeking ahead and behind so finding the second and second-to-last value would take the same amount of time.
    2. Linked sequence operations are used.1

      1: Not the question asked.

    3. Free links reference stored in array2 are used to insert element at constant time in a linked list.3

      The freeLink array stores the reference for the next-available free link.

      If we are at index 0 and want to insert we look-up to find out next empty link so we get 1 and add element there. Later we know at index 3 there is empty node and so on.

      2: Array? Where?

      3: A free list is used to find a free node in constant time, but that doesn’t apply here.

    4. A double linked list is used when a value at the head and tail are inserted in the same time. I say this because a single linked list can only edit what’s after the node, while a double linked list can "see" what is before and after each node.
    5. The linked list can allocate node and insert the value just after the head and just before the tail at the same amount of time. That mean this links should be static linked sequence.4

      4: But the question states the linked list is implemented with dynamic storage.

    6. Circular double linked list. In circular linked list head is tail5 so we are performing head insert value just after the head and insert value before the head.

      5: The head and the tail are adjacent.

    7. The dynamic data types internal storage is linked in a sequence using both previous and next pointers to their respective data elements in that sequence. This allows for the ability of traversing the data type a number n < length from either end, head or tail, at the incremental rate.6

      6: True, but the question isn’t about arbitrary traversals.

    8. When a linked list implemented with dynamically allocated nodes can insert a value just after the head in the same amount of time that it can insert a value just before the tail occurs it happens in constant time. It can either use the LIFO or FIFO actions where it is either last in first out or first in first out.7 The links that can be used can either be links in a linked list, stack, or a queue. This happens when you want to have values in a sequence.

      7: What does this have to do with anything?

    9. Double link list was used.
      1. For the value to insert after head,
        1. create new node.
        2. Link the head pointer to the new pointer.
        3. Link the new pointer to the pointer next to the head.
      2. For the value insert before tail.
        1. create new node.
        2. find the tail pointer and link to new pointer.
        3. link the new pointer to the pointer before tail.
      Both insertion take same steps and same time.
    10. Double linked list would be used in this case. Because in the double linked list the node knows the next node and it knows the previous node. So it is very easy using pointers to add a node either after the head or before the tail as the action that takes place is constant time.
    11. Double linked list is used. Double linked list allows user to go through each element from the head to tail or from the tail to the head.8

      8: Yes, but that’s more than the question requires.

    12. Double links are used. Inserting just after the head requires you traverse only to the head. This is a simple operation since the list keeps a pointer to the head. If it were singly linked, to insert before the tail would require you traverse (n - 1) times. That time is expensive. In a doubly linked list you can use the pointer to the tail and that node’s previous to get the node at (n - 1) in the same time to get the nodes at the head and (1).
    13. For a linked list to be able to insert a value behind the head in the same time as inserting just before the tail, a doubly-linked list is used. By enabling traversing of the list in either direction, efficiency of speed is realized - but at the expense of efficiency of memory.
    14. Being that this implementation is implying that constant time is achieved just after the head and right before the tail, but does not mention any other locations in the sequence, it is a doubly-linked list. That implies that there is a set of nodes showing the location of the next value and another set of nodes showing the previous value. Thus providing constant time access to the head, the 2nd element, the tail and the tail - 1 element.
    15. Doubly linked lists, which allows one node to reference two others, causing shorter amount of time9 to insert values in allocated nodes.10

      9: A constant amount of time.

      10: Why?

    16. In this particular case, if it takes the same amount of time to insert a value after the head as it does to insert a value before the tail, then it is clear that double links are used. This is so because double linked lists carry before and after information, causing insertions after or before an element to take the same amount of time. (as opposed to single linked lists which Carry only after information).
    17. This dynamically allocated nodes would work just like any nodes except that the nodes are double linked. (Don’t remember the term used when nodes are linked in a fashion where the links can run both way example.)

  3. Show how to determine if a circular buffer is empty or full without counting the values in the buffer. Make sure you clearly state your assumptions.


    If values are off limits, the only part of the circular buffer available are the head and tail pointers. Assume that the head points to the next value, if any, to be removed and the tail points the location at which the next value added will be stored; then the buffer is not full when the head and tail pointers are different. However, when the head and tail are equal, it’s not clear if the buffer’s full or empty. The way to distinguish between empty and full is to remember which pointer was changed to make them equal. If the head pointer was moved (that is, a value was removed), the buffer’s empty. If the tail was moved (that is, a value was added), the buffer’s full.

    The need to remember the last pointer moved can be avoided by further assuming that an n-element buffer can only hold at most n - 1 elements. Now the buffer is unambiguously empty when the head and tail are equal, and full when (head + 1) mod n == tail.

    There are other techniques using different assumptions, but they become increasingly more obscure.

    The answers, in no particular order and approximately verbatim:

    1. The circular buffer is empty when the head is empty/null and the tail is null.1

      The circular buffer is full when the head is empty/null and the tail is equal to (n - 1), n number of values in the circular buffer.2

      1: What does it mean for the head or tail to be null? Are they null themselves, or pointing to nulls?

      2: How do you know that arrangement will always be present when the buffer is full?

    2. The Java collections used to implement a queue offer a method that must be implemented in the queue class isEmpty(). Calling this method would provide a boolean response as to whether or not a queue implemented in this manner is empty.3 It could also be determined if a queue is empty by dequeuing and looking for an exception.4 In a similar manner a full queue could be determined by adding a dummy value, and checking queue-size to see if it grows due to lack of space.5

      3: Ok, but how does isEmpty() work?

      4: What happens if no exception is thrown?

      5: The question disallows counting elements.

    3. Because a circular buffer’s head or the first node is connected to its tail,6 which makes a circle, there is no beginning or end. The user7 can move a value to another point in the circular buffer if it is possible8 then use that as a reference point as to where the user can start the data count.9 If not, then try to move the value to another point in the circular buffer.

      6: Is the head always connected to the tail in a circular buffer?

      7: The user of the circular buffer?

      8: Why would moving a value from one place to another in a circular buffer be helpful?

      9: But the question disallows counting.

    4. I’m assuming ‘head’ is the index of the first value in the buffer and ‘tail’ is the index of the first empty spot. In other words, tail is the index of the element after the last value. Also ‘size’ is the size of the allocated buffer, i.e. NOT the count.

      If head is equal to tail then the list is empty, i.e., count = 0. If ((tail + 1) % size) == head, then the buffer is full, i.e., count = size + 1.

      In this implementation there is a phantom tail, if you were to completely fill the buffer then head would equal tail and would make it ambiguous as to whether the buffer is empty or full. This requires a buffer of at least two elements allocated.10

      10: Does this fix the ambiguity?

    5. Using an array implementation. When the head node is equal to the tail node and the head node references data, the buffer is full. When the head node is equal to the tail node and the head node points to no data, the buffer is empty.11

      11: What happens if the queue can hold nulls as values?

    6. Because circular buffer’s head or first node is connected to its tail, which make the circle there is no beginning or end. The user can move a value to another point in the circular buffer if it is possible then use that as a reference point as to where the user can start the data count.12 If not, then try to move the value to another point13 in the circular buffer.14

      12: Possibly, but the question disallows value counting.

      13: Which point?

      14: And then what?

    7. Yes, if the size of the circular buffer is known this can be determined by checking the free nodes list which checks to see if there is a null value in the array.15 By listing all free nodes and by knowing the size of the buffer you should be able to determine if the buffer is full or empty.16

      15: What if the buffer can store nulls as values?

      16: Isn’t that counting?

    8. Let the circular buffer be a circular queue.

      Class CQueue {
        Node head
        T data
        }
      
        temp = head;
        if (temp.next == temp) // circular queue is empty
        else // circular queue is full.17
      

      17: How is a queue implemented using dynamic storage ever full?

    9. Remove a value from circular buffer. If it is undefined it will be empty.18 Add a value to circular buffer. If it is overflow, it will be full.19

      18: Or if the value removed was the last value.

      19: Or if there was only one open slot left.

    10. I would try to add a value to the circular buffer to see if it were full and if I got an overflow message I would then know if it’s full.20 In order to check if the circular buffer was empty I would try to return the values and if nothing returned then I would know how many values are in the buffer.21

      20: What if the value you add makes the buffer full?

      21: Isn’t that an expensive way to determine if any values are in the buffer? And what do you do with the returned values?

    11. One way is trying to remove a value from the circular buffer. If the value is removed, it means the buffer is not empty but if it is syntax22 no value to remove, then we know that buffer is empty.23 Similarly, if we try to add a value to the buffer, and it gives a syntax pointing that no space for the value to put in. It means the buffer is full.24

      22: ? The word looked like “syntax” anyway.

      23: And what of the removed value?

      24: And what of the added value?

    12. A circular buffer can be determined empty if either the head, which is given the reference “0” or the tail, which is given the reference “n - 1” is null.25 A circular buffer can be determined full if the head and tail (reference 0 and n - 1 respectively) both contain a value.26

      25: Do you mean the head or tail is referring to null, or are themselves null?

      26: What happens if the head isn’t 0 or the tail isn’t n - 1.

    13. Assumption: We implement two stacks in a circular buffer.27

      Solution: The two stacks' top-of-stack s1 and s2 respectively placed at i = 0 and i = size (say n). Whenever the push operation takes place, the s1, s2 increments in opposite directions.28

      push(s1, x)
        s1[TOS1] ← x
        TOS1TOS1 + 1
      

      push(s2, x)
        s2[TOS2] ← x
        TOS2TOS2 - 1
      

      Therefore, when TOS1 = TOS2 then the buffer is full. Similarly when removing elements.

      pop(s1)
        if s1[TOS1] == null
        then buffer is empty29
      

      pop(s2)
        if s2[TOS2] == null
        then buffer is empty
      

      27: You mean implement two stacks in the circular buffer, or implement the circular buffer using two stacks?

      28: Why would you push on both ends of the queue?

      29: Is s2 empty too? Why?

    14. Assumption: use static array to implement queue as circular buffer. Set head, tail, and array size to 0.

      if (head == tail) then array is empty.
      

      After a few enqueue.30

      if (((tail + 1) % array.length) ==) then array is full.31
      

      30: What about dequeues?

      31: But what happens when the head is a few values behind the tail?

    15. One way to determine if a circular buffer is empty without counting the values is to simply attempt to dequeue a single value. If you succeed, the buffer is not empty, replace the value.32 To check the converse, attempt to add a value. If the buffer does not overflow (or resize if dynamic), it was not full.33,34

      32: You mean the buffer wasn’t empty; it may be empty after you’ve removed a value.

      33: Correct; the queue wasn’t full, but it may be after you add a value.

      34: What about the changes made to the queue? What happens to those?

    16. Let us consider buffer size is n with a starting value of zero.35 If N = n - 1, then the circular buffer is full. Here N is the head node.36 If N = 0, then the circular buffer is empty.

      35: Do you mean the buffer has capacity n and current size 0?

      36: What about the tail?

    17. Have an equally sized linked list that keeps track of whether or not the buffer is full or not.37 If it is,38 have the list that is copying the original39 display the position in the original that is full. So if there is a value in the 3rd node, it will display “3.”40

      37: Isn’t that expensive in time and space?

      38: Determined how?

      39: The original what?

      40: But what of the other queue positions?

  4. As discussed in class, a linked list implemented with two arrays can add and remove values in constant time. Can such a linked list also solve the phantom reference problem? Justify your answer.


    Yes. Values placed in the array don’t move once placed. A reference to a value (that is, the value’s index) in the array remains valid until the value is removed from the array.

    The answers, in no particular order and approximately verbatim:

    1. Yes, if the value that reference the phantom element has been removed by either of the two arrays it shall be solved.1

      1: How?

    2. Yes, such linked lists can solve the phantom reference problem. This linked list enqueue and dequeue one array and dequeue another array.2

      2: How does this solve the phantom-reference problem? And how do values move from the enqueue array to the dequeue array?

    3. Yes. A singly linked list implemented with an array for values and another for next references can solve the phantom reference problem. Traversal of this data structure is still in linear time, so in order to remove a value, it must be located first,3 and once located, the value in the reference array pointing to it can be set to the next item in the sequence by copying its next reference value into the one used to locate the value being removed, and removing the index value that it is being copied from.4 This procedure clears the value and nulls its “node” while replacing the “node” pointing to it with the next in sequence.

      3: Why not use the reference to locate the value directly?

      4: What consequences does this have for the phantom-reference problem?

    4. Since the linked list two array implementation creates a pointer (value locator) it can also solve the phantom reference problem. Through the use of a free list, empty references are immediately available for use, while the reference list ensures access to the values.5

      5: True, but that doesn’t have much to do with the phantom reference problem that I can see.

    5. Yes. A linked list implemented w/ two arrays that can either remove or add values in constant time can also solve the phantom reference problem. The reason for this is because in a phantom reference problem it6 needs for it to occur in a certain amount of time7 and not take long at all if it is done in a certain amount of time then it is more efficient and have more effectiveness.

      6: Which it?

      7: What does this mean? How does time enter into it?

    6. Yes, in two arrays one array acted as pointers; the other array stores the values. When both arrays are full we can use dynamic array to increase the size.8 This will solve the phantom reference problem.

      8: How is this relevant?

    7. No. A linked list implemented with two arrays cannot solve the phantom reference problem.9

      9: Why not?

    8. Yes, the two array implementation of linked list solves the phantom reference problem because when we store references of empty nodes/links of the linked list,10 we can also store the reference of phantom end and ware[?] not to entire[?] value in it.

      10: What about the nodes with values?

    9. A linked list can’t solve the phantom reference problem. When a linked list implemented with another two arrays, it’ll use a lot of memory.11

      11: Perhaps, but what effect does that have on the phantom-reference problem?

    10. The phantom reference problem occurs when an element is reference that is no longer in the list. This kind of list does not solve this issue as the indices are not unique to the value of the element it references. One may try to access an element which has since been removed and get a different value than expected without knowing it’s a phantom reference.
    11. No, the linked list described above can’t solve the phantom reference problem. Even though this list is implemented with two arrays and can add and remove values in constant time it can’t solve the phantom reference problem because if a reference is made to an object that is not in the arrays, it will still only see the n - 1 element as the last spot in the array.12

      12: That’s one part of the phantom-reference problem, but there are others.

    12. No, this will not solve the problem. This is because the arrays that are within the linked list still have the same behavior, where the data or value can be shuffled between the empty space that is created when data is removed or moved to another point in the array that is in the linked list.13

      13: Why would the values have to be moved around when in the array?

    13. Yes. Once the linked list implemented with two arrays, it can add or remove values in constant time.14 Phantom references means that we do not have the name/value of the node, which is removed. Now, since it is constant time, we can add the value after it is removed.

      14: True, but more important for the phantom reference problem is that adding and removing values don’t change the positions of other values in the linked list.

    14. Yes, a linked list implemented with two arrays can solve the phantom reference problem. The index at which a value is removed can be tracked so that when a new value is added, it simply goes in that empty space.15 Thereby no index value is changed.

      15: Is that the phantom-reference problem?

    15. No.
    16. No, you are still dealing with links to nodes within a sequence removal of a node within the sequence during traversal16 would still require a refactoring17 at your position within that sequence.

      16: The phantom-reference problem is related to, but independent of, the modifying-while-traversing problem.

      17: Refactoring what how?

    17. If you have an array that is linked to the list, keeping track of which node carries that value, then yes, it will solve the problem.18

      18: Why exactly? What good does keeping track to?

  5. Describe the simplest, most space efficient way to implement a deque using an array if the objective is to minimize value movement within the array.


    Use a circular buffer. Deques provide access at both ends, same as queues, except both adding and removing at each end. A circular buffer provides unrestricted movement at both ends, at least until the buffer’s full or empty.

    The answers, in no particular order and approximately verbatim:

    1. The simplest way would be to implement another set of arrays1 where we can add the values to the empty spaces, so we don’t have to worry about moving all the values.2

      If it was a case of [???] we could have made the node point towards the result nodes, so the empty nodes goes to the free list and we are not moving any values.

      1: That’s a lot of extra space.

      2: What purpose do the multiple arrays serve?

    2. There are many ways to implement a deque using an array if the objective is to minimize value movement within the array. Perhaps the most space efficient way to do so is to pop it3 out of the sequence.4 If it is an array, A knows everything and data within the array so it would be ok just to move or dequeue it. It will minimize the value movement by doing so also because it is just moving that 1 item. That way if you traverse it5 then there will be no errors found.6 In this case it would be the same as if it were in a stack or a queue. It matters by the way which one is removing in first or last & which one is going in first or last.

      3: Pop what out of the sequence?

      4: When is popping done?

      5: Do you mean the deque here?

      6: What kind of errors?

    3. Use a free-list.7 Find an empty space in the array.8 Insert the value following the free-list pointer.

      7: Why would there be extra value shifting without a free list?

      8: Wouldn’t that be at the ends of the deque?

    4. The simplest, most space efficient way to implement a deque using an array would be to use a dequeue, or “double end queue.”9 By being able to remove values at either end (the head or tail), you minimize the shuffling of values.

      9: Right, but how do you do that? That’s what the question’s asking.

    5. Shift values in the array, i.e., a[i] = a[i + 1] until i < size - 1 where size is length of the queue.10

      10: Does this minimize value movement?

    6. The simplest, most space efficient way to implement a deque using an array would be to use a circular buffer.

      At this point you can queue values on either end without having to move elements.

      Value movement may otherwise occur if values are queued to one end more than the other, and dequeued more from the opposite end in a non-circular buffer.

      To address this problem you can make the array circular. This way the values may still logically “move” depending on the access, but you would never have to explicitly move each element to make room. This assumes the array is large enough and doesn’t get filled.

      A simple fix for that problem would be to allow the deque to eat the tails on a queue, thus the array would never fill.
    7. Because an array can not shuffle the data from right to left to save space, by using a dequeue it would be best to insert the data that is the least important first,11 so that what has more priority can be extracted first.12

      11: How is data’s importance determined?

      12: Is that a deque?

    8. To implement a deque using an array, we need to copy old values into an array,13 and then we can remove the value from the array by passing the index of the value that we want to remove.14

      13: Copy old values from where?

      14: Passing the value index where?

    9. Two heads of the deque are referenced from either end of the array as elements are inserted to the front of the queue the left head index would increase and as data is inserted into the back of the queue the right head index is decreased, when the indexes match the queue is full. Removal of data would increase the left index and increase the right index. Left index starts as 0. Right index starts as the size at the allocated array - 1.15

      15: What happens when the values reach the end of an array?

    10. Use circular buffer. Each time after the dequeue method, reset the head of the array.16 Don’t have to shift the rest of the values to fill the gap. head = (head + 1) % array.length.

      16: What is resetting? What does it do?

    11. The easiest way is to use modulo math for two variables representing the head and tail of the dequeue, and using one array of sufficient size, link the head and tail as if you are creating a circular buffer. As values are added or removed from either “end” the index values of the head and tail variables change, allowing the values in the array to remain where they are placed while the head and tail move around them.
    12. Deque is a kind of queue, where you can add/remove element from both ends. Objective is to minimize value movement within array. Therefore the simplest and most space efficient way to implement deque is using array as stack.17

      Stack stores and removes element as when they are being called by subroutines. They grow and shrink in size efficiently solves space efficient problem.18

      AddFirst(s, v)
        if (arr.elements > size)
          throw exception
        else
          s[tos] ← x
          tos = tos + 1
      

      RemLast(s) x ← s[tos] tos = tos - 1 return x

      AddLast(s, v)
        if (arr.elements > size)
          throw exception
        else
          s[tos] ← x
          tos = tos + 1
      

      RemLast(s) x ← s[tos] tos = tos - 1 return x

      17: Just one stack, or two?

      18: True, but deques grow and shrink at both ends. How does a stack handle that?

    13. The simplest way to implement a deque using an array if the objective is to minimize value movement would be if the values you want removed are in the front of the array.19 I say this because the deque operation takes values away from the front and if the objects you want removed are at the end, the value movement will be high.20

      19: Which is what part of the array? The low indices?

      20: Do you mean value movement in and out of the array, or within the array?

    14. The simplest and most space efficient way to implement a dequeue within an array is to use a second, pointer array.21 This arrangement minimizes movement within the array,22 since values do not need to shift, and empty spaces within the array are available for reuse. Using a free list enables both speed and efficiency.

      21: Which doubles the space used with no increase in values stored.

      22: But the array’s implementing a deque. What values move in a deque?

    15. By using a stack array23 we can implement a dequeue.24

      23: What’s a stack array?

      24: Does this provide minimal data movement with efficient space use?

    16. Because with queues it’s first in, first out, the best option at minimizing value movement would be to keep track of each value.25 copy the array,26 while leaving out the value you wanted to remove to begin with but keeping the original amount of nodes this way the values don’t get moved around.27

      25: Wouldn’t that have to be done even if value movement wasn’t being minimized?

      26: That’s moving values.

      27: Why is this an advantage, given deque access.

    17. Start by reading the values in the dequeue to see which elements are linked in the array.28 Start by implementing the deque values that are in the sequence with the array elements.29

      28: Aren’t all the values linked in the array?

      29: How is that done?


This page last modified on 27 October 2010.