题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路
创建两个栈stack1和stack2,使用两个先进后出的栈实现一个先进先出的队列
1、先考虑Push操作:
向队列插入元素,因为队列和栈一样都是在表头添加,所以可以直接添加到stack1的表头。
2、再考虑Pop操作:
因为对于队列的删除操作,先进先出,是删除表尾,即先添加进来的元素。如果直接在stack1中删除的话,栈的删除是先进后出,不符合队列的删除规则,所以不能直接在stack1中删除。
因此,考虑到将stack1 中的元素依次出栈,并入栈到stack2中。此时,stack2中的元素是将先加入的元素放在栈顶,后放入的元素放在栈底。元素在stack2中的顺序正好和原来在stack1中的顺序相反。删除时对stack2 中的元素进行操作,此时删除的就是先添加的元素,符合队列的先进先出规则。
因此我们的思路是:当stack2中不为空时,在stack2中的栈顶元素是最先进入队列的元素,可以弹出。如果stack2为空时,我们把stack1中的元素逐个弹出并压入stack2。由于先进入队列的元素被压倒stack1的栈底,经过弹出和压入之后就处于stack2的栈顶,有可以直接弹出。如果有新元素d插入,我们直接把它压入stack1即可。
流程图如下:代码实现:
import java.util.Stack;
public class QueueWithTwoStacks {
class Queue{
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
/** * 插入结点 */
public void push(int node) {
stack1.push(node);
}
/** * 删除结点 */
public int pop() {
if (!stack2.empty()) {
return stack2.pop();
else{
if (stack1.empty())
throw new RuntimeException("队列为空!");
else {
while (!stack1.empty())
stack2.push(stack1.pop()); }
return stack2.pop();
}
}
}
}
PS:站与队列的基础知识
栈与队列的定义:
栈:后进先出(LIFO-last in first out):最后插入的元素最先出来。
队列:先进先出(FIFO-first in first out):最先插入的元素最先出来。
栈的理解
栈(stack)是限定仅在表尾/栈顶进行插入和删除操作的线性表。又被称为后进先出的线性表,简称LIFO结构。
注意:栈首先是一个线性表,栈元素具有线性关系及前驱后继关系。特殊之处在于限制了线性表的插入和删除位置,始终在栈顶进行。
当栈存在一个元素时,top为0,将空栈的判定条件定为top等于-1.
ADT 栈(stack) Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。 Operation
InitStack(*S) : 初始化操作,建立一个空栈S。
DestoryStack(*S) : 若栈存在,则销毁它。
ClearStack(*S) : 将栈清空。
StackEmpty(S) : 若栈为空,返回true,否则返回false。
GetTop(S, *e) : 若栈存在且非空,用e返回S的栈顶元素。
Push(*S, e) : 若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S, *e) : 删除栈S中的栈顶元素,并用e返回其值。
StackLength(S) : 返回栈S的元素个数。 endADT
由于栈本身是一个线性表,所以线性表的顺序存储和链式存储对于栈也是适用的。
栈的顺序存储结构
1、进栈操作
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 1024typedef int Status;
//插入元素e为新的栈顶元素
Status Push(SqStack *S, SElemType e) //栈满
{
if (S->top == MAXSIZE - 1) {
return ERROR; }
S->top++;
//栈顶指针增加一
S->data[S->top] = e//将新插入的元素赋值给栈顶空间
return OK;
}
2、出栈操作
#define OK 1
#define ERROR 0
typedef int Status;
typedef int SElemType;
//出栈操作,则删除栈顶元素,返回其值,否则返回ERROR
Status Pop(SqStack *s, SElemType *e)
{
if(S->top == -1)
{
return ERROR;
}
*e = s->data[s->top];
s->top --;
return OK;
}
用数组实现栈
由于数组大小未知,如果每次插入元素都扩展一次数据(每次扩展都意味着构建一个新数组,然后把旧数组复制给新数组),那么性能消耗相当严重。
这里使用贪心算法:
1、数组每次被填满后,加入下一个元素时,把数组拓展成现有数组的两倍大小。
2、每次移除元素时,检测数组空余空间有多少。当数组里的元素个数只有整个数组大小的四分之一时,数组减半。
思考:
为什么不是当数组里的元素个数只有整个数组大小的二分之一时,数组减半?
考虑以下情况:数组有4个元素,数组大小为4个元素空间。此时,加一个元素,数组拓展成8个空间;再减一个元素,数组缩小为4个空间;如此循环,性能消耗严重。
代码实现:
public class StackbyArray {
s = new String[1];
int N = 0; //N存储元素个数
public void push(String item) {
if(N == s.length)
//检测元素个数是否超过数组长度
Resize(s.length * 2);
else
//先添加元素,再将N加1.表示元素个数
s[N++] = item;
}
public int pop() {
if(N <= 0)
return 0;
if(N >0 && N = s.length/4)
{
Resize(s.length / 2);
}
//取出数组中s[N-1]位置的元素,并将该位置置为空
String out = s[--N];
s[N] = Null;
return out;
}
public Resize(int capacity){
String[] copys = new String[capacity];
for(int i = 0; i < N; i++)
{
copys[i] = s[i];
}
s = copys;
}
}
}
两栈共享空间-两栈共用一个数组
栈的顺序存储还是很方便的,因为它只准栈顶进出元素,所以不存在线性表插入和删除时需要移动元素的问题。
用一个数组存储两个栈。数组有两个端点,分别作为两个栈的栈底,即栈1的栈底为数组下标0处,栈2的栈底为数组下标n-1处。两个栈增加元素,就是两端点向中间延伸。
可知:
1、栈1为空时,top1为-1;栈2为空时,top2为n;
2、栈1的top1等于n-1时,栈1满;栈2的top2为0时,栈2满。
3、两个栈见面时,即两个指针相差为1时,即top2 = top1 + 1为栈满。
需求分析:
1、需要添加一个参数StackNumber,用来指定栈。
2、采用一个数组存储两个栈,通常是两个栈的空间需求有相反关系,即一个栈增长时,另一个栈在缩短。
3、针对两个具有相同数据类型的栈的设计。
栈的链式存储结构
对于链栈来说,是将栈顶放在单链表的头部。基本不存在栈满的情况,除非是内存没有可用空间。
对于空栈,链表原定义是头指针指向孔,则链栈的空就是top=Null。
进栈操作
新元素值为e的新节点为s,top为栈顶指针。
代码实现:
#define OK 1
#define ERROR 0
typedef int SElemType; //根据实际情况而定
typedef int Status; //根据实际情况而定
//插入元素e为新的栈顶元素
Status Push(LinkStack *S, SElemType e){ LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top; //把当前的栈顶元素赋值给新结点的直接后继,如图中操作 1
S->top = s; //将新结点s赋值给栈顶指针,如图中操作 2
S->count++; //栈的长度加一 return OK;
}
出栈操作:
变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p。
代码实现:
#define OK 1
#define ERROR 0
typedef int SElemType; //根据实际情况而定
typedef int Status; //根据实际情况而定
//出栈操作
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(LinkStack *S, SElemType *e){ LinkStackPtr p;
if (StackEmpty(S)) 判断栈是否为空
{
return ERROR;
}
*e = S->top->data;
p = S->top; //将栈顶结点赋值给p,如图中操作 3
S->top = S->top->next;
//使得栈顶指针下移一位,指向后一结点,如图中操作 4
free(p); //释放结点p
S->count--;
return OK;}
//判断栈是否为空
bool StackEmpty(LinkStack *S){
if (S->count == -1) {
return true; }
return false;
}
顺序栈和链栈的区别
对比 | 顺序栈 | 链栈 |
---|---|---|
时间复杂度 | O(1) | O(1) |
长度 | 固定 | 无限制 |
空间 | 空间浪费 | 每个元素均有指针域,增加了内存开销 |
适用范围 | 变化范围可控 | 变化范围不可控 |
用数组实现队列
与栈类似采用贪心算法:
1、数组每次被填满后,加入下一个元素时,把数组拓展成现有数组的两倍大小。
2、每次移除元素时,检测数组空余空间有多少。当数组里的元素个数只有整个数组大小的四分之一时,数组减半。
不同之处在于:
由于是先进先出,移除是从队列的最前端开始的。所以当我们移除数个数据后,队列数据是存储在数组的中间部分的,移除元素的位置需要记录,即队列头数据的位置需要记录。令队列数据的尾端数据ID为LastIndex,首端数据ID为HeadIndex,则LastIndex - HeadIndex为队列数据元素个数。
当队列数据元素个数为整个数组空间的四分之一时,数组减半,且队列数据左移至数组最左端。即LastIndex -= HeadIndex;HeadIndex=0。
代码实现:
public class QueuebyArray {
s = new String[1];
int HeadIndex = 0;
int LastIndex = 0;
public void push(String item) {
if(N == s.length)
//检测元素个数是否超过数组长度
Resize(s.length * 2);
else
//在HeadIndex+1位置添加新元素
s[++HeadIndex] = item;
}
public int pop() {
//元素个数由LastIndex >= HeadIndex判断
if(LastIndex >= HeadIndex)
return 0;
if(LastIndex < HeadIndex && (LastIndex - HeadIndex + 1) = s.length/4)
{
Resize(s.length / 2);
}
//取出数组中s[LastIndex]位置的元素,并将该位置置为空
String out = s[LastIndex++];
s[LastIndex-1] = Null;
return out;
}
public Resize(int capacity){
String[] copys = new String[capacity];
for(int i = 0; i < N; i++)
{
copys[i] = s[i];
}
s = copys;
}
}
}
数据结构中的堆栈与内存中的堆栈不同
一、数据结构中的堆栈
在数据结构中的堆栈,实际上是两种数据结构:堆和栈。堆和栈都是一种数据项按序排列的数据结构。
1.栈
一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取。
2.堆
堆是一种经过排序的树形数据结构,每个结点都有一个值。通常所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆,即父节点的值总是比子节点的值小(或大)。由于堆的这个特性,常用来实现优先队列,堆的存取是任意位置的。
二、内存空间中的堆区栈区
栈区分配局部变量空间,堆区是程序运行时申请的内存空间。另外还有静态区是分配静态变量,全局变量空间的;只读区是分配常量和程序代码空间的。