[C language] Realize data structure - stack, queue

Both stacks and queues are commonly used data structures. Here, C language is used to implement stacks and queues.


1. Stack

Stack: It is a special linear table that only allows data to be output and input at one end, and the end where data is inserted and deleted is added to the top of the stack, and the other end is called the bottom of the stack. Its data follows the principle of first in first out (Last in First out). 

Because of this feature, if the input data is 1 2 3 4 5, then the output data is the opposite 5 4 3 2 1. 

Push (push): Insert data to the top of the stack.

Pop : Delete the data on the top of the stack.

accomplish:


typedef int STdatatype;

typedef struct stack
{
	STdatatype *arr;
	int top;
	int capacity;

}stack;

//初始化
void StackInit(stack* ps);
//销毁堆栈
void StackDestory(stack* ps);
//压栈
void StackPush(stack* ps, STdatatype x);
//出栈
void StackPop(stack* ps);
//取栈顶的值
STdatatype StackTop(stack* ps);
//判断栈是否为空
bool StackIsEmpty(stack* ps);
//栈的长度
int StackSize(stack* ps);

void StackInit(stack* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = 0;
	ps->top = 0;

}

void StackDestory(stack* ps)
{
	assert(ps);

	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void StackPush(stack* ps, STdatatype x)
{
	assert(ps);

	if (ps->capacity == ps->top){
		int newCapacity  = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STdatatype* p = (STdatatype*)realloc(ps->arr, sizeof(STdatatype)* newCapacity);
		if (p==NULL)
		{
			perror("申请内存失败\n");
			exit(0);
		}
		ps->capacity = newCapacity;
		ps->arr = p;
	}
	ps->arr[ps->top] = x;
	ps->top++;

}

void StackPop(stack* ps)
{
	assert(ps);
	assert(!StackIsEmpty(ps));
	ps->top--;
}

STdatatype StackTop(stack* ps)
{
	assert(ps);
	assert(!StackIsEmpty(ps));
	return ps->arr[ps->top-1];
}

bool StackIsEmpty(stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

int StackSize(stack* ps)
{
	assert(ps);
	assert(!StackIsEmpty(ps));
	return ps->top;
}

An array is used here to simulate the characteristics of a stack.

test:

PS: The stack cannot be traversed. To access the next data, the current data must be popped from the top of the stack. 

PS: However, we still need to pay attention, not when traversing, the output data can only be 5 4 3 2 1, if the data is output while accessing here, the data is still sequential.

Example:

Two, the queue

Queue: A special linear table that only allows inserting data operations at one end and deleting data operations at the other end. The queue has First In First Out (First In First Out)
Into the queue: The end of the insertion operation is called the tail of the queue.
Out of the queue: The end of the delete operation is called the head of the queue.

Because it can only output data at the head of the queue, so enter a piece of data 1 2 3 4 5, then the order of output data should also be 1 2 3 4 5.

accomplish:

typedef int Qdatatype;

struct QueNode
{
	Qdatatype data;
	struct QueNode* next;
};

struct Queue
{
	QueNode* head;
	QueNode* tail;
};

//初始化
void QueueInit(Queue* st);
//销毁队列
void QueueDestroy(Queue* st);
//插入数据 这里不用二级指针,因为传入的是结构体Queue,而不是头节点。
void QueuePush(Queue* st, Qdatatype x);
//删除数据
void QueuePop(Queue* st);
//获得队头的数据
Qdatatype QueueTop(Queue* st);
//获得队尾的数据
Qdatatype QueueBack(Queue* st);
//检查队列是否为空
bool QueueIsEmpty(Queue* st);
//队列长度
int QueueSize(Queue* st);

 PS: To find the length of the queue here, you can also define a variable in the structure to record, and insert a data record every time.

struct QueNode
{
	Qdatatype data;
	struct QueNode* next;
    int size;
};
void QueueInit(Queue* st)
{
	st->head = NULL;
	st->tail = NULL;
}

void QueueDestroy(Queue* st)
{
	assert(st);

	QueNode* cur = st->head;
	while (cur) {
		QueNode* p = cur;
		cur = cur->next;
		free(p);
	}
	st->head = NULL;
	st->tail = NULL;
}

void QueuePush(Queue* st, Qdatatype x)
{
	assert(st);
	QueNode* node = (QueNode*)malloc(sizeof(QueNode));
	node->data = x;
	node->next = NULL; 
    //队列只能往尾节点插入数据,直接让节点连接NULL,后面就不用置空了
    
   
	if (st->head == NULL){  //如果头节点为空,把节点当做头节点
		st->head = node;
		st->tail = node;
	}
	else {	//头节点不为空,连接在尾结点后面			  
		st->tail->next = node;
		st->tail = node;
	}

}

//队列出数据只能从头部出去
void QueuePop(Queue* st)
{
	assert(st);
	assert(!QueueIsEmpty(st));

	if (st->head == st->tail) {

		free(st->head);
		st->head = NULL;
		st->tail = NULL;
	}
	else {

		QueNode* p = st->head;
		st->head = st->head->next;
		free(p);
	}
}

Qdatatype QueueTop(Queue* st)
{
	assert(st);
	assert(!QueueIsEmpty(st));

	return st->head->data;
}

Qdatatype QueueBack(Queue* st)
{
	assert(st);
	assert(!QueueIsEmpty(st));
	return st->tail->data;
}

bool QueueIsEmpty(Queue* st)
{
	assert(st);

	return st->head == NULL;
}

int QueueSize(Queue* st)
{
	assert(st);
	QueNode* cur = st->head;
	int size = 0;
	while (cur) {
		size++;
		cur = cur->next;
	}
	return size;
}

        Because of the characteristics of its queue, a head node and a tail node are defined here, so that there is no need to traverse the linked list every time data is inserted later.

        The linked list used here simulates the characteristics of the queue.

test:

PS: Note that the queue cannot be traversed either. 

3. OJ question

1: Use a stack to implement a queue

Please use only two queues to implement a last-in-first-out (LIFO) stack, and support all four operations ( push, top, pop and  empty) of a normal stack.

  • void push(int x) pushes the element x onto the top of the stack.
  • int pop() removes and returns the top element of the stack.
  • int top() Returns the top element of the stack.
  • boolean empty() returns true if the stack is empty; otherwise, returns false

PS: Here we use the interface already written above to implement it.

typedef struct {

    stack pushst;
    stack popst;

} MyQueue;

bool myQueueEmpty(MyQueue* obj) {
    assert(obj);
    //如果两个栈都是空的,那么队列就是空的
    return StackIsEmpty(&obj->pushst) && StackIsEmpty(&obj->popst);
}

MyQueue* myQueueCreate() {

    MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));

    StackInit(&obj->pushst);
    StackInit(&obj->popst);

    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    
    assert(obj);
    StackPush(&obj->pushst,x);

}


