(Detailed explanation) data structure ----------- stack and queue C language implementation

This chapter will explain the following knowledge points in detail:

Table of contents

One: stack

        1: The definition of the stack, the characteristics of the stack

        2: What structure is used to realize the analysis of the stack and the reason?

        3: (Super detailed explanation) common interface of the stack and attach test cases

Two: Queue

        1: Definition of queue, characteristics of queue

        2: What structure is used to realize the analysis of queues and reasons?

        3: (super detailed explanation) the interface of the queue and attach test cases


The text begins~:-^-

        1: The definition and characteristics of the stack

        First of all, the stack is a special linear table , which can only perform insertion and deletion operations at one end . The end for data deletion and insertion is called the top of the stack , and the other end is the bottom of the stack . The data elements in the stack follow the last-in-first-out principle.

        The following is the name of the operation of the stack:

                Push stack: Indicates that data elements are inserted into the stack, also known as push/push. The incoming data is at the top of the stack .

                Pop-up: represents the operation of deleting data elements in the stack. The output data is on the top of the stack .

The characteristics                 of the stack : data elements can only be inserted and deleted at one end of the stack, and the data elements follow the principle of last in first out .

        2: What structure is used to implement the stack and the reason?

After comparing the advantages and disadvantages of the sequence table and the linked list, we prefer to use the sequence table to implement the data structure of the stack         when implementing the stack . Of course, it can also be implemented using a linked list.

        Reason: According to the first-in-last-out feature of the stack, it can only insert and delete data elements at the top of the stack.

        And our sequence table is very suitable for it. The time complexity of tail insertion and tail deletion of the sequence table is O(1), and the cache utilization rate of the sequence table is faster than that of the linked list, so the structure of the sequence table is better than the linked list.

         3: (detailed) common interface of the stack

        The common operations of the stack are: stack initialization, stack destruction, stack push, stack pop, and get the value of the top element of the stack

        Interpretation of whether the stack is empty, the size of the stack

        Before explaining these interfaces, let's first explain the definition of the stack

        Contains three member variables, S is the first address of our open space, top is used to record the number of elements, and is the next position of the last element in the array, capacity is used to indicate the size of the array space capacity.

        code show as below:

//动态的顺序栈
typedef int STDataType;

typedef struct Stack
{
	STDataType* S;
	int top;//用来表示栈中最后一个元素的下一个元素的位置
	int capacity;
}ST;

        1: Initialization of the stack: make all the values ​​​​of the three member variables empty or 0

        code show as below:

void InitStack(ST* ps)
{
	assert(ps);
	ps->capacity = 0;
	ps->top = 0;
	ps->S = NULL;
}

        2: Stack push operation: Before each push to the stack, we need to judge whether the space is sufficient, and then directly use the feature of random access of the sequence table to end-insert the stack. We need to top++ each time the stack is completed.

        There is a very essence in the following code. Since we did not open up space for the array when we initialized, we cleverly used the ternary operator to allocate the size of our newly opened space. If it is the first Once we make the size of the space into 4, the subsequent process is to expand the space by 2 times at a time.

         code show as below:

        

void PushStack(ST* ps, STDataType x)
{
	assert(ps);
	//先检查空间够不够
	if (ps->top == ps->capacity)
	{
		int newcapcity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);
		STDataType* tmp = (STDataType*)realloc(ps->S, sizeof(STDataType) * newcapcity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->S = tmp;
		ps->capacity = newcapcity;
	}
	ps->S[ps->top] = x;
	++ps->top;
}

         3: Stack popping operation: here we need to use a violent solution to first determine whether the stack is empty, if it is empty, we will report an error, and if it is not empty, we can perform the popping operation, and then our top--.

        code show as below:

void PopStack(ST* ps)
{
	assert(ps);
	//非空
	assert(ps->top > 0);

	ps->top--;

}

        4: The operation of obtaining the top element of the stack: first of all, it is necessary to judge whether the stack is empty , and if it is empty, this interface will report an error.

        What we need to pay attention to in this code is that the subscript of the top element of our stack is not top but top-1

        code show as below:

        

STDataType GetStack(ST*ps)
{
	assert(ps);
	//非空
	assert(ps->top > 0);

	return ps->S[ps->top-1];
}

        5: The size of the stack: we can just return to top.

        code show as below:

        

int SizeStack(ST* ps)
{
	assert(ps);
	
	return ps->top;
}

        6: Determine whether the stack is empty: if top is 0, it means that there is no element in the stack and return true . If there is an element, return true . Here, it should be noted that there are no these two keywords in the c language. We need to introduce the header file <stdbool .h>

        code show as below:

        

