用js来实现那些数据结构05(栈02-栈的应用)

简介:   上一篇文章我们一起实现了栈,那么这一篇文章我们一起来用栈解决问题。看看如何用栈来解决进制转换,平衡圆括号以及汉诺塔问题,使我们对栈有更为深入的理解。1、进制转换  我们先来看看十进制如何转换成二进制,十进制整数转换为二进制整数采用"除2取余,逆序排列"法。

  上一篇文章我们一起实现了栈,那么这一篇文章我们一起来用栈解决问题。看看如何用栈来解决进制转换平衡圆括号以及汉诺塔问题,使我们对栈有更为深入的理解。

1、进制转换

  我们先来看看十进制如何转换成二进制,十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2整除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为0时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。简单来说就是拿十进制数去除以二,如果整除了,那么余数为0,放入栈中,如果没有整除,余数就是1,放入栈中,直至相除的结果为0。依据所得到的结果,后得到的余数排列在最前面。也就是栈顶元素从左到右排列。

  我们已经知道了十进制如何转换成二进制,那么我们看看代码是怎么实现的吧。

function decimalToBinary(decNumber) {
  //声明一个stack对象
  const remStack = new Stack();
  // 需要转换的数字
  let number = decNumber;
  //每次相除后所得的余数
  let rem;
  //转换后的二进制字符串
  let binaryString = '';
//当number为0的时候结束循环
  while (number > 0) {
      //对余数向下取整,因为这里不取整的话会出现小数,js没有浮点或者整形这一说。
    rem = Math.floor(number % 2);
    // 存储当前的余数
    remStack.push(rem);
    //因为上面对number取余只是拿到了最后余数的结果,number本身并没有除以二,所以这里除以二是为了保证后面可以再一次取余的结果正确性
    number = Math.floor(number / 2);
  }
  //这里的意思是如果栈中还有元素,那么就循环拼接字符串。
  while (!remStack.isEmpty()) {
    binaryString += remStack.pop().toString();
  }

  return binaryString;
}

console.log(decimalToBinary(100));//1100100
console.log(decimalToBinary(1));//1
console.log(decimalToBinary(2));//10
console.log(decimalToBinary(111));//1101111
console.log(decimalToBinary(65));//1000001

  那么上面就实现了十进制转换二进制的方法,那么我想可不可以使它更完善一点,实现十进制转换成二进制,八进制,十六进制等。

function baseConverter(decNumber, base) {
  const remStack = new Stack();
  // 这是转换的对比字典,大家知道在十位以后的禁止转换中,10用A代表,11用B代表。
  //所以当我们在转换的时候需要使余数的数字被字母所替换。
  const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let number = decNumber;
  let rem;
  let baseString = '';
  //这里只做2到36位之间的转换,因为理论上来讲可以进行任何位数之间的互相转换。
  //但是在不同位数之间转换的时候会有更为复杂的情况
  if (!(base >= 2 && base <= 36)) {
    return '';
  }

  while (number > 0) {
    rem = Math.floor(number % base);
    remStack.push(rem);
    number = Math.floor(number / base);
  }

  while (!remStack.isEmpty()) {
    baseString += digits[remStack.pop()];
  }

  return baseString;
}


console.log(baseConverter(100,16));//64
console.log(baseConverter(1,16));//1
console.log(baseConverter(12,8));//14
console.log(baseConverter(122323123,36));//20TT4J
console.log(baseConverter(111111111,28));//6CLF9R
console.log(baseConverter(99,16));//63

  我们发现其实对于十进制转换成其它进制,貌似只是多了一个对照表digits,本质上并没有什么区别。

2、平衡圆括号

function parenthesesChecker(symbols) {
  const stack = new Stack();
  const opens = '([{';
  const closers = ')]}';
  let balanced = true;
  let index = 0;
  let symbol;
  let top;
  while (index < symbols.length && balanced) {
      // 从0开始(index的初始值),给symbol赋值
    symbol = symbols.charAt(index);
    // 这里判断symbol是否在opens中存在,即opens.indexOf的返回值不为负数。
    if (opens.indexOf(symbol) >= 0) {
      // 如果存在,那么说明是开始符号,入栈。
      stack.push(symbol);
      //那么之前判断了是否存在开始符号,如果不存在的话就不会入栈,所以stack就没有元素,自然stack为空,balanced返回false。
      //因为如果一开始的第一个符号就是尾部符号一定是无法对称平衡的。
    } else if (stack.isEmpty()) {
      balanced = false;
    } else {
      // 获取栈顶元素并移除
      top = stack.pop();
      // 这个else其实是当symbols中对应的下标没有值得时候,就说明是closer中的值之一。
      //所以这里的symbol其实是closer,所以获取最近入栈的值进行比较,就能判断出是否是平衡的。
      if (!(opens.indexOf(top) === closers.indexOf(symbol))) {
        balanced = false;
      }
    }
    // 继续循环
    index++;
  }
  // 这里,如果是balanced的,并且stack为空,那么说明我们的所有元素必然是一一对称的。
  //因为如果不对称是无法完全出栈的
  if (balanced && stack.isEmpty()) {
    return true;
  }
  return false;
}


console.log(parenthesesChecker("(()()())"))
console.log(parenthesesChecker("(({})){["))
console.log(parenthesesChecker("{}()[]"))
console.log(parenthesesChecker("{{{}}}}"))
console.log(parenthesesChecker("[][][]["))

  其实这个方法的核心在于,判断当前下标的参数是头部分还是尾部分,头部就入栈,如果是尾部就出栈头部并和当前尾部对比。正是利用了栈的特性。

