热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:结构与算法栈

本文由编程笔记#小编为大家整理,主要介绍了结构与算法-----栈相关的知识,希望对你有一定的参考价值。  前面讲解了数组,数组更多的是用来进行数据的存储
本文由编程笔记#小编为大家整理,主要介绍了结构与算法-----栈相关的知识,希望对你有一定的参考价值。


    前面讲解了数组,数组更多的是用来进行数据的存储,我们期望的数据结构是插入、删除和查找性能都比较好。对于无序数组,插入快,但是删除和查找都很慢,为了解决这些问题,后面我们会讲解比如二叉树、哈希表的数据结构。

  而本篇博客讲解的数据结构和算法更多是用作程序员的工具,它们作为构思算法的辅助工具,而不是完全的数据存储工具。这些数据结构的生命周期比数据库类型的结构要短得多,在程序执行期间它们才被创建,通常用它们去执行某项特殊的业务,执行完成之后,它们就被销毁。这里的它们就是——栈和队列。


1、栈的基本概念

  stack)又称为堆叠,栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

          技术分享图片                  技术分享图片

  栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(push),删除则称为退栈(pop)。

  由于堆叠数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。栈也称为后进先出表。

  这里以羽毛球筒为例,羽毛球筒就是一个栈,刚开始羽毛球筒是空的,也就是空栈,然后我们一个一个放入羽毛球,也就是一个一个push进栈,当我们需要使用羽毛球的时候,从筒里面拿,也就是pop出栈,但是第一个拿到的羽毛球是我们最后放进去的。


2、Java模拟简单的顺序栈实现

  技术分享图片

      


package com.jkqiang.demo;
public class MyStack {
private int[] array; // 容量
private int maxSize; // 最大
private int top; // 栈顶
public MyStack(int size) {
this.maxSize = size;
array
= new int[size];
top
= -1;
}
// 压入数据
public void push(int value) {
if (top ) {
array[++top] = value;
}
}
// 弹出栈顶数据
public int pop() {
return array[top--];
}
// 访问栈顶数据
public int peek() {
return array[top];
}
// 判断栈是否为空
public boolean isEmpty() {
return (top == -1);
}
// 判断栈是否满了
public boolean isFull() {
return (top == maxSize - 1);
}
}

  测试:

      


public class Test {
public static void main(String[] args) {
MyStack stack
= new MyStack(3);
stack.push(
1);
stack.push(
2);
stack.push(
3);
System.out.println(stack.peek());
while(!stack.isEmpty()){
System.out.println(stack.pop());
}

}
}

结果:

  技术分享图片

  该栈是用数组实现的,内部定义了一个数组,一个表示最大容量的值以及一个指向栈顶元素的top变量。构造方法根据参数规定的容量创建一个新栈,push()方法是向栈中压入元素,指向栈顶的变量top加一,使它指向原顶端数据项上面的一个位置,并在这个位置上存储一个数据。pop()方法返回top变量指向的元素,然后将top变量减一,便移除了数据项。要知道 top 变量指向的始终是栈顶的元素。

  产生的问题:

  ①、上面栈的实现初始化容量后,后面是不能进行扩容的(虽然栈不是用来存储大量数据的),如果说后期数据量超过初始容量之后怎么办?自动扩容

  ②、我们是用数组实现栈,在定义数组类型的时候,也就规定了存储在栈中的数据类型,那么同一个栈能不能存储不同类型的数据呢?声明为Object

  ③、栈需要初始化容量,而且数组实现的栈元素都是连续存储的,那么能不能不初始化容量呢?改为由链表实现


3、增强功能版栈

  对于上面出现的问题,第一个能自动扩容,第二个能存储各种不同类型的数据,解决办法如下:(第三个在讲链表的时候在介绍)

  这个模拟的栈在JDK源码中,大家可以参考 Stack 类的实现。

  技术分享图片

 

 

 

        


package com.jkqiang.demo;
import java.util.Arrays;
import java.util.EmptyStackException;
public class ArrayStack {
// 存储元素的数组,声明为Object类型能存储任意类型的数据
private Object[] elementData;
private int top; // 指向栈顶的指针
private int size; // 栈的总容量
// 默认构造一个容量为10的栈

public ArrayStack() {
this.elementData = new Object[10];
this.top = -1;
this.size = 10;
}
public ArrayStack(int initialCapacity) {
if (initialCapacity <0) {
throw new IllegalArgumentException("栈初始容量不能小于0: " + initialCapacity);
}
this.elementData = new Object[initialCapacity];
this.top = -1;
this.size = initialCapacity;
}
// 压入元素
public Object push(Object item) {
isGrow(top
+ 1); // 是否需要扩容
elementData[++top] = item;
return item;
}
// 弹出栈顶元素
public Object pop() {
Object obj
= peek();
remove(top);
return obj;
}
// 获取栈顶元素
public Object peek() {
if (top == -1) {
throw new EmptyStackException();
}
return elementData[top];
}
// 判断栈是否为空
public boolean isEmpty() {
return (top == -1);
}
// 删除栈顶元素
public void remove(int top) {
// 栈顶元素置为null
elementData[top] = null;
this.top--;
}
/**
* 是否需要扩容,如果需要,则扩大一倍并返回true,不需要则返回false
*
*
@param minCapacity
*
@return
*/
public boolean isGrow(int minCapacity) {
int oldCapacity = size;
// 如果当前元素压入栈之后总容量大于前面定义的容量,则需要扩容
if (minCapacity >= oldCapacity) {
// 定义扩大之后栈的总容量
int newCapacity = 0;
// 栈容量扩大两倍(左移一位)看是否超过int类型所表示的最大范围
if ((oldCapacity <<1) - Integer.MAX_VALUE > 0) {
newCapacity
= Integer.MAX_VALUE;
}
else {
newCapacity
= (oldCapacity <<1); // 左移一位,相当于*2
}
this.size = newCapacity;
int[] newArray = new int[size];
elementData
= Arrays.copyOf(elementData, size);
return true;
}
else {
return false;
}
}
}

