数据结构与算法 --栈(五)

一、什么是栈?


栈:是一种“操作受限”的线性表,只允许在一端插入和删除数据,特点是先进后出(FILO

 

二、为什么需要栈?


1、任何数据结构都是对特定应用场景的抽象,数组和链表随人使用起来更加灵活,但却暴露了几乎所有操作,难免引发错误操作风险

2、所以当某些数据集合只涉及在某端插入和删除数据,且满足先进后出特性时,那么应该首选择栈这种数据结构

 

三、实现栈


栈是一种操作受限的数据结构,可以用数组和链表实现

无论是顺序栈还是链式栈。入栈,出栈只涉及栈顶个别数据的操作,所以时间复杂度都是01)

顺序栈实现 :

package dataStruct.stack;

/*
 * 栈数组实现
 * @author chao
 */
public class StackOfArray {
	Object[] items;
	int n;
	int count;

	public int size() {
		return count;
	}

	public StackOfArray(int n) {
		this.items = new Object[n];
		this.n = n;
		this.count = 0;
	}

	public void ensureCapacitity(int n) {
		Object[] temp = this.items;
		items = new Object[n * 2 + 1];
		for (int i = 0; i < temp.length; i++) {
			items[i] = temp[i];
		}
	}

	public boolean push(Object ele) {
		if (count == n) {
			ensureCapacitity(n);
		}
		;
		items[count] = ele;
		count++;
		return true;
	}

	public Object pop() {
		if (count == 0)
			return false;
		Object temp = items[count - 1];
		count--;
		return temp;
	}
}

链式栈 :

package dataStruct.stack;

/**
 * 栈链表实现
 * @author chao
 */
public class StackOfSinglyLinked {
	//头节点
	private Node head;
	//有多少个节点
	private int size;

	public boolean isEmpty() {
		return size() == 0;
	}

	public void push(Object ele) {
		//新增加的节点作为链表头节点
		Node oldNode = head;
		head = new Node(ele, oldNode);
		size++;
	}

	public Object pop() {
		if (head == null) {
			return -1;
		}
		Object value = head.ele;
		head = head.next;
		size--;
		return value;
	}

	public void clear() {
		this.head = null;
		size = 0;
	}

	public int size() {
		return size;
	}

	public String toString() {
		if (size <= 0) {
			return "[]";
		}
		Node p = head;
		StringBuilder sb = new StringBuilder(size);
		sb.append("]");
		while (p != null) {
			sb.append(p.ele);
			if (p.next != null) {
				sb.append(",");
			} else {
				sb.append("[");
			}
			p = p.next;
		}
		return sb.reverse().toString();
	}

	// 链表节点
	private static class Node {
		Object ele;
		Node next;

		public Node(Object ele, Node next) {
			this.ele = ele;
			this.next = next;
		}
	}
}

 

四、栈的应用


1、栈在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量。每进入一个函数,就会将其中的临时变量作为栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

 

2、栈在表达式求值中的应用(比如:34+13*9+44-12/3
利用两个栈,其中一个用来保存操作数,另一个用来保存运算符。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈,若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入操作数栈,继续比较

 

3、栈在括号匹配中的应用(比如:{}{[()]()}
用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明未匹配的左括号为非法格式。

package dataStruct.stack;

import java.util.HashMap;
import java.util.Map;

/*
 * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串
 */
public class Solution {
	public static void main(String[] args) {
		Solution so = new Solution();
		System.out.println(	so.isValid("]"));
	}
	Map<Character, Character> map;

	public Solution() {
		this.map = new HashMap<>();
		map.put('}', '{');
		map.put(')', '(');
		map.put(']', '[');
	}

	public  boolean isValid(String str) {
		StackOfSinglyLinked stack = new StackOfSinglyLinked();
		char[] array = str.toCharArray();
		for (int i = 0; i < array.length; i++) {
			char p = array[i];
			//从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;
			
			if (map.containsKey(p)) {
				//当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。
				Object pop = stack.pop();
				//如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式
				if (pop != map.get(p)) {
					return false;
				}
			} else {
				stack.push(p);
			}
		}

		return stack.isEmpty();
	}
}

4.如何实现浏览器的前进后退功能?
我们使用两个栈XY,我们把首次浏览的页面依次压如栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据一次放入Y栈。当点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,说明没有页面可以继续后退浏览了。当Y栈没有数据,那就说明没有页面可以点击前进浏览了。

package dataStruct.stack;

/**
 * 栈实现浏览器后退前进功能
 * @author chao
 *
 */
public class SampleBrowser {
	String currentPage; //当前页面
	StackOfSinglyLinked back; 
	StackOfSinglyLinked forward;
	
	public SampleBrowser() {
		this.back = new StackOfSinglyLinked(); 
		this.forward = new StackOfSinglyLinked(); 
	}
	//模拟打开一个页面
	public void open(String url) {
		//如果当前页面不为空,也就是之后再次打开页面就把之前的页面放入back栈中
		if(currentPage!=null) {
			back.push(this.currentPage);
			forward.clear();
		}
		showUrl(url,"open");
	}
	
	public void showUrl(String url,String prefix) {
		System.out.println(prefix+"page: "+url);
		this.currentPage=url;
	}
	
	public String goBack() {
		if(canGoBack()) {
			//回退页面的时候,将当前打开的页面放入forward栈中
			forward.push(currentPage);
			//弹出要回退到的页面
			String preUrl = (String) back.pop();
			//打开要回退到的页面
			showUrl(preUrl, "back");
			return preUrl;
		}
		
		System.out.println("Cannot go back, no pages behind.");
		return null;
	}
	public String goForward() {
		if(canGoForward()) {
			//前进页面的时候,将当前打开的页面放入back栈中
			back.push(currentPage);
			//弹出要前进的页面
			String afterUrl = (String) forward.pop();
			//打开要前进的页面
			showUrl(afterUrl, "forward");
			return afterUrl;
		}
		
		System.out.println("Cannot go forward, no pages forward.");
		return null;
	}
	
	public boolean canGoBack() {
		return back.size()>0;
	}
	public boolean canGoForward() {
		return forward.size()>0;
	}
	
	
}

思考

1、我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?

   函执调用的执行顺序符合栈的后进先出特点局部变量的生命周期应该和函数一致,随着方法的调用结束,会自动释放栈帧内存,所以用栈保存临时变量

 

2、我们都知道,JVM 内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?

内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。
内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。
代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。

摘自评论

猜你喜欢

转载自blog.csdn.net/qq_40949465/article/details/88973787
今日推荐