Recently at home in the evening to see algorithms,4th Edition, I bought the English version, think this book is relatively easy to understand, and "figure Code and Mao", take advantage of this opportunity to learn to do notes, this will also be impressive, this is the reason for writing this series of articles. In addition, Princeton University also has this book in the Coursera of the Open class, there is another algorithm analysis course, the author of this course is also the author of this book, both courses are very good.
The computer program is inseparable from the algorithm and data structure, this paper introduces the stack (stack) and the implementation of queues (queue). NET in the data structure, typical applications, and so on, hoping to deepen their understanding of these two simple data structures.
1.
Basic Concepts
The concept is simple, the stack is a OFF,LIFO data structure, and the queue is a first-in, fisrt (OUT,FIFO) structure, such as:
2.
Implement
Now see how to implement the above two data structures. Before doing so, the Framework design guidelines This book tells us that when designing an API or entity class, the API specification should be written around the scene.
1.1 Stack the implementation
Stack is a last-in-first-out data structure, and for stack we want to provide at least the following methods:
Stack<t> () |
Create an empty stack |
void Push (T s) |
Add a new element to the stack |
T Pop () |
Remove and return the most recently added element |
Boolean IsEmpty () |
Whether the stack is empty |
int Size () |
The number of elements in the stack |
To implement these functions, we have two methods, arrays and linked lists, which look at the list implementation:
The chain list implementation of the stack:
We first define an inner class to hold the node for each linked list, which includes the current value and points to the next value, and then establishes a node that holds the value at the top of the stack and the number of elements in the record stack;
Class node{public T item{get;set;} Public Node Next {get; set;}} Private Node first = null;private int number = 0;
Now to implement the Push method, which pushes an element to the top of the stack, first saves the original element at the top of the stack, creates a new top element of the stack, and then points the next element to the top of the stack. The entire pop process is as follows:
The implementation code is as follows:
void Push (T node) { node oldfirst = first; First = new Node (); First. item= node; First. Next = Oldfirst; number++;}
The Pop method is also very simple, first save the value of the top element of the stack, and then set the top element of the stack to the next element:
T Pop () { T item = first. Item; First = first. Next; number--; return item;}
A linked list-based stack implementation, in the worst case, requires only constant time for push and pop operations.
Array implementations of Stacks:
We can use the array to store the elements in the stack when push, add an element directly s[n] to the array, the pop directly back to s[n-1].
First, we define an array and then, given the initialization size in the constructor, the push method implements the following, which is the addition of an element in the collection:
T[] Item;int number = 0;public Stackimplementbyarray (int capacity) { item = new t[capacity];} public void Push (T _item) { if (number = = Item. Length) Resize (2 * item. Length); item[number++] = _item;}
Pop method:
Public T Pop () { T temp = item[--number]; Item[number] = default (T); if (number > 0 && Number = = Item. LENGTH/4) Resize (item. LENGTH/2); return temp;}
In the push and pop methods, in order to save memory space, we will organize the array. When pushing, when the number of elements reaches the capacity of the array, we open up a new array of twice times the current element, and then copy the elements from the original array into the new array. Pop, when the number of elements is less than 1/4 of the current capacity, we reduce the size capacity of the original array by 1/2.
The Resize method is basically an array copy:
private void Resize (int capacity) { t[] temp = new t[capacity]; for (int i = 0; i < item. Length; i++) { Temp[i] = Item[i]; } item = temp;}
When we narrow the array, we use the case of judging 1/4, so that the efficiency is higher than 1/2, because it can effectively avoid inserting, deleting, inserting, deleting in 1/2 attachments, so as to enlarge and narrow the array frequently. Shows how the elements in the array and the size of the array change in the case of insertions and deletions:
Analysis:
1. In the worst case, the pop and push operations are proportional to the number of elements of n time, the time is mainly spent on the expansion or reduction of the number of arrays, the array copy.
2. The elements are compact in memory, high density, easy to take advantage of memory time and spatial locality, easy to cache the CPU, less linklist memory consumption, high efficiency.
2.2 Queue the implementation
Queue is an advanced first-out data structure, like a stack, he also has a list and array of two implementations, understanding the implementation of the stack, the implementation of the queue is relatively simple.
Stack<t> () |
To create an empty queue |
void Enqueue (T s) |
Add a new element to the queue |
T Dequeue () |
Remove the oldest added element in a queue |
Boolean IsEmpty () |
Whether the queue is empty |
int Size () |
Number of elements in the queue |
First look at the implementation of the linked list:
The Dequeue method is to return the first element in the list, similar to the Pop method in the stack:
Public T Dequeue () { T temp = first. Item; First = first. Next; number--; if (Isempety ()) Last = null; return temp;}
Unlike the push method of stack, Enqueue adds a new element at the end of the list:
public void Enqueue (T item) { Node oldlast = last; last = new Node (); Last. item = Item; if (Isempety ()) {First = last; } else { oldlast.next = last; } number++;}
Again, now look at how to use arrays to implement queue, first we use arrays to hold data, and define variables head and tail to record the end-to-end elements of a queue.
Unlike stack implementations, in the queue, we define head and tail to record header and tail elements. When Enqueue, the Tial plus 1, the element is placed in the tail, when Dequeue, head minus 1, and return.
public void Enqueue (T _item) { if ((head-tail + 1) = = Item. Length) Resize (2 * item. Length); item[tail++] = _item;} Public T Dequeue () { T temp = item[--head]; Item[head] = default (T); if (Head > 0 && (tail-head + 1) = = Item. LENGTH/4) Resize (item. LENGTH/2); return temp;} private void Resize (int capacity) { t[] temp = new t[capacity]; int index = 0; for (int i = head; i < tail; i++) { Temp[++index] = Item[i]; } item = temp;}
3.
Stack
and
Queue
in
. NET
There are stack and queue generic classes in. NET, and you can use the Reflector tool to see its implementation. First look at the implementation of the stack, the following is the interception of some of the code, only the Push,pop method is listed, the other way you want to use Reflector view:
The
can be seen. The implementation of the stack in net is similar to what we wrote earlier, and is implemented using arrays. NET in the initial capacity of the stack is 4, in the push method, you can see when the number of elements reached the array length, the expansion of twice times the capacity, and then copy the original array into the new array. The Pop method is basically the same as the one we implemented earlier, and here is the code, which only captures the part:
[Serializable, ComVisible (false), DebuggerTypeProxy (typeof (System_stackdebugview<>)), DebuggerDisplay (" Count = {count} "), __dynamicallyinvokable]public class stack<t>: Ienumerable<t>, ICollection, ienumerable{ Fields Private t[] _array; Private Const int _defaultcapacity = 4; private static t[] _emptyarray; private int _size; private int _version; Methods static Stack () {Stack<t>._emptyarray = new t[0]; } [__dynamicallyinvokable] public Stack () {This._array = stack<t>._emptyarray; this._size = 0; this._version = 0; } [__dynamicallyinvokable] public Stack (int capacity) {if (capacity < 0) {Throwhelp Er. Throwargumentoutofrangeexception (exceptionargument.capacity, Exceptionresource.argumentoutofrange_ neednonnegnumrequired); } This._array = new t[capacity]; this._size = 0; this._version = 0; } [__dynamicallYinvokable] public void CopyTo (t[] array, int arrayindex) {if (array = = null) {Throwhelpe R.throwargumentnullexception (Exceptionargument.array); } if ((Arrayindex < 0) | | (Arrayindex > Array. Length) {throwhelper.throwargumentoutofrangeexception (Exceptionargument.arrayindex, Exceptionresource. Argumentoutofrange_neednonnegnum); } if (array. Length-arrayindex) < This._size) {throwhelper.throwargumentexception (exceptionresource.argument_in Validofflen); } array.copy (This._array, 0, Array, arrayindex, this._size); Array.reverse (Array, arrayindex, this._size); } [__dynamicallyinvokable] public T Pop () {if (this._size = = 0) {Throwhelper.throwinva Lidoperationexception (Exceptionresource.invalidoperation_emptystack); } this._version++; T local = This._array[--this._size]; This._array[this._size] = DefaUlt (T); return local; } [__dynamicallyinvokable] public void Push (T item) {if (this._size = = This._array. Length) {t[] Destinationarray = new t[(This._array. Length = = 0)? 4: (2 * this._array. Length)]; Array.copy (This._array, 0, Destinationarray, 0, this._size); This._array = Destinationarray; } this._array[this._size++] = Item; this._version++; }//Properties [__dynamicallyinvokable] public int Count {[__dynamicallyinvokable, TARGETEDPATC Hingoptout ("Performance critical to-inline this type of method across NGen image boundaries")] get { return this._size; } }}
Next look at the implementation of the queue:
[Serializable, DebuggerDisplay ("Count = {count}"), ComVisible (false), DebuggerTypeProxy (typeof (System_ queuedebugview<>)), __dynamicallyinvokable] public class queue<t>: Ienumerable<t>, ICollection, IEn umerable {//Fields private t[] _array; Private Const int _defaultcapacity = 4; private static t[] _emptyarray; private int _head; private int _size; private int _tail; private int _version; Methods static Queue () {Queue<t>._emptyarray = new t[0]; } public Queue () {This._array = queue<t>._emptyarray; } public Queue (int capacity) {if (capacity < 0) {Throwhelper.throw ArgumentOutOfRangeException (exceptionargument.capacity, Exceptionresource.argumentoutofrange_ neednonnegnumrequired); } This._array = new t[capacity]; This._head = 0; This._tail = 0; this._size = 0; } public T Dequeue () {if (this._size = = 0) {Throwhelper.throwinvalido Perationexception (Exceptionresource.invalidoperation_emptyqueue); } T local = This._array[this._head]; This._array[this._head] = default (T); This._head = (this._head + 1)% This._array. Length; this._size--; this._version++; return local; public void Enqueue (T item) {if (this._size = = This._array. Length) {int capacity = (int) ((This._array). Length * 200L)/100L); if (Capacity < (This._array. Length + 4) {capacity = This._array. Length + 4; } this. Setcapacity (capacity); } This._array[this._tail] = Item; This._tail = (this._tail + 1)% This._array. Length; this._size++; this._version++; } private void setcapacity (int capacity) {t[] Destinationarray = new T[capacity]; if (This._size > 0) {if (This._head < This._tail) {ARR Ay. Copy (This._array, This._head, Destinationarray, 0, this._size); } else {array.copy (This._array, This._head, Destinationarray, 0, This._a Rray. Length-this._head); Array.copy (This._array, 0, Destinationarray, This._array. Length-this._head, This._tail); }} This._array = Destinationarray; This._head = 0; This._tail = (This._size = = capacity)? 0:this._size; this._version++; } public int Count {[__dynamicallyinvokable, Targetedpatchingoptout (' performance critical to INL Ine this type of method across NGen image bOundaries ")] get {return this._size; } } }
Can see. NET in the implementation of the queue is also based on the array, defines the head and tail, when the length of the array to reach the capacity of the time, the use of the Setcapacity method for expansion and copying.
4.
Stack
and
Queue
applications
The data structure of the stack is widely used, such as the parser in the compiler, the Java Virtual Machine, the undo operation in the software, the fallback operation in the browser, the function call implementation in the compiler, and so on.
4.1-wire heap (thread Stack)
The line heap is an area of memory allocated by the operating system. Typically there is a special register (stack pointer) on the CPU that is called a heap pointer. When the program initializes, the pointer points to the top of the stack and the top of the stack has the largest address. The CPU has special instructions to push the value onto the thread heap, and the value pops out of the stack. Each push operation places the value where the heap pointer is pointing and decrements the heap pointer. Each pop removes the value that the heap pointer points to from the heap, and the heap pointer increments, and the heap grows downward. Push-to-line heap, and the values of POPs from the thread heap, are stored in the registers of the CPU.
When a function call is initiated, the CPU uses a special instruction to press the current instruction pointer (instruction pointer), such as the address of the currently executing code, into the heap. The CPU then jumps to the called function to execute by setting the instruction pointer to the address of the function call. When the function returns a value, the old instruction pointer pops out of the heap, and then resumes execution from the address of the instruction.
When entered into the called function, the heap pointer is reduced to allocate more space on the heap for local variables in the function. If a 32-bit variable is assigned to the heap in the function, when the function returns, the heap pointer returns to the previous function call and the allocated space is freed.
If the function has parameters, these parameters are allocated on the heap before the function call, and the code in the function can access the parameters from the current heap.
The line heap is a limited amount of memory space, and if too many nested functions are called, or if the local variable allocates too much memory space, a stack overflow error occurs.
The change of line heap is simply shown.
4.2 Evaluation of an arithmetic expression
One of the most classic examples of stack use is the evaluation of an arithmetic expression, which also includes the evaluation of the prefix expression and the suffix expression. E. Dijkstra invented the use of two stacks, a save operation value, a method of saving operators to achieve the evaluation of the expression, the following steps:
1) push to the stack that belongs to the value when the value is entered.
2) When the operator is entered, push to the stack of the operator.
3) When opening parenthesis is encountered, ignore
4) When the closing parenthesis is encountered, pop an operator, pop two values, and then push the result of the calculation to the stack of values.
Here is the evaluation of a simple parenthesis expression in C #:
<summary>///a simple expression operation///</summary>///<param name= "args" ></param>static void Main ( String[] args) { stack<char> operation = new stack<char> (); stack<double> values = new stack<double> (); For convenience, use ToChar directly for double-digit array problems char[] Chararray = Console.ReadLine (). ToCharArray (); foreach (char s in Chararray) { if (s.equals (') ') {} else if (s.equals (' + ')) operation. Push (s); else if (s.equals (' * ')) operation. Push (s); else if (s.equals (') ')) { char op = operation. Pop (); if (Op. Equals (' + ')) values. Push (values. Pop () + values. Pop ()); else if (op. Equals (' * ')) values. Push (values. Pop () * values. Pop ()); } else values. Push (Double.Parse (s.tostring ())); } Console.WriteLine (values. Pop ()); Console.readkey ();}
The results of the operation are as follows:
Demonstrates the changes in the Operation Stack and the data stack.
In compiler technology, the prefix expression, the evaluation of the suffix expression, is used in the heap.
graphical drawing in 4.3 object-c and OpenGL
There are "drawing contexts" in both Object-c and OpenGL, and sometimes we don't want to affect the global settings for the drawing of the local object, so we need to save the last drawing state. The following is a typical code for drawing a circle in Object-c:
-(void) Drawgreencircle: (cgcontextref) ctxt { uigraphicspushcontext (ctxt); [[Uicolor Greencolor] setfill]; Draw My Circle uigraphicspopcontext ();} -(void) DrawRect: (CGRect) arect { Cgcontextref context = Uigraphicsgetcurrentcontext (); [[Uicolor Redcolor] setfill]; Do some stuff [self drawgreencircle:context]; Do more stuff and expect fill color to be red}
As you can see, in the Drawgreencircle method, before we set the fill color, we push to save the information of the drawing context, and then we set some environment variables for the current operation, draw the graph, and after the drawing is done, we pop out the previously saved drawing context information, This does not affect the subsequent drawing.
4.4 Some other scenarios
One scenario is to use a stack to process extra invalid requests, such as a user pressing the keyboard, or pressing a function key continuously for a short period of time, and we need to filter these invalid requests. A common practice is to press all requests into the heap and then to process the pop out one, which is the latest request.
Application of queue
In real life, the application of the queue is also very extensive, the most extensive is the queue, "first served" first come first service, and the word queue the queue meaning.
There are, for example, playlists on our player, our data stream objects, asynchronous transmission structures (file IO, pipeline communication, sockets, etc.)
There are also some workarounds for conflicting access to shared resources, such as print queues for printers. Message Queuing, and so on. Traffic simulation, call center user wait time simulation and so on.
5. a
little bit of sentiment
This paper briefly introduces the principle and implementation of stack and queue, and introduces some applications.
The last bit of sentiment is not to use data structures in order to use data structures. For example, before I saw an array reversal problem, just learned that stack might think, this simple ah, directly the string to push in, and then pop out on it, perfect solution. However, this is not the most effective, in fact, there is a more effective way, that is, in the middle of the fold, and then left and right side to replace.
public static void Reverse (int[] array, int begin, int end) {while (end > Begin) { int temp = Array[begin]; Array[begin] = array[end]; Array[end] = temp; begin++; end--;} }
"Reprint" talking about algorithms and data structures: a stack and a queue