Data structure Chapter 3 (stack and queue)

Posted by codrgii on Sat, 22 Jan 2022 02:10:25 +0100

1. Definition and characteristics of stack and queue

1.1 definition and characteristics of stack

  • stack: a linear table that is restricted to insert or delete operations only at the end of the table
    • top: end of table
    • bottom of stack: header end
    • Empty stack: an empty table without elements
    • **Linear table of Last In First Out (LIFO) * *: the first element off the stack is the top element

1.2 definition and characteristics of queue

  • queue: * * linear table of First In First Out (FIFO) * *
    • Inserts are allowed only at one end of the table and elements are deleted at the other end
    • rear: the end that allows insertion
    • front: the end that can be deleted

2. Representation of stack and implementation of operation

2.1 representation and implementation of sequential stack

  • Stack realized by sequential storage structure
  • A group of continuous storage units are used to store the data elements from the bottom of the stack to the top of the stack, and the pointer top is attached to indicate the position of the top element in the sequential stack
#define MAXSIZE 5

typedef struct {
    SElemType *base;						// Stack bottom pointer
    SElemType *top;							// Stack top pointer
    int stacksize;							// Maximum available capacity of stack
} SqStack;

2.1.1 initialization

  • step
    • Dynamically allocate an array space with maximum capacity of MAXSIZE for the sequential stack, and make the base point to the base address of this space
    • The stack top pointer top is initially base, indicating that the stack is empty
    • Set stacksize to max size of stack
void InitStack(SqStack* S) {
	S->base = (ABC*)malloc(MAXSIZE * sizeof(ABC));
    
	S->top = S->base;
    
	S->stacksize = MAXSIZE;
    
	printf("Init Success\n");
}

2.1.2 stacking

  • step
    • Push the new element to the top of the stack
    • Stack top pointer plus 1
void Push(SqStack* S, ABC abc) {
	*S->top = abc;
	S->top++;
	printf("Push Success\n");
}

2.1.3 out of stack

  • step
    • Stack top pointer minus 1
    • Stack top element out of stack
void Pop(SqStack* S) {						// Stack top space is not released
	S->top--;
	printf("Pop Success\n");
}

2.1.4 stack top elements

void GetTop(SqStack S) {
	ABC* abc = S.top - 1;
	printf("first = %d, second = %d\n", abc->first, abc->second);
	printf("Get Success\n");
}

Test code

#include <stdio.h>
#include <stdlib.h>

#define MAXSIZE 5

void InitStack(SqStack);
void Push(SqStack, ABC);
void Pop(SqStack);
void GetTop(SqStack);

typedef struct {
	int first;
	int second;
} ABC;

typedef struct {
	ABC* top;
	ABC* base;
	int stacksize;
} SqStack;

int main() {
	SqStack S;
	ABC a;
	ABC b;
	ABC c;

	a.first = 0;
	a.second = 0;
	b.first = 1;
	b.second = 1;
	c.first = 2;
	c.second = 2;

	InitStack(&S);
	printf("****************\n");

	Push(&S, a);
	Push(&S, b);
	Push(&S, c);
	GetTop(S);
	printf("****************\n");

	Pop(&S);
	printf("first = %d\n", (S.top)->first);
	printf("****************\n");

	GetTop(S);
	printf("****************\n");
}

void InitStack(SqStack* S) {
	S->base = (ABC*)malloc(MAXSIZE * sizeof(ABC));
	S->top = S->base;
	S->stacksize = MAXSIZE;
	printf("Init Success\n");
}

void Push(SqStack* S, ABC abc) {
	*S->top = abc;
	S->top++;
	printf("Push Success\n");
}

void Pop(SqStack* S) {						// Stack top space is not released
	S->top--;
	printf("Pop Success\n");
}

void GetTop(SqStack S) {
	ABC* abc = S.top - 1;
	printf("first = %d, second = %d\n", abc->first, abc->second);
	printf("Get Success\n");
}

2.2 representation and implementation of chain stack

  • Stack realized by chain storage structure
  • Usually, the chain stack is represented by a single chain list
typedef struct StackNode {
    ElemType data;
    struct StackNode *next;
} StackNode, *LinkStack;
  • Because the main function of the stack is to insert and delete at the top of the stack, it is convenient to use the head of the linked list as the top of the stack, and there is no need to add a head node for operation convenience like a single linked list

2.2.1 initialization

void InitStack(LinkStack* S) {
	*S = (LinkStack)malloc(sizeof(StackNode));
	(*S)->stacksize = 0;
	(*S)->next =  NULL;
	printf("Init Success\n");
}

2.2.2 stacking

  • step
    • Allocate space for the stacked element e and point to it with the pointer p
    • Set the new node data field to e
    • Insert a new node into the top of the stack
    • Change the stack top pointer to p
void Push(LinkStack* S, ABC abc) {
	LinkStack new = (LinkStack)malloc(sizeof(StackNode));
    
	new->abc = abc;
    
	new->next = (*S)->next;
    
	(*S)->next = new;
    
	(*S)->stacksize++;
	printf("Push Success\n");
}

