一、栈
栈是一种限制的线性表,它插入和删除数据只能在栈的栈顶一段进行,另一端叫做栈底。它的数据有先进后出的特点。它的功能就是将数据从一种序列改变为另一种序列。
栈从它的数据存储形式来分,有两种。
1、用顺序存储形式-----顺序栈
2、链式存储形式-----链式栈
#pragma once
typedef size_t DataType;
//顺序栈的结构定义----采用动态顺序表
typedef struct ma_seq_Stack
{
DataType * _stack;
DataType _size;
DataType _capacity;
}ma_seq_Stack;
//初始化栈
void StackInit(ma_seq_Stack * s, DataType capacity)
{
assert(s && capacity > 0);
s->_stack = (DataType*)malloc(sizeof(DataType)*capacity);
assert(s->_stack);
s->_size = 0;
s->_capacity = capacity;
}
//进栈
void StackPush(ma_seq_Stack * s, DataType x)
{
assert(s);
if (s->_size == s->_capacity)
{
s->_capacity *= 2;
s->_stack = (DataType*)realloc(s->_stack, sizeof(DataType)*s->_capacity * 2);
}
s->_stack[s->_size] = x;
s->_size++;
}
//出栈
void StackPop(ma_seq_Stack * s)
{
assert(s && s->_size);
--s->_size;
}
//返回栈的size
DataType StackSize(ma_seq_Stack * s)
{
assert(s);
return s->_size;
}
//返回栈顶元素
DataType StackTop(ma_seq_Stack * s)
{
assert(s && s->_size);
return s->_stack[s->_size - 1];
}
//判断栈是否为空
bool StackEmpty(ma_seq_Stack * s)
{
return s->_size;
}
void test_seq_Stack()
{
ma_seq_Stack stack;
StackInit(&stack, 5);
StackPush(&stack, 1);
StackPush(&stack, 2);
StackPush(&stack, 3);
StackPush(&stack, 4);
StackPush(&stack, 5);
StackPush(&stack, 6);
while (StackEmpty(&stack))
{
cout << StackTop(&stack) << " ";
StackPop(&stack);
}
cout << endl;
}
//链式栈 因为栈的删除和插入都在栈顶进行操作,所以栈顶Top始终指向栈顶前面的头结点。
typedef struct StackNode
{
DataType _data;
struct StackNode* _next;
}StackNode;
//创建节点
StackNode* CreartStackNode(DataType x)
{
StackNode* node = (StackNode*)malloc(sizeof(StackNode));
node->_data = x;
node->_next = NULL;
return node;
}
//进栈--进行类似于单链表的头插
void StackNodePush(StackNode** top, DataType x)
{
if (top == NULL)
{
*top = CreartStackNode(x);
}
else
{
StackNode* newNode = CreartStackNode(x);
newNode->_next = *top;
*top = newNode;
}
}
//出栈--进行类似于单链表的头删
void StackNodePop(StackNode** top)
{
if (top == NULL)
{
return;
}
StackNode* cur = (*top);
(*top) = (*top)->_next;
free(cur);
}
DataType StackNodeTop(StackNode* top)
{
return top->_data;
}
void testStackNode()
{
StackNode* top = NULL;
StackNodePush(&top, 1);
StackNodePush(&top, 2);
StackNodePush(&top, 3);
StackNodePush(&top, 4);
StackNodePush(&top, 5);
while (top)
{
cout << StackNodeTop(top) << " ";
StackNodePop(&top);
}
cout << endl;
}
二、队列
相当于生活中的排队一样,只允许在一段进行插入,在另一端进行删除的线性表具有先进先出的特性。允许插入的一端称为队尾,允许删除的一段称为队头。
队列也分为顺序队列和链队列
假设有n个元素的队列,需要创建一个d大于n的数组,并且把所有元素存储在数组的前n个单元,数组下标为0的为队头,入队就是在队尾加一个元素,出队就是删除下标为0的位置上的元素,那就是所有的元素得向前移动,因此时间复杂度为O(n),顺序队列的实现和顺序表完全相同。
可是想想,出队列时,没必要把数组全部移动,如果没有限制必须存放在数组的前n个单元,出队的性能会大大增加,也就是说,队头不一定存放在下标为0的位置,每出一次队列,队头的下标++一下。
为了避免只有一个元素时,对头和队尾重合,处理的很麻烦,所以引入两个指针,head指向队头元素,tail指向队尾元素的下一个位置。
但是把数组占满时,tail指向了数组的外边,这将产生很麻烦的问题。还有假设队列最后一个单元占了一个元素,但是队头前面还有空着的数组空间,此时也不能再进行入队,这就叫假溢出。
所以引入了循环队列的定义解决假溢出。
循环队列就是后面满了,再把tail设为下标为0 的地址。这种头尾相接的顺序存储称为循环队列。
可是问题又来了:刚才定义head==tail时为空队列,可是现在队列满了,也是head==tail。
办法一:设置一个flag,刚开始队列为空,head==tail,flag=0;再经历一次head==tail,设置flag=1;此时队列满;
办法二:当队列满时,要保留一个空间空着。用于tail可能比head大,也可能小,所以尽管他们相差一个位置就是满的情况,也可能是差了一圈。所以此时队列满的条件是(tail+1)%MAXSIZE==head。 计算队列长度的公式为(tail-head+MAXSIZE)%MAXSIZE。
队列的链式存储结构---链队列
相当于带头结点的单链表,只能尾插和头删。为了操作方便,队头指针指向头结点,对尾指针指向尾结点。
总结:
栈:只能进行尾插和尾删的线性表。
队列:只能进行头删和尾插的线性表。
在顺序存储方面都有一些弊端。
栈可以使用两栈共享空间解决,队列可以使用循环队列解决。
三、栈和队列的应用
1、逆波兰表达式
计算机通过把中缀表达式通过栈转为后缀表达式计算出结果的。
中缀转后缀规则:
1、初始化一个空栈。
2、从左到右依次判断,如果是操作数,就直接输出,如果是操作符,就进栈。
3、如果是“(”,没有配对,进栈。
4、如果有“)”配对,则出栈,一直到“(”出栈为止。
5、如果此时操作符的优先级大于栈顶,则进栈,否则栈顶元素出栈。
6、直到栈为空,则成功输出了后缀表达式。
计算机计算后缀表达式规则:
1、如果是操作数,则进栈。
2、如果有操作符,则将栈顶的元素作为右操作数,出栈,再讲栈顶作为左操作数,出栈,计算结果后,进栈。
3、栈中只有最后一个操作数,就是后缀表达式的值。
栈的其他功能:
1、两个栈实现一个队列
stack1只负责进栈,stack2只负责出栈,出栈时,吧stack1.pop(top),然后在push到stack2中在stack2中pop。
2、两个队列实现一个栈
和上述方法相似
3、两栈共享空间。
借助数组来存放两个栈。其中下标为0的是一个栈的栈底,另一个栈的栈底为数组的末端,两个栈增加元素就是从数组的两端向中间延伸。他们是向中间靠拢,如果两个栈的top不相遇,就可以一直使用。