数据结构:栈和队列的操作及应用

本文介绍了栈和队列的基本操作,给出了C和C++实现的源代码链接。还列举了多个栈和队列的面试题,如括号匹配、用队列实现栈等,并给出分析。最后阐述了栈和队列的区别,包括进出顺序、操作位置和遍历速度等方面。
栈的基本操作

分为三部分,实现入栈,出栈,获取栈顶元素,获取栈内有效元素个数,置空,销毁等操作。
Stack.h

#pragma once
#include <assert.h>
#include <stdio.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int top;//标记栈顶,相当于顺序表中的size
	int capacity;
}Stack;
void StackInit(Stack* s);
void StackDestroy(Stack *s);
void StackPush(Stack *s,STDataType data);
void StackPop(Stack *s);
STDataType StackTop(Stack *s);
int StackEmpty(Stack *s);
int StackSize(Stack *s);

Stack.c

#include "Stack.h"
#include <malloc.h>
#include <string.h>
void StackInit(Stack* s)
{
	assert(s);
	s->arr = (STDataType*)malloc(sizeof(STDataType)* 3);
	if (NULL == s->arr)
	{
		assert(0);
		return;
	}
	s->capacity = 3;
	s->top = 0;
}
void CheckCapacity(Stack *s)
{
	assert(s);
	if (s->top == s->capacity)
	{
		int NewCapacity = s->capacity * 2;
		STDataType* temp = (STDataType*)malloc(sizeof(STDataType)*NewCapacity);
		if (NULL == temp)
		{
			assert(0);
			return;
		}
		memcpy(temp, s->arr, s->top*sizeof(STDataType));
		free(s->arr);
		s->arr = temp;
		s->capacity = NewCapacity;
	}
}
void StackPush(Stack *s, STDataType data)
{
	assert(s);
	CheckCapacity(s);//扩容
	s->arr[s->top] = data;
	s->top++;
}
void StackPop(Stack *s)
{
	assert(s);
	if (0 == s->top)//栈存在但是栈顶没有元素
		return;
	s->top--;
}
STDataType StackTop(Stack *s)//获取栈顶元素
{
	assert(s);
	return s->arr[s->top - 1];
}
int StackSize(Stack *s)//获取栈内有效元素个数
{
	assert(s);
	return s->top;
}
int StackEmpty(Stack *s)
{
	assert(s);
	return 0 == s->top;
}
void StackDestroy(Stack *s)
{
	assert(s);
	if (s->arr)
	{
		free(s->arr);
		s->arr = NULL;
		s->capacity = 0;
		s->top = 0;
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
#include <stdlib.h>
void menu()
{
	printf("***********************************************\n");
	printf("**  1.压栈           2.出栈                  **\n");
	printf("**  3.获取栈顶元素   4.获取栈内有效元素个数  **\n");
	printf("**  5.清空           6.销毁                  **\n");
}
void test()
{
	Stack s;
	int input = 0;
	STDataType data = 0;
	StackInit(&s);
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入要入栈的数:");
			scanf("%d", &data);
			StackPush(&s, data);
			break;
		case 2:
			StackPop(&s);
			break;
		case 3:
			data=StackTop(&s);
			printf("栈顶元素为:%d\n", data);
			break;
		case 4:
			data=StackSize(&s);
			printf("有效元素个数为:%d\n", data);
			break; 
		case 5:
			StackEmpty(&s);
			break;
		case 6:StackDestroy(&s);
			break;
		}
	} while (input);
}
int main()
{
	test();
	system("pause");
	return 0;
}

附加C++实现栈的操作源代码(学习C++与Linux后)(github):
https://github.com/wangbiy/review/tree/master/stack

队列的基本操作

queue.h

#pragma once
typedef int QUDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QUDataType data;
}QueueNode;//结点
typedef struct Queue
{
	QueueNode* front;
	QueueNode* rear;
}Queue;
void QueueInit(Queue* pq);//初始化
void QueueDestroy(Queue* pq);//销毁
void QueuePush(Queue* pq, QUDataType data);//入队
void QueuePop(Queue* pq);//出队
QUDataType QueueFront(Queue* pq);//获取队头元素
QUDataType QueueBack(Queue* pq);//获取队尾元素
int Queuempty(Queue *pq);//置空
int QueueSize(Queue *pq);//获取有效元素个数
void QueueShow(Queue* pq);//显示

queue.c

