栈的理解学习

栈的定义与使用

Posted by SkioFox on May 17, 2017

数据结构中的栈

栈是一种特殊的线性表,仅能够在栈顶进行操作,有着先进后出(后进先出)的特性。如下图

avatar

  • 从数据存储的角度讲,实现栈有两种方式,一种是以数组为基础,一种是以链表为基础。
    1. 以数组的方式存储数据
      // 先定义一个简单的Stack类
      function Stack(){
        // item不使用this,是为了防止变量暴露到外部,形成item私有变量。
        // this.item=[];=>非常不推荐
        // 设计类的时候属性不能暴露,非常安全
        var item = [];//使用数组存储数据
      };
      
    2. 栈的方法或者操作: push/pop/top/isEmpty/size/clear
  • push(element): 添加一个新元素到栈顶位置.
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false。
  • clear():移除栈里的所有元素。
  • size():返回栈里的元素个数。这个方法和数组的length属性很类似。
    1. 方法实现
  • push 方法:将最新的元素放在了数组的末尾
      // 压栈操作
      this.push = function (element) {
          items.push(element)
      }
    
  • pop 方法:出栈操作应该是将栈顶的元素删除, 并且返回.
      // 出栈操作
      this.pop = function (element) {
          return items.pop()
      }
    
  • peek方法:主要目的是看一眼栈顶的元素
      // peek操作
      this.peek = function () {
          return items[items.length - 1]
      }
    
  • isEmpty方法:判断栈中是否有元素
      // 判断栈中的元素是否为空
      this.isEmpty = function () {
          return items.length == 0
      }
    
  • size方法:获取栈中元素的个数
      // 获取栈中元素的个数
      this.size = function () {
          return items.length
      }
    
    1. 整体代码
// 栈类
function Stack() {
	// 栈中的属性
	var items = []

	// 栈相关的方法
	// 压栈操作
	this.push = function (element) {
		items.push(element)
	}

	// 出栈操作
	this.pop = function () {
		return items.pop()
	}

	// peek操作
	this.peek = function () {
		return items[items.length - 1]
	}

	// 判断栈中的元素是否为空
	this.isEmpty = function () {
		return items.length == 0
	}

	// 获取栈中元素的个数
	this.size = function () {
		return items.length
	}
	
	// 	清空栈
	this.clear = function () {
		items = [];
	}
}
module.exports=Stack;
// 此处不要使用如下导出方式,如下方式实例化需要new Stack.Stack(), 直接将构造器导出到module上
// exports.Stack=Stack

  1. 栈的使用
    • 测试题 avatar
    • 题目答案:C
    • A: 65进栈, 5出栈, 4进栈出栈, 3进栈出栈, 6出栈, 21进栈,1出栈, 2出栈
    • B: 654进栈, 4出栈, 5出栈, 3进栈出栈, 2进栈出栈, 1进栈出栈, 6出栈
    • D:65432进栈, 2出栈, 3出栈, 4出栈, 1进栈出栈, 5出栈, 6出栈 - 使用栈模拟
        // 模拟面试题
        var stack = new Stack()
        // 情况下代码模拟
        stack.push(6)
        stack.push(5)
        stack.pop()     // 5
        stack.push(4)
        stack.pop()     // 4
        stack.push(3)
        stack.pop()     // 3
        stack.pop()     // 6
        stack.push(2)
        stack.push(1)
        stack.pop()     // 1
        stack.pop()     // 2
      
  2. 思考
    1. 给你一个数组,你可以通过索引操作任意一个元素。但是给你一个栈,你能任意操作元素吗?栈提供给你的方法只允许你操作栈顶的元素。这种限制其实给了我们一种思考方式,这个方式也是栈的特性。后进先出,先进后出。
    2. 栈的底层实现其实就是数组,那为什么不直接使用数组的方法去做呢。这就是封装的思想,封装是为了隐藏实现细节,站在栈的角度思考问题会比数组的角度思考问题更加方便。
    3. 既然栈的底层就是对数组的操作,平时都这么熟悉数组,为什么没有自己去实现一个栈呢?尽管底层实现这么简单,越简单越能说明问题。
  3. 应用练习
    • 用一个函数判断括号匹配问题 avatar
      // 数组来存储的问题:存入数组再想办法一一抵消,但是无法判断一个左括号对应哪个右括号
      // 栈的解决思路:for循环遍历每一个字符:
       function is_leagl_brackets(string){
         var stack= new Stack();
         for (var i=0 ; i<string.length ; i++) {
             var item = string[i];
             // 遇到左括号,就把括号压入栈中
             if(item=="("){
                 stack.push(item);
             // 遇到右括号,判断栈是否为空,如果为空,说明缺少左括号,直接返回false。如果栈不为空,
             // 就把栈顶元素移除,消除这对括号。
             }else if (item==")"){
                 if(stack.isEmpty()){
                     return false;
                 }else{
                     stack.pop();
                 }
             }
         }
         // 返回栈是否为空
         return stack.isEmpty();
       }
       console.log(is_leagl_brackets("()sadad(dsadsd)saf()(DFSFS)"));
       console.log(is_leagl_brackets("()()sd()(sd()fw)("));
      
    • 后缀表达式 avatar
	function calc_exp(exp) {
		var stack=new Stack();
		for(var i=0;i<exp.length;i++){
			var item=exp[i];
			// 判断是否遇到运算符
			if(["+","-","/","*"].indexOf(item)>=0){
				
				// 弹出栈顶的两个元素
				var value_1=stack.pop();
				var value_2=stack.pop();
				var exp_str=value_2+item+value_1;
				// 计算并取整
				var res=parseInt(eval(exp_str));
				// 计算结果压入栈中,注意转成字符串,之前取整了
				stack.push(res.toString())
			// 未遇到运算符,入栈
			}else{
				stack.push(item);
				// console.log(item)
			}
		}
		// 返回栈中的元素
		return stack.pop();
	}

	console.log(calc_exp(["4","13","6","/","+"])) // 6

	console.log(calc_exp(["10","6","9","3","+","-11","*","/","*","17","+","5","+"])) // 2

  • 实现min 方法=>返回栈里最小的元素 时间负责度为o(1)
	// 引入写好的stack模块,思路:建立两个栈,一个存储数据,一个存储最小值
	Stack = require("./stack")

	function MinStack(){
		var data_stack=new Stack();  // 存储数据
		var min_stack=new Stack();  // 存储最小值
		// push方法 往栈中放入最小值作为栈顶
		this.push = function (item) {
			// 将值存入栈
			data_stack.push(item);
			// 栈为空或者item小于栈顶的元素
			if(min_stack.isEmpty()||item< min_stack.peek()){
				// item为最小值成为栈顶
				min_stack.push(item);
			}else{
				min_stack.push(min_stack.peek())
			}
		}
		// pop方法弹出栈顶
		this.pop=function(){
			data_stack.pop();
			min_stack.pop();
		}
		// min方法,返回栈中最小值
		this.min=function(){
			return min_stack.peek();
		}
	}

	minstack= new MinStack();
	minstack.push(3);
	minstack.push(6);
	minstack.push(8);
	console.log(minstack.min()); // 3
	minstack.push(2);
	console.log(minstack.min()); // 2