栈和队列(c语言实现)

  作战地图如下

   



栈:

    一,栈是啥???:

栈是一种线性(每个数据的相对位置前后在空间或逻辑上连续)的数据结构,主要涉及压栈出栈两种操作。压栈是指向一组数据的尾部添加新数据,出栈是指从一组数据的尾部移除数据。数据"后进先出"

    二,模拟实现:

由于栈仅涉及对尾部的操作,因此选择用数组(其实链表也可)实现。

#include<stdio.h>
#include<stdlib.h> //使用realloc时要包含
#include<stdbool.h> //使用bool类型时要包含
#include<assert.h> //使用assert断言时要包含

typedef int datatype; //方便后续更改类型

typedef struct stack
{
	datatype *arr;
	int top;
	int capacity;
}stack;

//初始化栈
void InitStack(stack *p)
{
	assert(p);
	p->capacity = 0;
	p->top = -1;
	p->arr = NULL;
}

//入栈
void PushStack(stack *p, datatype data)
{
{
 	assert(p);	
	if (p->capacity == (p->top + 1))
	{
		int newcapacity = p->capacity == 0 ? 4 : 2* p->capacity;
		datatype* newp = (datatype*)realloc(p->arr, newcapacity* sizeof(datatype));
		if (newp == NULL)
		{
			perror("realloc failed!");
			return;
		}
		p->arr = newp;
		p->capacity = newcapacity;
	}
	p->arr[++(p->top)] = data;
}

//出栈
void PopStack(stack *p)
{
	assert(p);
	assert(p->top >= 0);
	--(p->top); //访问数组元素时使用top作为下标,减少top即可
}

//获取栈顶元素
datatype Stacktop(stack *p)
{
	assert(p);
	assert(p->top >= 0);
	return p->arr[p->top];
}

//获取栈中有效元素个数
int Stacksize(stack *p)
{
	assert(p);
	assert(p->top >= 0);
	return p->top+1;
}

//检查栈为空
bool Stackempty(stack* p)
{
	assert(p);
	return p->top==-1;
}

//销毁栈
void DestoryStack(stack* p)
{
	assert(p);
	free(p->arr);
	p->arr = NULL;
}

三,模拟实现的细节补充:

   ① top的初始值:由于使用数组实现,第一个元素下标为0,而我们对习惯把对整形时初始化为0,这样就会出现歧义,如下图:

解决方法有两种:

   ② 保留功能单一,代码量少的函数:例如获取栈顶元素的函数,看似写成函数是多此一举,其实至少要考虑一下两点

  1. 如果在函数的声明和定义分离式,使用者不知明晰top的真实含义(即top是指向栈顶元素位置还是指向栈顶元素的下一个位置的下标),就会有越界访问的风险。
  2. 写成函数是可以添加必要的检查代码(如assert断言),可以防止诸如对空栈进行删除的操作

四、应用:20. 有效的括号 - 力扣(LeetCode)

栈接口的实现…………

bool isValid(char* s) 
{
    stack st;
    InitStack(&st);
    while(*s) //当字符串的走到\0是结束
    {
        if(*s == '(' || *s == '{' || *s == '[') //遇到左括号时入栈
        {
            PushStack(&st,*s);
        }
        else
        {
            if(Stackempty(&st))
            {
                return false; //处理字符串只有一个左括号的情况
            }
            datatype top_value = Stacktop(&st);
            PopStack(&st);
            if(top_value == '(' && *s != ')'
            || top_value == '{' && *s != '}'
            || top_value == '[' && *s != ']')
            {
                return false;
            }
        }
        s++;
    }
    bool ret = Stackempty(&st); //处理字符串只有一个左括号的情况
    return ret;
}

队列:

    一、队列是啥???:

队列是一种线性的数据结构(每个数据的相对位置前后在空间或逻辑上连续),主要操作涉及入队列出队列。入队列和栈类似,是在队尾添加数据,而出队列是在队头移除数据,正因如此才造成了队列有别于栈的特性----"先进先出"。

    二、模拟实现

由于队列删除数据涉及第一个元素的修改,使用数组实现的话涉及挪动后面的元素,效率低下。所以用单链表(单向不带头不循环)实现(用双向链表会多消耗一个指针变量的内存,不推荐)。

typedef int datatype;

typedef struct queuenode
{
	datatype data;
	struct queuenode *next;
}qnode;

typedef struct ftnode
{
	qnode* phead;
	qnode* ptail;
	int size;
}ftnode;

// 初始化队列
void InitQueue(ftnode* p)
{
    assert(p);
    p->phead = p->ptail = NULL;
    p->size = 0;
}

//销毁
void Destory(ftnode * p)
{
    assert(p);
    qnode* cur = p->phead;
    while (cur)
    {
        qnode* next = cur->next;
        free(cur);
        cur = next;
    }
    p->phead = p->ptail = NULL;
    p->size = 0;
}

//队尾插入
void Push(ftnode* p,datatype x)
{
    assert(p);
    // 创建新节点
    qnode* newnode = (qnode*)malloc(sizeof(qnode));
    if (!newnode)
    {
        perror("malloc failed!!!");
        return;
    }
    newnode->data = x;
    newnode->next = NULL;

    if (!(p->phead))
    {
        p->phead = p->ptail = newnode;
    }
    else
    {
        p->ptail->next = newnode;
        p->ptail = newnode;
    }
    p->size++;
}

//队头删除
void Pop(ftnode *p)
{
    assert(p);
    if (p->size == 0) // 检查队列是否为空
    {
        fprintf(stderr, "Queue underflow!\n");
        return;
    }
    qnode* next = p->phead->next;
    free(p->phead);
    p->phead = next;
    if (p->phead == NULL) // 如果队列为空,更新 ptail
    {
        p->ptail = NULL;
    }
    p->size--;
}

//获取队头数据
datatype DataHead(ftnode *p)
{
    assert(p);
    return p->phead->data;
}

//获取队尾数据
datatype DataTail(ftnode* p)
{
    assert(p);
    return p->ptail->data;
}

三、模拟实现的细节补充:

    ①巧妙地避开二级指针:由于链表通过next指针寻找相邻节点的特性,每个节点都要定义为一个指针,声明和定义头结点时也是如此,因此进行函数传参时要使用二级指针来修改外部的头结点。这样不仅不易理解而且实际操作起来也比较繁琐 (二级指针同样使我难受),为了解决这个问题,在定义好节点的结构体后,再把特殊的节点(头结点phead和尾结点ptail)以指针的形式用另一个结构体定义好,这样只用通过这个结构体变量的指针就能对实际的节点进行修改了(因为这时直接就能访问到地址,从而对修改节点)。

    ②更方便的获取元素个数:第一点中提到了把特殊节点进行封装的其中一个好处,不仅如此,因为phead和ptail这两个指针同时也代表了队列所关系的队头和队尾元素,所以每次插入或删除都会对他们有影响。那么在这个结构体中再加入一个size来表示元素的个数就会很方便,插入时顺便让size++,删除时顺便让size--,还省去了写一个计算元素个数的函数,何乐而不为?

四、622. 设计循环队列 - 力扣(LeetCode)

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

MyCircularQueue* myCircularQueueCreate(int k)
{
    MyCircularQueue *obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr = (int*)malloc(sizeof(int)*(k+1));   
    obj->head = obj->tail = 0;
    obj->k = k; //////////////    
    return obj;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) ;
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->arr[obj->tail] = value;
    obj->tail++;
    obj->tail %= obj->k+1;
    return true;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
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->arr[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    return obj->tail == 0 ? obj->arr[obj->k] : obj->arr[obj->tail-1];
     //return obj->arr[(obj->tail - 1 + obj->k + 1) % (obj->k + 1)];
}

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

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return obj->head == (obj->tail+1) % (obj->k+1);
}

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->arr);
    obj->arr = NULL;
    free(obj);
    obj = NULL;
}

