【C语言】实现数据结构——栈,队列

栈和队列都常用的数据结构,这里使用C语言实现栈和队列,


一、栈

【C语言】实现数据结构——栈,队列_第1张图片

栈:是一种特殊的线性表,其只允许在一端出数据和入数据,插入数据和删除数据的一端加栈顶,另一端叫栈底。其数据遵守先进后出的原则(Last in First out)。 

因为这个特点那么如果进入一段数据是 1 2 3 4 5,那么其出数据就是反的 5 4 3 2 1。 

压栈(push):往栈顶插入数据。

出栈(pop):把数据在栈顶删除。

实现:


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;
}

这里使用了数组来模拟实现栈的特点。

测试:

【C语言】实现数据结构——栈,队列_第2张图片

PS:栈是不能被遍历的,要访问下一个数据,必须把现在的数据弹出栈顶。 

PS:但是,还是要注意,并不是遍历的时候,出数据就只能是5 4 3 2 1,如果这里一边访问一边出数据,数据还是顺序的。

举例:

【C语言】实现数据结构——栈,队列_第3张图片

二、队列

【C语言】实现数据结构——栈,队列_第4张图片

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 (First In First Out)
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为队头。

因为其只能在队头出数据,所以进入一段数据 1 2 3 4 5,那么出数据的顺序也该是 1 2 3 4 5。

实现:

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:这里求队列长度也可以在结构体中定义一个变量来记录,每次插入一个数据记录一下。

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;
}

        因为其队列的特点,这里定义了一个头节点和尾结点,这样每次在后面插入数据的时候就不用遍历链表了。

        这里使用的链表来模拟队列的特点。

测试:

【C语言】实现数据结构——栈,队列_第5张图片

PS:注意队列也不能遍历。 

三、OJ题

1:使用栈实现队列

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

PS:这里就使用上面已经写好的接口来实现。

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;
}

 思路:

        使用两个栈结构,一个pushst,一个popst, 往队列里面插入数据的时候把数据插入到pushst里面,而删除数据的时候弹出popst里面的数据。

        而popst里面的数据就是pushst里面的数据倒过来的,因为以栈的特点,倒过来的数据刚好的反的,而这时候pop掉popst里面的数据,就相当于删除原来数据的头部数据。

【C语言】实现数据结构——栈,队列_第6张图片

 【C语言】实现数据结构——栈,队列_第7张图片

 题目链接:https://leetcode.cn/problems/implement-queue-using-stacks/

 2:使用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 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;
}

思路:

        使用两个队列结构,一个Q1,一个Q2,插入数据的时候,插入有数据的队列,如果两个队列都为空,随便插入一个空的队列,但是一定要有一个队列为空。

        插入数据也和上面一样倒数据,但是因为其队列的特点,倒完数据其还是顺序摆放,但是栈删除数据,只能在栈顶删除。那么其实可以倒数据的时候,留下队尾的数据,全部倒完后再删除队尾的数据,就相当于删除了原数据的尾部。

【C语言】实现数据结构——栈,队列_第8张图片

 【C语言】实现数据结构——栈,队列_第9张图片

题目链接:https://leetcode.cn/problems/implement-stack-using-queues/

四、循环队列

        循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

【C语言】实现数据结构——栈,队列_第10张图片

【C语言】实现数据结构——栈,队列_第11张图片

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);
}

         这里使用了数组来模拟环形队列。

         因为是环形的,所以这里队列一开始初始化的时候就要给定固定的长度,在结构体中定义一个变量 来记录环形队列的长度。但是要注意这里为了方便下面判断队列是满还是空,要多添加一个虚设空间,所以其队列真实长度为K+1。

         head指向队列的头部,tail指向链表的尾部。插入数据的时候往下标为tail的空间插入,然后tail往后走一个(tail固定指向队尾的下一个)。

【C语言】实现数据结构——栈,队列_第12张图片

        当队列都插入满数据后,tail会在head的前面。

【C语言】实现数据结构——栈,队列_第13张图片

        那么这里就可以判断,如果tail+1==head,那么说明队列是满的,就不能插入数据了。(虚设空间不记录数据)。        

        删除数据,就让head往后走一个。

【C语言】实现数据结构——栈,队列_第14张图片

PS:这里前面的数据不用删除,后面插入数据的时候,会把数据覆盖掉。

        当队列的数据全部删除后,tail和head相遇。

【C语言】实现数据结构——栈,队列_第15张图片

        那么这里就可以判断,如果head==tail,那么说明队列为空。 

PS:这里正方形内部是记录的数据,外面是数组的下标。

注意:

        有几个情况要注意,因为其循环队列是用数组模拟的,所以一些特定的情况要避免。

【C语言】实现数据结构——栈,队列_第16张图片

        这里判断队列是否是满的时候,tail+1=5超过了数组下标,而head是0,虽然是满的但是计算不正确。

        如果这里tail超出了队列的长度,可以让其模上队列的长度,就可以把tail的变为0。

        这里 t=tail+1=5,让 t%=k+1,5%5=0,这里t就等于0,head也是0,t==head,那么就说明队列满了。

        而且这里如果tail不等于5,假设等于3,模上数组长度还是其自己,还是3。

PS:注意这里只是做判断,并没有改变tail现在的位置。

        【C语言】实现数据结构——栈,队列_第17张图片

         同理,这里队列没满,要继续往队尾插入数据,插入后tail往后走一步,这里也tail也会超出下标,那么也可以让其模上长度,tail++;tail%=k+1;其也会走到数组起始位置。

PS:这里插入数据了,要让tail往下走一个,要改变tail。

【C语言】实现数据结构——栈,队列_第18张图片

        删除数据时也要注意,如果head在边界位置,也要让其模上长度,head++;head%=k+1;

【C语言】实现数据结构——栈,队列_第19张图片

        最后是读取队尾数据的时候,这里队尾是tail的上一个,tail-1变为-1了,这样怎么模呢?

        这里其实可以让tail先加上一个k+1,然后再模其长度,t=tail-1+k+1;t%=k+1;非常巧妙的写法。如果tail不在边界,加上长度也没什么影响,因为后面也会被模没了。


你可能感兴趣的:(数据结构)