chapter begins with a review of the use of dynamically allocated memory in C++ Next come implementations of linked stacks and queues As
Section 4. 2 • Linked Stacks
4.2 LINKED STACKS
InSection 2.2, we used an array of entries to create a contiguous implementation of a stack. It is equally easy to implement stacks as linked structures in dynamic memory.
We would like clients to see our new implementation as interchangeable with consistency
our former contiguous implementation. Therefore, our stacks must still contain entries of typeStack_entry. Moreover, we intend to build up stacks out of nodes, so we will need a declaration
typedefStack_entry Node_entry;
to equate the types of the entries stored in stacks and nodes. Moreover, we must provide methods topushandpopentries of typeStack_entryto and from our stacks. Before we can write the operationspush and pop, we must consider some more details of exactly how such a linked stack will be implemented.
The first question to settle is to determine whether the beginning or the end of the linked structure will be the top of the stack. At first glance, it may appear that (as for contiguous stacks) it might be easier to add a node at the end, but this choice makes popping the stack difficult: There is no quick way to find the node immediately before a given one in a linked structure, since the pointers stored in the structure give only one-way directions. Thus, after we remove the last element, finding the new element at the end might require tracing all the way from the head. To pop a linked stack, it is much better to make all additions and deletions at the beginning of the structure. Hence the top of the stack will always be thefirstnode of the linked structure, as illustrated in Figure 4.9.
top entry middle entry bottom entry top_node
Figure 4.9. The linked form of a stack
90
Each linked structure needs to have a header member that points to its first node; for a linked stack this header member will always point to the top of the stack. Since each node of a linked structure points to the next one, we can reach all the nodes of a linked stack by following links from its first node. Thus, the only information needed to keep track of the data in a linked stack is the location of its declaration of type
Stack top. The bottom of a Stackcan always be identified as the Nodethat contains a
We can therefore declare a linked stack by setting up a class having the top of the stack as its only data member:
91
classStack{
public:
Stack( );
boolempty( )const;
Error_code push(constStack_entry&item);
Error_code pop( );
Error_code top(Stack_entry&item)const; protected:
Node*top_node;
};
Since this class contains only one data member, we might think of dispensing with the class and referring to the top by the same name that we assign to the stack itself. There are four reasons, however, for using the class we have introduced.
➥
The most important reason is to maintain encapsulation: If we do not use a class to contain our stack, we lose the ability to set up methods for the stack.➥
The second reason is to maintain the logical distinction between the stack itself,which is made up of all of its entries (each in a node), and the top of the stack, which is a pointer to a single node. The fact that we need only keep track of the top of the stack to find all its entries is irrelevant to this logical structure.
➥
The third reason is to maintain consistency with other data structures and otherimplementations, where structures are needed to collect several methods and pieces of information.
➥
Finally, keeping a stack and a pointer to its top as incompatible data types helps with debugging by allowing the compiler to perform better type checking. Let us start with an empty stack, which now meanstop_node == NULL, and con- empty stacksider how to addStack_entry itemas the first entry. We must create a newNode
storing a copy of item, in dynamic memory. We shall access this Node with a pointer variable new_top. We must then copy the address stored innew_topto
theStackmembertop_node. Hence, pushingitemonto theStackconsists of the
instructions
Node*new_top =newNode(item); top_node = new_top;
Notice that the constructor that creates theNode*new_topsets itsnextpointer to the default valueNULL.
As we continue, let us suppose that we already have a nonempty Stack. In pushing a linked stack
order to push a new entry onto theStack, we need to add aStack_entry itemto it. The required adjustments of pointers are shown in Figure 4.10. First, we must create a newNode, referenced by a pointernew_top, that stores the value ofitem
Section 4.2 • Linked Stacks
129
X
top_node top_node
Node
Empty stack Stack of size 1
new_top
New
Old Old Old
top_node
Link marked X has been removed.
Colored links have been added. node
top node second node bottom node
Figure 4.10. Pushing a node onto a linked stack
and points to the old top of theStack. Then we must changetop_nodeto point to
90
the new node. The order of these two assignments is important: If we attempted to do them in the reverse order, the change of the top from its previous value would mean that we would lose track of the old part of the Stack. We thus obtain the following function:
92
Error_code Stack::push(constStack_entry&item)
/*Post: Stack_entry item is added to the top of the Stack; returns success or returns
a code of overflow if dynamic memory is exhausted.*/ {
Node*new_top =newNode(item,top_node);
if(new_top == NULL)returnoverflow;
top_node = new_top;
returnsuccess; }
Of course, our fundamental operations must conform to the earlier specifications, and so it is important to include error checking and to consider extreme cases. In particular, we must return an Error_codein the unlikely event that dynamic memory cannot be found fornew_top.
One extreme case for the function is that of an empty Stack, which means
top_node == NULL. Note that, in this case, the function works just as well to push
the first entry onto an emptyStackas to push an additional entry onto a nonempty
Stack.
It is equally simple to pop an entry from a linkedStack. This process is illus- popping a linked stack
top_node
old_top X
Figure 4.11. Popping a node from a linked stack
92
Error_code Stack::pop( )
/*Post: The top of the Stack is removed. If the Stack is empty the method returns
underflow; otherwise it returns success.*/ {
Node*old_top = top_node;
if(top_node == NULL)returnunderflow;
top_node = old_top->next; deleteold_top;
returnsuccess; }
When we reset the value oftop_nodein the methodpop, the pointerold_topis the only link to the node that used to occupy the top position of theStack. Therefore, once the function ends, andold_topgoes out of scope, there will be no way for us to access thatNode. We therefore deleteold_top; otherwise garbage would be created. Of course, in small applications, the method would work equally well without the use ofdelete. However, if a client repeatedly used such an implementation, the garbage would eventually mount up to occupy all available memory and our client’s program would suffocate.
Our linked stack implementation actually suffers from a number of subtle de- fects that we shall identify and rectify in the next section. We hasten to add that we know of no bugs in the methods that we have presented; however, it is possible for a client to make aStackobject malfunction. We must either document the limi- tations on the use of ourStackclass, or we must correct the problems by adding in a number of extra features to the class.
Exercises 4.2
E1. Explain why we cannot use the following implementation for the methodpushin our linkedStack.
Error_code Stack::push(Stack_entry item)
{
Node new_top(item,top_node);
top_node = new_top;
returnsuccess; }