测试结果:


public class Test {
public static void main(String[] args) {
ArrayStack stack
= new ArrayStack(3);
stack.push(
1);
//System.out.println(stack.peek());
stack.push(2);
stack.push(
3);
stack.push(
"abc");
System.out.println(stack.peek());
stack.pop();
stack.pop();
stack.pop();
System.out.println(stack.peek());

}
}

  技术分享图片


4、利用栈实现字符串逆序

  我们知道栈是后进先出,我们可以将一个字符串分隔为单个的字符,然后将字符一个一个push()进栈,在一个一个pop()出栈就是逆序显示了。如下:

  将 字符串“how are you” 反转!!!

  ps:这里我们是用上面自定的栈来实现的,大家可以将ArrayStack替换为JDK自带的栈类Stack试试

       


public class Test {
public static void main(String[] args) {
ArrayStack stack
= new ArrayStack();
String str
= "how are you";
char[] cha = str.toCharArray();
for(char c : cha){
stack.push(c); //入栈
}
while(!stack.isEmpty()){
System.out.print(stack.pop()); //出栈
}

}
}

测试结果:

  技术分享图片


5、利用栈判断分隔符是否匹配   

  写过xml标签或者html标签的,我们都知道<必须和最近的>进行匹配,[ 也必须和最近的 ] 进行匹配。

  比如:这是符号相匹配的,如果是 abc] 那就是不匹配的。

  对于 12,我们分析在栈中的数据:遇到匹配正确的就消除

  技术分享图片

  最后栈中的内容为空则匹配成功,否则匹配失败!!!

       


public class Test {
////分隔符匹配
//遇到左边分隔符了就push进栈,遇到右边分隔符了就pop出栈,看出栈的分隔符是否和这个有分隔符匹配

public static void main(String[] args) {
ArrayStack stack
= new ArrayStack(3);
String str
= "12";
char[] cha = str.toCharArray();
for(char c : cha){
switch (c) {
case ‘{‘:
case ‘[‘:
case<:
stack.push(c);
break;
case ‘}‘:
case ‘]‘:
case>:
if(!stack.isEmpty()){
char ch = stack.pop().toString().toCharArray()[0];
if(c==‘}‘ && ch != ‘{‘
|| c==‘]‘ && ch != ‘[‘
|| c==‘)‘ && ch != ‘(‘){
System.out.println(
"Error:"+ch+"-"+c);
}
}
break;
default:
break;
}
}

}
}

 













1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31



//分隔符匹配

//遇到左边分隔符了就push进栈,遇到右边分隔符了就pop出栈,看出栈的分隔符是否和这个有分隔符匹配

@Test

public void testMatch(){

    ArrayStack stack = new ArrayStack(3);

    String str = "12";

    char[] cha = str.toCharArray();

    for(char c : cha){

        switch (c) {

        case ‘{‘:

        case ‘[‘:

        case ‘<‘:

            stack.push(c);

            break;

        case ‘}‘:

        case ‘]‘:

        case ‘>‘:

            if(!stack.isEmpty()){

                char ch = stack.pop().toString().toCharArray()[0];

                if(c==‘}‘ && ch != ‘{‘

                    || c==‘]‘ && ch != ‘[‘

                    || c==‘)‘ && ch != ‘(‘){

                    System.out.println("Error:"+ch+"-"+c);

                }

            }

            break;

        default:

            break;

        }

    }

}





 


6、总结

  根据栈后进先出的特性,我们实现了单词逆序以及分隔符匹配。所以其实栈是一个概念上的工具,具体能实现什么功能可以由我们去想象。栈通过提供限制性的访问方法push()和pop(),使得程序不容易出错。

  对于栈的实现,我们稍微分析就知道,数据入栈和出栈的时间复杂度都为O(1),也就是说栈操作所耗的时间不依赖栈中数据项的个数,因此操作时间很短。而且需要注意的是栈不需要比较和移动操作,我们不要画蛇添足。


推荐阅读
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
520TING小妖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有