“栈和队列的相互转换”:

一、232. 用栈实现队列 - 力扣(LeetCode)   

 ​​​​    ①题意:使用两个栈实现一个“先进先出”的队列,使之能实现队头删除、队尾插入、获取队头元素以及队列判空等4个基本功能。

     ②思路分析:栈的队尾插入和队列类似,而判空则是两个栈同为空即可,并不特殊,所以着重分析队头删除和获取队头元素。如下图:

    

栈声明和接口接口 略......

typedef struct 
{
    stack *push_stack; //专门用于入数据
    stack *pop_stack; ///专门用于出数据
} MyQueue;


MyQueue* myQueueCreate() 
{
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
     obj->push_stack = (stack*)malloc(sizeof(stack));
     obj->pop_stack = (stack*)malloc(sizeof(stack));
    InitStack(obj->push_stack);
    InitStack(obj->pop_stack);
    return obj;
}

void myQueuePush(MyQueue* obj, int x) 
{
     PushStack(obj->push_stack,x);
}

int myQueuePeek(MyQueue* obj) 
{
   if(Stackempty(obj->pop_stack)) 
   {
     while(!Stackempty(obj->push_stack))
     {
        int mid = Stacktop(obj->push_stack);
        PopStack(obj->push_stack);
        PushStack(obj->pop_stack,mid);
     }
   }
  return Stacktop(obj->pop_stack);
}

int myQueuePop(MyQueue* obj) 
{
    int ret = myQueuePeek(obj);
   
    PopStack(obj->pop_stack);
   
    return ret;
}

bool myQueueEmpty(MyQueue* obj) 
{
    return Stackempty(obj->pop_stack) && Stackempty(obj->push_stack);
}

void myQueueFree(MyQueue* obj) 
{
   InitStack(obj->push_stack);
   InitStack(obj->pop_stack);
   free(obj);
   obj = NULL;
}

二、225. 用队列实现栈 - 力扣(LeetCode)

    ①题意:使用两个队列实现一个数据"先进后出"的栈,实现入栈、获取栈顶元素、删除栈顶元素以及判断是否为空栈等4个功能。

    ②思路分析:入栈只需和队列一样在尾部插入即可,两个队列都为空则栈为空。要着重分析获取栈顶元素和删除栈顶元素,如下图:

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值