3、汉诺塔

  什么是汉诺塔?我相信很多人小时候都玩过,有图有真相,没图不BB。

  在开始玩汉诺塔游戏之前,我先给大家说一下汉诺塔游戏的规则

    规则一:每次操作只能移动一个圈圈,把它从一个柱子移到另一个柱子上。

    规则二:大圈圈不能架在小圈圈的上面。

  这是游戏的规则,那么换作程序的话,规则是这样的:假设这里有三根相邻的柱子,标号为A,B,C,A柱子上由下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问至少需要移动多少次?

  我的理解,1、目的是把这个汉诺塔从一个柱子依照由下到上的顺序完整的移动到另一个柱子上,

       2、大圈不能在小圈之下,但是可以隔层放置大小圈,比如八号最大,越往上越小,那么在移动的过程中,5号是可以放在7号上面的。

       3、可以往回放。

  如果还有不清楚规则的地方,可以去这里亲自玩一下这个游戏。

  我们已经对汉诺塔有了简单的了解,那么我们看看如何用栈来实现这个游戏吧:

//plates:盘子数量,source源柱子,helper暂存柱子,dest目标柱子,sourceName源柱子名称,helperName暂存柱子名称,destName目标柱子名称,moves步数(若不传值则默认为一个数组)
function towerOfHanoi(plates, source, helper, dest, sourceName, helperName, destName, moves = []) {
    console.log(moves.length)
  //如果盘子的数字不大于0 ,那么直接返回moves,终止递归的条件。
  if (plates <= 0) return moves;

  if (plates === 1) {
    dest.push(source.pop());
    const move = {};
    move[sourceName] = source.toString();
    move[helperName] = helper.toString();
    move[destName] = dest.toString();
    moves.push(move);
  } else {
      //递归调用自身。并且将盘子的数量减少一个,这里交换了dest和helper的位置,是为了dest.push中存入的栈是helper栈,也就是说是为了存入对应的柱子。
    towerOfHanoi(plates - 1, source, dest, helper, sourceName, destName, helperName, moves);
    //从源柱子拿出最顶层的一个放入目标柱子(如果dest和helper互换了位置,那么其实这里的dest实际上代表的是helper)
    dest.push(source.pop());
    //声明常量,用来存储当前各个柱子的盘子栈况
    const move = {};
    move[sourceName] = source.toString();
    move[helperName] = helper.toString();
    move[destName] = dest.toString();
    // 存入moves
    moves.push(move);
    towerOfHanoi(plates - 1, helper, source, dest, helperName, sourceName, destName, moves);
  }
  return moves;
}

function hanoiStack(plates) {
    // 创建每一个柱子的栈对象,source是最开始拥有所有圈圈的柱子,dest是目标柱子,helper是中间的暂存柱子
  const source = new Stack();
  const dest = new Stack();
  const helper = new Stack();
  //倒序循环把每一个圈圈序号放入source栈
  for (let i = plates; i > 0; i--) {
    source.push(i);
  }
  //通过return调用towerOfHanoi计算方法。
  return towerOfHanoi(plates, source, helper, dest, 'source', 'helper', 'dest');
}
//这个方法是计算在汉诺塔的层数为plates的时候,每一个是从哪个柱子拿到哪个柱子的
function hanoi(plates, source, helper, dest, moves = []) {
  if (plates <= 0) return moves;
  if (plates === 1) {
    moves.push([source, dest]);
  } else {
    hanoi(plates - 1, source, dest, helper, moves);
    moves.push([source, dest]);
    hanoi(plates - 1, helper, source, dest, moves);
  }
  return moves;
}


console.log(hanoiStack(2))
console.log(hanoi(8, 'source', 'helper', 'dest'));

  到这里,我们对栈有了一定的了解,相信大家在今后无论什么情况下遇到“栈”这个词都不会再陌生和懵懂了。那么对栈的学习到这里就基本结束了。下一篇文章会跟大家一起学习一下队列这个数据结构。

  

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!

一只想要飞得更高的小菜鸟
目录
相关文章
|
18天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
94 9
|
9天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
19 1
|
27天前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
42 1
|
1月前
|
存储 算法 C语言
通义灵码在考研C语言和数据结构中的应用实践 1-5
通义灵码在考研C语言和数据结构中的应用实践,体验通义灵码的强大思路。《趣学C语言和数据结构100例》精选了五个经典问题及其解决方案,包括求最大公约数和最小公倍数、统计字符类型、求特殊数列和、计算阶乘和双阶乘、以及求斐波那契数列的前20项和。通过这些实例,帮助读者掌握C语言的基本语法和常用算法,提升编程能力。
63 4
|
12天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
15天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
17天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
45 4
|
20天前
|
数据可视化 JavaScript 前端开发
数据可视化进阶:D3.js在复杂数据可视化中的应用
【10月更文挑战第26天】数据可视化是将数据以图形、图表等形式呈现的过程,帮助我们理解数据和揭示趋势。D3.js(Data-Driven Documents)是一个基于JavaScript的库,使用HTML、SVG和CSS创建动态、交互式的数据可视化。它通过数据驱动文档的方式,将数据与DOM元素关联,提供高度的灵活性和定制性,适用于复杂数据的可视化任务。 示例代码展示了如何使用D3.js创建一个简单的柱状图,展示了其基本用法。D3.js的链式调用和回调函数机制使代码简洁易懂,支持复杂的布局和交互逻辑。
61 3
|
26天前
|
机器学习/深度学习 存储 人工智能
数据结构在实际开发中的广泛应用
【10月更文挑战第20天】数据结构是软件开发的基础,它们贯穿于各种应用场景中,为解决实际问题提供了有力的支持。不同的数据结构具有不同的特点和优势,开发者需要根据具体需求选择合适的数据结构,以实现高效、可靠的程序设计。
61 7
|
21天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
下一篇
无影云桌面