2.2.3 out of stack

  • step
    • Assign the stack top element to e
    • Temporarily save the space of the top element of the stack for release
    • Modify the stack top pointer to point to the new stack top element
    • Free up space for original stack top elements
void Pop(LinkStack* S) {
	StackNode* node = (*S)->next;
    
	(*S)->next = node->next;
    
	(*S)->stacksize--;
    
	free(node);
    
	printf("Pop Success\n");
}

2.2.4 stack top elements

void GetTop(LinkStack S) {
	ABC abc = S->next->abc;
	printf("first = %d, second = %d\n", abc.first, abc.second);
	printf("Get Success\n");
}

Test code

#include <stdio.h>
#include <stdlib.h>

void InitStack(LinkStack);
void Push(LinkStack, ABC);
void Pop(LinkStack);
void GetTop(LinkStack);

typedef struct {
	int first;
	int second;
} ABC;

typedef struct StackNode{
	ABC abc;
	int stacksize;
	struct StackNode* next;
} StackNode, *LinkStack;

int main() {
	LinkStack S;
	ABC a;
	ABC b;
	ABC c;

	a.first = 0;
	a.second = 0;
	b.first = 1;
	b.second = 1;
	c.first = 2;
	c.second = 2;

	InitStack(&S);
	printf("****************\n");

	Push(&S, a);
	Push(&S, b);
	Push(&S, c);
	GetTop(S);
	printf("****************\n");

	Pop(&S);
	printf("****************\n");

	GetTop(S);
	printf("****************\n");
}

void InitStack(LinkStack* S) {
	*S = (LinkStack)malloc(sizeof(StackNode));
	(*S)->stacksize = 0;
	(*S)->next =  NULL;
	printf("Init Success\n");
}

void Push(LinkStack* S, ABC abc) {
	LinkStack new = (LinkStack)malloc(sizeof(StackNode));
	new->abc = abc;
	new->next = (*S)->next;
	(*S)->next = new;
	(*S)->stacksize++;
	printf("Push Success\n");
}

void Pop(LinkStack* S) {
	StackNode* node = (*S)->next;
	(*S)->next = node->next;
	(*S)->stacksize--;
	free(node);
	printf("Pop Success\n");
}

void GetTop(LinkStack S) {
	ABC abc = S->next->abc;
	printf("first = %d, second = %d\n", abc.first, abc.second);
	printf("Get Success\n");
}

3. Stack and recursion

3.1 problems solved by recursive algorithm

  • Recursion: the application of the definition itself directly (or indirectly) within the definition of a function, procedure or data structure

