剑指offer刷题-用两个栈实现队列及栈、队列的知识补充

题目描述
用两个栈来实现一个队列,完成队列的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.堆
堆是一种经过排序的树形数据结构,每个结点都有一个值。通常所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆,即父节点的值总是比子节点的值小(或大)。由于堆的这个特性,常用来实现优先队列,堆的存取是任意位置的。

二、内存空间中的堆区栈区

栈区分配局部变量空间,堆区是程序运行时申请的内存空间。另外还有静态区是分配静态变量,全局变量空间的只读区是分配常量和程序代码空间的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值