数据结构基础4:栈

前言:栈(Stack)是一种特殊的线性表,只允许在线性表的一端操作,栈顶允许操作,栈底不允许操作。生活中的栈比如书店的一摞教科书、自助餐厅的一摞餐盘,其工作方式就是后进先出,只要规定取得时候只能从最上面取,然后新的书/盘子放在这摞东西的顶部。
栈的特性:后进先出(LIFO)。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶(压栈push),需要读数据的时候从栈顶开始弹出数据(出栈pop)。栈可以用来在函数调用的时候存储断点,所以做递归时也要用到栈。

数据结构基础4:栈_第1张图片

一、栈的作用

每当启动一个新线程时,Java虚拟机都会为它分配一个JVM栈。Java栈以帧为单位保存线程的运行状态,JVM只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈,函数调用开始和函数调用返回就对应栈桢的压栈和出栈。

函数调用:每当线程调用一个Java方法时,JVM就会在该线程对应的调用栈(call stack)中压入一个栈帧(stack frame)。

栈在程序的运行中有着举足轻重的作用,最重要的是栈以栈桢为单位保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者过程活动记录。

  • 栈桢(Stack Frame):

栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数返回地址、函数返回结果等。每个栈帧对应着一个未运行完的函数,用来实现函数调用的数据结构。

  在函数栈帧中,一般包含以下几类重要信息。

 (1)局部变量和参数:为函数局部变量开辟的内存空间。

 (2)栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过栈帧平衡计算得到),用于在本栈被弹出后恢复出上一个栈帧。

(3)函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。

注意:JVM栈是线程私有的,所以我们不用考虑多线程情况下栈数据访问同步的情况。

二、栈的数组实现

因为栈是一种插入和删除操作都被限制在一端进行的线性表,所以可以使用数组或链表来描述。

ArrayStack:内部定义一个数组char[]用于存在所有入栈的元素,数组线性表的右端定义为栈顶。栈大小在创建时指定。

栈的基本操作有入栈、出栈、查看栈顶元素、查看栈是否满/空。

​

​

public class ArrayStack
{
    private int maxSize;// 栈的大小
    private int top;//栈顶指针。当top = -1时,栈为空
    private char[] arr;

    public ArrayStack7(int size) 
    {
	   maxSize = size;
	   top = -1;
	   arr = new char[maxSize];
    }

    public void push(char value)
    { // 压入数据

	arr[++top] = value;
        maxSize++;
    }

    public char pop() 
    { // 弹出数据

       maxSize--;
       return arr[top--]; 
    }

    public char peek()
    { // 访问栈顶元素

	       return arr[top];
    }

    public boolean isFull()
    { // 栈是否满了

	  return maxSize - 1 == top;
    }

    public boolean isEmpty() 
    { // 栈是否为空

      return top == -1;
   }

}

 

上述数组栈的大小在创建时就规定好了,其实我们可以新添一个变量arrayLength,当执行push操作时,先检查top == arrayLength -1,如果栈满了就执行栈扩容操作increaseCapacity()。这样如果我们给arr一个初始capacity,就不用在构造栈时栈的大小。

public void increaseCapacity()
{
   char[] newArr = new Char[2*arrayLenth];
   
   ///遍历栈的原有数组,复制元素到新的扩容数组中
   for(int i =0;i

三、栈的链表实现

当用链表描述栈时,我们必须确定哪一端表示栈。若用链表的右端作为栈顶,则栈操作peek、push和pop的实现就需要调用链表方法get(listSize)、insert(size+1,data)和delete(listSize),每一个链表方法需要用时O(listSize)。而用链表左端作为栈顶,调用的链表方法为get(1),insert(1,data),和delete(1),每一个链表方法需要用时θ(1)。

所以分析表明,我们应该选择链表左端作为栈顶。栈的大小不固定,不需要事先指定大小。

public class LinkStack 
{
	public int stackSize;

	Chain4 link = new Chain4();
	
	public void push(int data)
	{    
		Node node = new Node(data);
		link.insertNodeByIndex(1,node);
		stackSize++;
	}
	
	public int pop()
	{
		int num = link.get(1);
		link.deleteNode(1);
		stackSize--;
		
		return num;
	}
	
        public int peek()
       {
    	return link.get(1);
	}
	
	
	public int size() 
        {
		
		return stackSize;
	}
	
	public boolean isBlank() {
		
		return stackSize == 0;
	}
	
}

 

四、Java包中的Stack类

Stack继承于Vector,只定义了默认构造函数,用来创建一个空栈。其包括由Vector定义的所有方法,也定义了自己的一些方法。T只能是类,返回的对象需要强制转换

public class Stack extends Vector<>
{

   T push(T item) // 把项压入堆栈顶部。
           
   T pop() // 移除堆栈顶部的对象,并作为此函数的值返回该对象。 

   T peek() //查看堆栈顶部的对象,但不从堆栈中移除它。 

   boolean empty() //测试堆栈是否为空。  

   int search(Object o) //返回对象在堆栈中的位置,以 1 为基数。
}

实例:

public class StackDemo {
 
    static void showpush(Stack st, int a) {
        st.push(new Integer(a));
        System.out.println("push(" + a + ")");
        System.out.println("stack: " + st);
    }
 
    static void showpop(Stack st) {
        System.out.print("pop -> ");
        Integer a = (Integer) st.pop();
        System.out.println(a);
        System.out.println("stack: " + st);
    }
 
    public static void main(String args[]) {
        Stack st = new Stack();
        System.out.println("stack: " + st);
        showpush(st, 42);
        showpush(st, 66);
        showpush(st, 99);
        showpop(st);
        showpop(st);
        showpop(st);
        try {
            showpop(st);
        } catch (EmptyStackException e) {
            System.out.println("empty stack");
        }
    }
}

输出结果:

数据结构基础4:栈_第2张图片

注意:stack.pop返回的是T类型的变量,需要强制转换为相应类比如:Interger a = (Interger)stack.pop()

 

 

你可能感兴趣的:(数据结构与算法)