data structures - part ii
DESCRIPTION
Data Structures - Part II. CS215 Lecture #8. Stacks. Last-In-First-Out (LIFO) Stacks are often used in programming when data will need to be used in the reverse order A stack has two operations: push (places a new item at the top of the stack) pop (retrieves the top item from the stack). - PowerPoint PPT PresentationTRANSCRIPT
1
Data Structures - Part II
CS215 Lecture #8
Stacks
Last-In-First-Out (LIFO) Stacks are often used in
programming when data will need to be used in the reverse order
A stack has two operations: push (places a new item at the top of
the stack) pop (retrieves the top item from the
stack)
Stack Implementation
A stack can be implemented in several ways, e.g., by an array or by a linked list.
A stack pointer is a variable that identifies the beginning of the part of the array that is currently unused
A stack pointer variable, therefore, holds an address.
Declaring a Stack
stack: .word 0:maxstacksize
sp: .word stack
or
stack: .word 0:maxstacksize
sp: .word
. . .
la sp, stack
sp uses the addressbound to the label stack
as the initial value (staticinitialization)
dynamic initialization
Some variations to the implementation In some implementations, a stack pointer
points to the top of the stack In our sample implementation, the stack
pointer points to the next available space we can push an element into
A stack can be grown upward or downward, i.e., we can either add a value to the stack pointer or subtract a value from it when pushing an element
Pushing an element unto the stack
This operation is called push.
move M[sp],x
add sp,sp,4
Example 16.1
sp
initially
sp
move M[sp],20add sp,sp,4
20
Example 16.2
20 5 move M[sp],5add sp,sp,4
sp
sp
move M[sp],10 add sp,sp,4
20 5 10
Taking an item off the stack
This operation is called pop.
add sp,sp,-4
move x,M[sp]
Example 16.3
sp
initially20 5 10
sp
add sp,sp,-4 move x,M[sp]
20 5 10
Note: popping the stack means moving back the pointer to the next available space. There is no need to explicitly delete the data. It will
get overwritten in the next push.
Full and Empty Stack
SAL does not provide any form of boundary check, i.e., there is no automatic feature that detects whether an element is “pushed” beyond the stack’s capacity
A robust program must check before a push whether the stack is full.
It should also check whether the stack is empty before a pop.
Example 16.4
P.187 textbook
.data
stack: .byte 0:50
sp: .word stack #static initialization
bottom: .word stack
bias: .word 48
top: .word
number: .word
digit: .word
push_error: .asciiz “Full stack failure “
pop_error: .asciiz “Empty stack failure “
Example 16.4
P.187 textbook
add top,bottom,50
loop_top: rem digit,number,10
add digit,digit,bias
bge sp,top,bad_push #check if full
move m[sp],digit
add sp,sp,1
div number,number,10
bgtz number,loop_top
bad_push: puts push_error
done
Example 16.5
P.187 textbook
print_it: blt sp,bottom,bad_pop #empty check
add sp,sp,-1
putc m[sp]
bgt sp,bottom,print_it
done
bad_pop: puts pop_error
done
Queues
A queue is a data structure that maintains a “first-in first-out” (FIFO) ordering.
In contrast, a stack maintains a “last-in first-out” (LIFO) ordering.
A queue adds new elements at the end. An element can only be removed at the front.
This is an abstraction of the “first-come first-served” practice.
Queue operations
A queue has two operations: enqueue dequeue
An enqueue operation adds new elements at the end of the queue or its tail. This is similar to the stack operation push; only that push now is done at the end of the array instead of at the front (or top).
A dequeue operation removes an element from the front of the array or its head.
Implementation
A queue can be implemented using an array.
A naïve implementation will allow the enqueued data to “walk” through the array.
tailhead
A circular queue
An array can be reused by allowing the enqueued data to “walk around” the array. This type of implementation is called a circular queue.
tailhead
The particular implementation that will be illustrated here uses an empty element. This will simplify the check for a full or empty queue.
If the queue is empty, the dequeue operation must not return an invalid element.
If the queue is full, the enqueue operation must not destroy an element already in the queue.
Adding elements (Enqueue)
To add one element, we simply add 1 to the the current value of the tail, then execute:
add address,base,tail move m[address],new_element #copy element into
array0 1
0 1 2
tailhead
B
B = blank cell
Removing Elements (Dequeue)
To delete one element, add 1 to the current value of head then we execute
add address,base,head move element,m[address] #copy it to element
tailhead
0 1 2
B
0 1 2
B
Detecting an Empty Queue
During a dequeue, the first thing that is done is to check whether (head == tail). If this is true, then the queue is empty.
tailhead
0 1 2
B
0 1 2
B
not empty
empty
Detecting a Full Queue
Before an enqueue is done, the tail is incremented by 1. If after the increment (head == tail) then the queue is full.
0 1 2
B
0 1 2
B
tailhead
before increment
after increment
full queuedetected
Making the address circular
Suppose we have the array
To make the address 4 equal to the address 0 we simply use modulo arithmetic, i.e., 4 modulo 4 = 0.
0 1 2 3 4
Using modulo arithmetic maps any address to an address within the allocated space, thus preventing access to out-of-the-range addresses
Also, the modulo conveniently gives us the offset from the base address.
We need the offset from the base address to access specific elements in the queue.
Example 17.1
.data
queue: .byte 0:4
queueaddr: .word
head: .word
tail: .word
linenumber: .byte
nextline: .byte
addr: .word
newline: .byte
string1: .asciiz "Which line is ringing ?"
string2: .asciiz "The next line to be answered is "
string3: .asciiz "Enqueueing line "
empty: .asciiz "No calls waiting "
full: .asciiz "ERROR: Queue is full. Exiting program. "
Example 17.2
.text
__start: la queueaddr,queue
loop: puts string1
get linenumber
beq linenumber,'\n',dequeue
get newline #reads the second character
enqueue: add tail,tail,1
rem tail,tail,4 #uses modulo beq tail,head,full_queue
puts string3
put linenumber
put '\n'
add addr,queueaddr,tail
move m[addr],linenumber
b loop
Example 17.3
dequeue: beq head,tail,empty_queue
add head,head,1
rem head,head,4 #uses modulo add addr,queueaddr,head
move nextline,m[addr]
puts string2
put nextline
put '\n'
b loop
empty_queue: puts empty
put '\n'
b loop
full_queue: puts full
done