int myQueuePop(MyQueue* obj) {

    assert(obj);
    assert(!myQueueEmpty(obj));

    //如果pop栈是空
    if(StackIsEmpty(&obj->popst)){
    
        //把push栈全部放到pop栈里面
        while(!StackIsEmpty(&obj->pushst)){ 

            StackPush(&obj->popst,StackTop(&obj->pushst));
            StackPop(&obj->pushst);
        }
    }

    int temp=StackTop(&obj->popst);
    StackPop(&obj->popst);

    return temp;
}

int myQueuePeek(MyQueue* obj) {

    assert(obj);
    assert(!myQueueEmpty(obj));

    
    if(StackIsEmpty(&obj->popst)){

        while(!StackIsEmpty(&obj->pushst)){

            StackPush(&obj->popst,StackTop(&obj->pushst));
            StackPop(&obj->pushst);
        }
    }

    return StackTop(&obj->popst);
}

void myQueueFree(MyQueue* obj) {

    assert(obj);
    StackDestory(&obj->pushst);
    StackDestory(&obj->popst);

    free(obj);
    obj=NULL;
}

 Ideas:

        Use two stack structures, one pushst and one popst . When inserting data into the queue, insert the data into pushst , and when deleting data, pop up the data in popst .

        The data in popst is the reverse of the data in pushst . Because of the characteristics of the stack, the reversed data is just the opposite. At this time, popping the data in popst is equivalent to deleting the header data of the original data.

 

 Link to the topic: https://leetcode.cn/problems/implement-queue-using-stacks/

 2: Using a queue to implement a stack

Please use only two queues to implement a last-in-first-out (LIFO) stack, and support all four operations (push, top, pop, and empty) of a normal stack.

  • void push(int x) pushes the element x onto the top of the stack.
  • int pop() removes and returns the top element of the stack.
  • int top() Returns the top element of the stack.
  • boolean empty() returns true if the stack is empty; otherwise, returns false
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;

bool myStackEmpty(MyStack* obj) {
    assert(obj);
    return QueueIsEmpty(&obj->q1) && QueueIsEmpty(&obj->q2);
}

MyStack* myStackCreate() {
    MyStack* node=(MyStack*)malloc(sizeof(MyStack));

    QueueInit(&node->q1);
    QueueInit(&node->q2);

    return node;
}