bool EmptyStack(ST* ps)
{
	assert(ps);
	  //这里代码的意思是如果top为0,则返回真,不为0返回false
    return ps->top == 0;
  
}

        In order to reflect the characteristics of the stack, when we print out the data in the stack, we do not directly traverse the array

        We are using --->

        

while (!EmptyStack(&st))
	{
		printf("%d ", GetStack(&st));
		PopStack(&st);
	}

        Here, the element at the top of the stack is taken each time, and then the element at the top of the stack is deleted. Only in this way can the characteristics of the stack be reflected.

        

        Illustration:

         

        The detailed explanation of the stack ends here.



    1: Definition and characteristics of queue

        Definition of queue: Queue is also a special linear table, it can only perform insertion at one end and delete at the other end , we call the inserted end the tail of the queue, and the deleted end we call the queue head , This structure is very common in our lives. We can imagine the situation where we are queuing in the cafeteria. If someone comes, it will be inserted at the end of the queue, and those who have finished eating will leave the queue from the head.

        Dequeuing: the operation of deleting the elements at the head of the queue

        Enqueue: The operation of inserting data at the end of the queue.

       Queue features : delete operations can only be performed at the head of the queue, and insertion operations can be performed at the end of the queue, following the principle of first in first out .

        picture:

        

         2: What structure is used to realize the queue and the reason?

        Announce the answer first: Compared with the sequence table, we generally use the linked list to implement the data structure of the queue.

        Reason: The linked list is more convenient when deleting the head. When inserting the tail, we only need to define a tail pointer to save time.

        3: (detailed) common interface of the queue

        The interface of the queue includes: initializing the queue, destroying the queue, enqueue operation, dequeue operation, and judging whether the queue is empty

Get the head element, get the tail element, and judge the size of the queue. 

       First, let's understand the structural code analysis of the queue:

typedef int QueueDataType;

typedef struct QNode
{
	struct QNode* next;
	QueueDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;//用来记录有多少个结点,可以直接算大小
}Que;

     First of all, each element of the queue is the basic structure of a linked list. We only need to record the head node and tail node of the linked list to represent the queue, and because we usually only pass the address when passing parameters, we just Define the head pointer and tail pointer into a structure , and add a member variable size to record the number of queue elements.

            One advantage of putting two pointers in the same structure is that we only need to change the value of our pointer through the structure pointer, otherwise we need to use secondary pointers to modify their values.

 

        1: Initialization of the queue: If your initialization is different from mine, it may cause some differences in the steps in the following interfaces. initialization is not unique

        code show as below:

            

void QueueInit(Que* pq)
{
	assert(pq);
	pq->head = pq->tail = 0;
	pq->size = 0;
}

        2: Enqueue operation: There are two cases here. The first one is that when inserting the first element, we need to change the pointers of the two pointers. In the other case, we only need to insert one after the tail pointer. The new node is enough, and at the same time we point the tail pointer to the new node, and our size++ after insertion;

        code show as below:

        

void QueuePush(Que* pq, QueueDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		exit(-1);
	}
	
	//为第一个结点的时候
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	//不是第一个结点
	else
	{
		pq->tail->next = newnode;
	}
	pq->tail = newnode;
	newnode->data = x;
	newnode->next = NULL;
	pq->size++;
}

        3: Dequeue operation: first of all, it is necessary to judge whether there are data elements in the queue, and there are two types, one is that there is only one element in the queue, then we need to delete this element, and set the values ​​of head and tail to empty. size--;

        code show as below:

void QueuePop(Que* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//只有一个结点的时候
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* phead = pq->head->next;
		free(pq->head);
		pq->head = phead;
	}
	pq->size--;

       4: Get the head element of the queue: This is very simple, first judge whether the queue is empty, if it is not empty, we only need to return head->data, that's it.

        code show as below:

QueueDataType QueueFront(Que* pq)
{
	assert(pq);//判断pq是否有意义
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

        5: Get the tail element of the queue: similar to the above idea, you only need to return the data element pointed to by the tail pointer.

        

QueueDataType QueueBack(Que* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

        6: Determine whether the queue is empty: if it is empty, then the head pointer is empty, if the head is NULL, return true, otherwise return false

        code show as below:

               

bool QueueEmpty(Que* pq)
{
	assert(pq);
	return pq->head==NULL;
}

        7: Judgment on the number of elements in the queue: we can be in according to the structure we defined, we only need to return the size.

        

int QueueSize(Que* pq)
{
	assert(pq);
	return pq->size;
}

        In some cases, the stack can be converted into a queue, and the queue can also be converted into a stack. Let us think about how it is converted.


 

 ! ! ! This chapter is over, thank you for your patience in watching!

        

Guess you like

Origin blog.csdn.net/2201_75964502/article/details/132567454