#include "queue.h"
#include <assert.h>
#include <stdio.h>
#include <malloc.h>
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->front = pq->rear = NULL;

}
void QueueDestroy(Queue* pq)//销毁
{
	QueueNode* cur = NULL;
	assert(pq);
	cur = pq->front;
	while (cur)
	{
		pq->front = cur->next;
		free(cur);
		cur = pq->front;
	}
	pq->front = pq->rear = NULL;
}
QueueNode* BuyQueueNode(QUDataType data)
{
	QueueNode* pNewNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (pNewNode == NULL)
	{
		assert(0);
		return NULL;
	}
	pNewNode->data = data;
	pNewNode->next = NULL;
	return pNewNode;
}
void QueuePush(Queue* pq, QUDataType data)//入队,尾插
{
	assert(pq);
	QueueNode* pNewNode = NULL;
	pNewNode = BuyQueueNode(data);
	if (NULL == pq->rear)//空队列
	{
		pq->front = pq->rear=pNewNode;
	}
	else
	{
		pq->rear->next = pNewNode;
		pq->rear = pNewNode;
	}
}
void QueuePop(Queue* pq)//出队
{
	assert(pq);
	if (NULL == pq->front)
	{
		return;
	}
	else if (pq->front==pq->rear)//只有一个节点
	{
		free(pq->front);
		pq->front = pq->rear = NULL;
	}
	else//多个元素
	{
		QueueNode *Del = pq->front;
		pq->front = Del->next;
		free(Del);
	}
}
QUDataType QueueFront(Queue* pq)//获取队头元素
{
	assert(pq);
	return pq->front->data;
}
QUDataType QueueBack(Queue* pq)//获取队尾元素
{
	assert(pq);
	return pq->rear->data;
}
int Queuempty(Queue *pq)//置空
{
	assert(pq);
	return NULL == pq->front;
}
int QueueSize(Queue *pq)//获取有效元素个数
{
	QueueNode* cur = NULL;
	int count = 0;
	assert(pq);
	cur = pq->front;
	while (cur)
	{
		count++;
		cur = cur->next;
	}
	return count;
}
void QueueShow(Queue* pq)
{
	QueueNode* cur = NULL;
	assert(pq);
	cur = pq->front;
	while (cur)
	{
		printf("%d---->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "queue.h"
#include <stdio.h>
#include <stdlib.h>
void menu()
{
	printf("***************************************\n");
	printf("**1.入队              2.出队         **\n");
	printf("**3.获取队头元素      4.获取队尾元素 **\n");
	printf("**5.获取有效元素个数  6.置空         **\n");
	printf("**7.销毁              8.显示         **\n");
	printf("***************************************\n");
}
void test()
{
	int input = 0;
	Queue q;
	QUDataType data = 0;
	QueueInit(&q);
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入要入队的元素:");
			scanf("%d", &data);
			QueuePush(&q, data);
			break;
		case 2:
			QueuePop(&q);
			break;
		case 3:
			data=QueueFront(&q);
			printf("队头元素为:%d\n", data);
			break;
		case 4:
			data = QueueBack(&q);
			printf("队尾元素为:%d\n", data);
			break;
		case 5:
			data=QueueSize(&q);
			printf("有效元素个数为:%d\n", data);
			break;
		case 6:
			Queuempty(&q);
			break;
		case 7:
			QueueDestroy(&q);
			break;
		case 8:
			QueueShow(&q);
			break;
		default:
			break;
		}
	} while (input);
}
int main()
{
	test();
	system("pause");
	return 0;
}

附加C++实现队列的基本操作源代码(学习Linux与C++后)(github):
https://github.com/wangbiy/review/tree/master/Queue

栈和队列面试题

1.括号匹配问题(c++版,比较方便)

class Solution {
public:
    bool isValid(string s) {
        	std::stack<char> str;
        
		int size = s.size();//取字符串长度
		for (int i = 0; i < size; ++i)
		{
			char ch = s[i];
			switch (ch)
			{
			case '(':
			case '[':
			case '{':
				str.push(ch);
				break;
			case ')':
			case ']':
			case '}':
			{
						if (str.empty())
						{
							return false;
						}
						char left = str.top();
                        str.pop();
						if (!((left == '('&&ch == ')')
							|| (left == '['&&ch == ']') || (left == '{'&&ch == '}')))
						{
							return false;
						}
						break;
			}

			}

		}
		if (!str.empty())
		{
			return false;
		}
		return true;
    }
};

分析:输入字符串,遍历取出字符串的每个字符,当遇到左括号时,入栈,当遇到右括号时,判断栈内是否为空,若为空,返回false,否则取出栈顶元素,出栈,与右括号比较判断是否匹配,如果不匹配,返回false,循环结束,若栈内还有元素,返回false,否则返回true。
2.用队列实现栈

class MyStack {
    std::queue<int> q;
public:
    MyStack() {      
    }
    void push(int x) {
        q.push(x);
        
    }
    int pop() {
        int n=q.size();
        for(int i=0;i<n-1;i++)
        {
            int v=q.front();
            q.pop();
            q.push(v);
        }
        int v=q.front();
        q.pop();
        return v;
    }
    int top() {
        int n=q.size();
        for(int i=0;i<n-1;i++)
        {
            int v=q.front();
            q.pop();
            q.push(v);
        }
        int v=q.front();
        q.pop();
        q.push(v);
        return v;        
    }
    bool empty() {
        return q.empty();        
    }
};

分析:进行压栈操作实际上就是数据入队列,直接将x进入队列,出栈要满足后进先出原则,而队列满足先进先出原则,因此,应当让前n-1个元素先从头部出队列,再从尾部入队列,返回队头元素,查看栈顶元素一样的道理,唯一不同的是只是看一眼队头元素,之后将它进入队列,判断栈内是否为空就是判断队列是否为空。
3.用栈实现队列

class MyQueue {
    std::stack<int> in;
    std:: stack<int> out;
public:
    MyQueue() {
        
    }
    void push(int x) {
        in.push(x);
        
    }
    int pop() {
        if(out.empty())
        {
            int n=in.size();
            for(int i=0;i<n;i++)
            {
                int v=in.top();
                in.pop();
                out.push(v);
            }
        }
        int v=out.top();
        out.pop();
        return v;       
    }
    int peek() {
         if(out.empty())
        {
            int n=in.size();
            for(int i=0;i<n;i++)
            {
                int v=in.top();
                in.pop();
                out.push(v);
            }
        }
        int v=out.top();
        return v;       
    }
    bool empty() {
        
        return in.empty()&&out.empty();    
    }
};

分析:定义两个栈,一个保存进来的元素,一个保存出去的元素,要用栈实现队列,进队列实际上就是进栈,出队列时,如果out栈为空,将in栈的栈顶元素一一取出,进入out栈,之后一一去out栈的元素,出栈。取队列首部的元素与之道理一样,不过不用出栈,只用看一眼就行,判断队列是否为空,只要两个栈都为空,队列为空。
4.实现一个最小栈

class MinStack {
    std:: stack<int> nomal;
    std:: stack<int> min;
public:
    MinStack() {
        
    }    
    void push(int x) {
        nomal.push(x);
        if(min.empty()||x<=min.top())
        {
            min.push(x);
        }
        else
        {
            min.push(min.top());
        }    
    }    
    void pop() {
        nomal.pop();
        min.pop();       
    }  
    int top() {
        return nomal.top();
    }  
    int getMin() {
        return min.top();
    }
};

分析:定义两个栈,一个存放每个元素,一个存放小的元素,先将一个元素进入nomal栈,如果min栈是空的或者接下来的元素小于min栈的栈顶元素,进入min栈,否则将min栈的栈顶元素进入min栈,这样min栈的栈顶元素一定为最小元素,在进行出栈操作时,nomal栈出一个,min栈出一个,取栈顶元素即是取nomal栈元素,取最小元素即是取min栈栈顶元素。
5.循环队列

class MyCircularQueue {
   int *arr;
   int size;
   int front;
   int rear;
   int capacity;
public:
   MyCircularQueue(int k) {
       arr=(int*)malloc(sizeof(int)*k);
       capacity=k;
       size=0;
       front=0;
       rear=0;        
   }
   bool enQueue(int value) {
       if(size>=capacity)
       {
           return false;
       }
       arr[rear]=value;
       rear=(rear+1)%capacity;
       size++;  
       return true;
   }
   bool deQueue() {
       if(size==0)
           return false;
       size--;
       front=(front+1)%capacity;
       return true;
       
   }
   int Front() {
       if(size==0)
           return -1;
       return arr[front];   
   }
   int Rear() {
       if(size==0)
           return -1;
       int index=(rear+capacity-1)%capacity;
       return arr[index];
       
   }
   bool isEmpty() {
       return size==0;
       
   }
   
   bool isFull() {
       return size==capacity;
       
   }
};

分析:实现循环队列,开辟一个动态空间,实现入队操作,如果有效元素个数大于容量,返回false,将value放入队列,如果到达队尾,实现循环,通过(rear+1)%capacity,使得rear==front,重新开始,出队列具有相同的道理,出队,front向后走,一直到队尾,进行取模操作,到达队头,在取队尾元素时,如果队列为空,返回false,(rear+capacity-1)%capacity可得到队尾位置。
6、逆波兰表达式
分析:逆波兰表达式,即后缀表达式,例如:2+1*3的逆波兰表达式就是2 1 3 * +,那么如何给定一个逆波兰表达式来求值,首先:取出元素,如果该元素是数字,入栈,否则,先到栈顶取当前运算符的右操作数,出栈,然后又在栈顶取左操作数,出栈,进行当前运算,将结果压栈,继续进行,直到所有元素处理完,这时栈顶元素就是运算结果。注意区分是负号还是减号,如果是负号,说明这个元素后面还有元素,否则后面为’\0’。
代码见下:

int evalRPN(char ** tokens, int tokensSize){
    Stack s;
    int ret=0;
    StackInit(&s);
    for(int i=0;i<tokensSize;++i)
    {
        char* str=tokens[i];
        if(!('+'==str[0] || ('-'==str[0]&&str[1]=='\0')||'*'==str[0]||'/'==str[0]))//说明不是运算符
        {
            StackPush(&s,atoi(str));
        }
        else//说明是运算符
        {
            int right=StackTop(&s);
            StackPop(&s);
            int left=StackTop(&s);
            StackPop(&s);
            switch(str[0])
            {
                case '+':
                    StackPush(&s,left+right);
                    break;
                case '-':
                    StackPush(&s,left-right);
                    break;
                case '*':
                    StackPush(&s,left*right);
                    break;
                case '/':
                    StackPush(&s,left/right);
                    break;
            }
        }       
    }
    ret=StackTop(&s);
    StackDestroy(&s);
    return ret;
}

7.验证栈序列
验证栈序列就是给一个入栈序列和出栈序列,判断这个出栈序列是否能由入栈序列得到,如果可以,返回true,否则返回false
分析:定义两个指针,一个指向入栈序列头,一个指向出栈序列头,如果此时栈是空的或者此时栈顶元素与出栈指针指向的元素不相等,继续让入栈序列的元素入栈,入栈指针向后走,直到遇到与出栈指针指向的元素相等的栈顶元素时,取出栈顶元素,出栈指针向后走,循环,直到出栈指针越界,说明出栈序列遍历完成,结束。
代码见下:

bool validateStackSequences(int* pushed, int pushedSize, int* popped, int poppedSize){
    Stack s;
    int inidx=0;
    int outidx=0;
    StackInit(&s);
    while(outidx<poppedSize)
    {
        while(StackEmpty(&s)||StackTop(&s)!=popped[outidx])//栈为空或者栈顶元素不等于popped栈的元素,入栈
        {
            if(inidx<pushedSize)
            {
                StackPush(&s,pushed[inidx++]);
            }
            else
             return false;
        }
        //栈顶元素等于popped栈的元素,出栈,outidx++
     StackPop(&s);
     outidx++;
    }
    return true;
}

8.用栈将递归转换为循环
例如,我们在实现单链表的逆置时,一般采用递归的方法,例如:

void printList(Node* pHead)
{
	if(pHead)
	{
		printList(pHead->next);
		printf("%d ",phead->data);
	}
}

但是这种方法很有可能会造成栈溢出,可以发现,递归的过程类似于栈的特性先进后出,因此可以使用栈将递归转换为循环,这样就不会产生栈溢出的问题,将上述代码可以变为:

void printList(Node* pHead)
{
	Node* cur=pHead;
	Stack s;
	StackInit(&s);
	while(cur)
	{
		StackPush(&s,cur->data);
		cur=cur->next;
	}
	while(!StackEmpty(&s))
	{
		printf("%d ",StackTop(&s));
		StackPop(&s);
	}
	StackDestroy(&s);
}
栈和队列的区别:

1.栈是先进后出,队列是先进先出
2.栈只允许在一头进行插入和删除,队列在一端插入,另一端删除
3.在栈中遍历数据需要扫描全部元素,而队列可以从两端扫描,所以相对来说队列比较快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值