3.1.1 definitions are recursive

  • Factorial function
    F a c t ( n ) = { 1 n = 0 n ∗ F a c t ( n − 1 ) n > 0 Fact(n)=\begin{cases} 1 \quad\quad\quad\quad\quad\quad\quad\quad n=0 \\ n*Fact(n-1)\quad\quad n>0\end{cases} Fact(n)={1n=0n∗Fact(n−1)n>0​

    int Fact(int i) {
        if (i == 0) {
            return 0;
        } else {
            return i*Fact(i-1);
        }
    }
    
  • Second order Fibonacci sequence
    F i b ( n ) = { 1 n = 1 or n = 2 F i b ( n − 1 ) + F i b ( n − 2 ) his he feeling shape FIB (n) = \ begin {cases} 1 \ Quad \ Quad \ Quad \ Quad \ Quad \ Quad \ Quad \ Quad \ Quad n = 1 or n=2 \ Fib(n-1)+Fib(n-2)\quad\quad other situations \ end{cases} Fib(n)={1n=1 or n=2Fib(n − 1)+Fib(n − 2) other situations

    int Fib(int i) {
        if (i == 1 || i == 2) {
            return 1;
        } else {
            return Fib(n-1) + Fib(n-2);
        }
    }
    
  • Recursive solution: decompose the complex problem into several relatively simple sub problems with the same or similar solution

  • Divide and Conquer: decomposition solution strategy

    • Service conditions
      • It can transform a problem into a new problem, and the solution of the new problem is the same or similar to that of the original problem. The only difference is the processing objects, and these processing objects are smaller and change regularly
      • The problem can be simplified through the above transformation
      • There must be an explicit recursive exit, or recursive boundary

3.1.2 the data structure is recursive

  • Linked list is a recursive data structure

  • Recursive algorithm for traversing each node in the output linked list

    • step
      • If p is NULL, the recursive end returns
      • Otherwise, p - > data is output, and p points to the subsequent node to continue recursion
    void TraverseList(LinkList p) {
        if (p == NULL) {
            return 0;
        } else {
            printf("data = %d\n", p->data);
            TraverseList(p->next);
        }
    }
    
    // Can be simplified to
    
    void TraverseList(LinkList p) {
        if (p) {
            printf("data = %d\n", p->data);
            TraverseList(p->next);
        }
    }
    

3.1.3 the solution of the problem is recursive

  • Hanoi Tower problem, eight queens problem, maze problem, etc

  • Recursive algorithm for Hanoi Tower problem

    • step
      • If n=1, move the disks numbered 1 to n-1 on A to B and C as the auxiliary tower
      • Move the disc numbered n directly from A to C
      • Recursion, move the disks numbered 1 to n-1 on B to C and A as auxiliary towers
    #define count 0;
    
    void Hanoi(int n, char A, char B, char C) {
        if (n == 1) {
            move(A, 1, C);
        } else {
            Hanoi(n-1, A, C, B);
            move(A, n, C);
            Hanoi(n-1, B, A, C);
        }
    }
    
    void move(char A, int n, char C) {
        printf("%d, %d, %c, %c", ++count, n, A, C);
    }
    

3.2 recursive process and recursive work stack

  • The link and information exchange between the calling function and the called function need to be carried out through the stack

  • When a function is called while another function is running

    • Before running the called function, the system needs to complete three things
      • Pass all arguments, return addresses and other information to the called function for saving
      • Allocate storage for the called local variable
      • Transfer control to the entry of the called function
    • The system should also complete three things before returning from the called function to the calling function
      • Save the calculation results of the called function
      • Release the data area of the called function
      • Transfer control to the calling function according to the return address saved by the called function
  • When multiple functions form nested calls, the information transfer and control transfer between these functions must be implemented through "stack" according to the principle of "post invocation first return".

    • The system arranges the data space required for the operation of the whole system in a stack
    • Whenever a function is called, it is allocated a storage area at the top of the stack
    • Each time you exit a function, its storage area is released
    • The data area of the currently running function must be at the top of the stack
  • In order to ensure the correct execution of recursive functions, the system needs to set up a "recursive work stack" as the data storage area used during the operation of the whole recursive function

    • The information required for recursion in each layer constitutes a working record, including all arguments, all local variables, and the return address of the previous layer

    • Each time you enter a layer of recursion, a new work record is generated and pushed into the top of the stack

    • Every time you exit a layer of recursion, a work record pops up from the top of the stack

    • The work record of the current execution layer must be the work record at the top of the recursive work stack, which is called "activity record"

3.3 efficiency analysis of recursive algorithm

3.3.1 analysis of time complexity

  • Take Fact(n) as an example
    T ( n ) = { O ( 1 ) n = 0 O ( 1 ) + T ( n − 1 ) n ≥ 1 T(n)=\begin{cases} O(1) \quad\quad\quad\quad\quad\quad\quad n=0 \\ O(1) + T(n-1)\quad\quad n\geq1\end{cases} T(n)={O(1)n=0O(1)+T(n−1)n≥1​
    Let n > 2, expand T(n-1) by using the above formula
    T ( n − 1 ) = O ( 1 ) + T ( n − 2 ) T(n-1)=O(1)+T(n-2) T(n−1)=O(1)+T(n−2)
    Brought into T(n) = O(1) + T(n-1)
    T ( n ) = 2 O ( 1 ) + T ( n − 2 ) T(n)=2O(1)+T(n-2) T(n)=2O(1)+T(n−2)
    Similarly, when n > 3
    T ( n ) = 3 O ( 1 ) + T ( n − 3 ) T(n)=3O(1)+T(n-3) T(n)=3O(1)+T(n−3)
    And so on, when n > I
    T ( n ) = i O ( 1 ) + T ( n − i ) T(n)=iO(1)+T(n-i) T(n)=iO(1)+T(n−i)
    When i = n
    T ( n ) = n O ( 1 ) + T ( 0 ) = ( n + 1 ) O ( 1 ) T(n)=nO(1)+T(0)=(n+1)O(1) T(n)=nO(1)+T(0)=(n+1)O(1)
    The solution of the recursive equation is: T(n) = O(n)

  • The time complexity of recursive algorithms for Fibonacci sequence problem is O(2^n)

  • The time complexity of recursive algorithms for Hanoi Tower problem is O(2^n)

3.3.2 analysis of spatial complexity

  • Spatial complexity
    S ( n ) = O ( f ( n ) ) S(n)=O(f(n)) S(n)=O(f(n))
    f(n) is the functional relationship between the number of work records in the "recursive work stack" and the problem scale n

  • The space complexity of recursive algorithms for factorial problems is O(n)

  • The space complexity of the recursive algorithm for Fibonacci sequence problem is O(n)

  • The space complexity of the recursive algorithm for Hanoi Tower problem is O(n)

3.4 using stack to convert recursion to non recursion

  • step
    • Set a work stack texturing recursive work record (including arguments, return addresses, local variables, etc.)
    • Enter the non recursive call entry (at the beginning of the called program) and put the actual parameters and return address passed by the calling program on the stack
    • Enter recursive call entry
      • When the end condition of recursion is not satisfied, recurse layer by layer and put the arguments, return addresses and local variables on the stack (this process can be realized by circular statements)
      • When the recursion end condition is satisfied, the given constant reaching the recursion exit is taken as the current function value
    • Return processing
      • When the stack is not empty, exit the stack top record repeatedly and perform the operation specified in the title according to the return address in the record
        • Calculate the current function value layer by layer until the stack is empty

Topics: data structure