void myStackPush(MyStack* obj, int x) {
    assert(obj);

    if(!QueueIsEmpty(&obj->q1)){
        QueuePush(&obj->q1,x);
    }
    else{
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) {

    assert(obj);
    assert(!myStackEmpty(obj));
    
    //判断有数据的队列,和空队列
    Queue* emptyQ = &obj->q1;
    Queue* nonemptyQ = &obj->q2;

    if(!QueueIsEmpty(&obj->q1)){
        emptyQ=&obj->q2;
        nonemptyQ=&obj->q1;
    }

    while(QueueSize(nonemptyQ) > 1){
        //把非空的队列中的数据,倒入到空的队列中去,留一个
        QueuePush(emptyQ,QueueTop(nonemptyQ));
        QueuePop(nonemptyQ);
    }

    int temp=QueueTop(nonemptyQ);
    QueuePop(nonemptyQ);

    return temp;
}

int myStackTop(MyStack* obj) {

    assert(obj);
    assert(!myStackEmpty(obj));

    if(!QueueIsEmpty(&obj->q1)){
        return QueueBack(&obj->q1);
    }
    else{
        return QueueBack(&obj->q2);
    }
}

void myStackFree(MyStack* obj) {
    assert(obj);

    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    
    free(obj);
    obj=NULL;
}

Ideas:

        Use two queue structures, one Q1 and one Q2 . When inserting data, insert the queue with data. If both queues are empty, insert an empty queue randomly, but one queue must be empty.

        Inserting data is also the same as above, but because of the characteristics of the queue, the data is still placed in order after being dumped, but the stack deletes data, which can only be deleted at the top of the stack. So in fact, when the data can be dumped, the data at the end of the queue is left, and then the data at the tail of the queue is deleted after all the data is dumped, which is equivalent to deleting the tail of the original data.

 

Link to the topic: https://leetcode.cn/problems/implement-stack-using-queues/

4. Circular Queue

        A circular queue is a linear data structure whose operation is based on the FIFO (first in first out) principle and the tail of the queue is connected after the head of the queue to form a loop. It is also known as a "ring buffer".

typedef struct {
    int *a;
    int k;
    int head;
    int tail;
} MyCircularQueue;

bool myCircularQueueIsFull(MyCircularQueue* obj) {

    int t = obj->tail+1;
    t %= obj->k+1;

    if(t == obj->head){
        return true;
    }
    return false;

}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->head == obj->tail;
}


MyCircularQueue* myCircularQueueCreate(int k) {

    MyCircularQueue* p=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    p->a=(int*)malloc(sizeof(int)*(k+1));
    p->head=0;
    p->tail=0;
    p->k=k;

    return p;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {

    if(myCircularQueueIsFull(obj))
        return false;
    
    obj->a[obj->tail]=value;

    obj->tail++;
    obj->tail %= obj->k+1;

    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;

    obj->head++;
    obj->head %= obj->k+1;
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    
    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    int t=obj->tail-1 + obj->k+1;

    t %= obj->k+1;

    return obj->a[t];
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

         An array is used here to simulate a circular queue.

Because it is a ring, a fixed length must be given when the queue is initially initialized, and a variable k           is defined in the structure  to record the length of the ring queue. But it should be noted that in order to facilitate the following to judge whether the queue is full or empty, an additional dummy space is added, so the real length of the queue is K+1.

         head points to the head of the queue, and tail points to the tail of the linked list. When inserting data, insert into the space subscripted as tail, and then the tail goes backward one (tail is fixed to the next one at the end of the queue).

        When the queue is full of data, the tail will be in front of the head.

        Then it can be judged here that if tail+1==head , then the queue is full and data cannot be inserted. (Dummy space does not record data).        

        Delete the data, let the head go back one.

PS: There is no need to delete the previous data here, and the data will be overwritten when the data is inserted later.

        When all the data in the queue is deleted, tail and head meet.

        Then it can be judged here that if head==tail , then the queue is empty. 

PS: Here, the inside of the square is the recorded data, and the outside is the subscript of the array.

Notice:

        There are several situations to pay attention to, because the circular queue is simulated by an array, so some specific situations should be avoided.

        When judging whether the queue is full or not, tail+1=5 exceeds the array subscript, and head is 0. Although it is full, the calculation is incorrect.

        If the tail exceeds the length of the queue here, you can make it modulo the length of the queue, and you can change the tail to 0.

        Here t=tail+1=5, let t%=k+1, 5%5=0, here t is equal to 0, head is also 0, t==head, then the queue is full.

        And here if tail is not equal to 5, assuming it is equal to 3, the length of the modulo array is still itself, or 3.

PS: Note that this is just a judgment, and does not change the current position of the tail.

        

         Similarly, if the queue is not full here, continue to insert data at the end of the queue. After inserting, tail will take a step back. Here, the tail will also exceed the subscript, so the length can also be modeled, tail++; tail%=k+1 ; It also goes to the beginning of the array.

PS: The data is inserted here, the tail needs to go down one, and the tail needs to be changed.

        Also pay attention when deleting data, if the head is at the boundary position, it must also be modulo the length, head++; head%=k+1;

        Finally, when reading the data at the tail of the queue, the tail of the queue here is the previous one of tail, and tail-1 has changed to -1. How to model this?

        Here you can actually add a k+1 to the tail, and then modulo its length, t=tail-1+k+1; t%=k+1; very clever writing. If the tail is not on the border, adding the length has no effect, because the back will also be molded out.


Guess you like

Origin blog.csdn.net/weixin_45423515/article/details/124851541