数据结构之栈(以字符反转和分隔符匹配为例子)

我是野猪。

栈的概念:栈作为一种数据结构,是一种只能在一端插入和删除操作的特殊线性表。它按照新进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹数据。

关键点:1.先入后出  2.访问限制,在特定的时刻,只允许一个元素被读取或删除(栈顶的元素)。3.栈操作:栈空、栈满、弹栈、入栈等

栈相关知识:

1.数据结构栈和队列更多的是作为程序员的工具来运用。他们主要作为构思算法的辅助工具,而不是完全的数据存储工具。这些数据结构的生命周期比那些数据库类型的结构要短暂的多。在程序执行期间他们才被创建,通常用他们去执行某项特殊的任务,当任务完成之后,他们就被销毁。

2.栈的主要机制可以通过数组来实现,也可以通过链表来实现。优先级队列的内部实现可以通过数组或堆来实现。

3.实际计算机系统中,大部分微处理器运用栈的体系结构。栈在程序的运行中有举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧。堆栈帧一般包含下面几个信息:①函数的返回地址和参数 ②临时变量:包含函数的非静态局部变量以及编译器自动生成的其他临时变量。当调用一个方法时,把他的返回地址和参数压入栈,当方法结束返回时,那些数据出栈。栈操作就嵌入在微处理器中。

4.栈通常很小,是临时的数据结构。


为了更好理解数据结构栈,我们根据栈的属性,编写自己的栈,并且根据栈先入后出的原则实现了两个最基本的例子:字符串反转和分隔符匹配问题。

根据性质编写自己的栈:

/**
 * 创建一个自己的栈
 * 1.先入后出的原则
 * 2.访问权限  在特定时刻只有一个数据项可以读取或者被删除  只能操作位于栈顶的元素
 * 组织数据的方式 入栈 出栈 栈空 栈满
 */

public class YezhuStack {

    //类型为Object的数组
    private final Object[] oStack;

    //数组的长度
    private int size;

    //栈顶标志
    private int top = -1;

    public YezhuStack(int max) {
        this.size = max;
        oStack = new Object[max];
    }

    /**
     * 入栈
     * 先判断是不是栈满的
     *
     * @param object
     */
    public void push(Object object) {
        if (!isFull()) {
            oStack[++top] = object;
        } else {
            throw new IndexOutOfBoundsException("the stack is full");
        }
    }

    /**
     * 栈满否
     *
     * @return
     */
    private boolean isFull() {
        return top == size - 1;
    }

    /**
     * 出栈
     * 出栈先判断栈是不是为空
     * 访问权限:在特定时刻只有一个数据项被允许读取或删除
     *
     * @return
     */
    public Object pop() {
        if (!isEmpty()) {
            return oStack[top--];
        } else {
            throw new IndexOutOfBoundsException("the stack is empty");
        }
    }

    /**
     * 栈是不是空
     *
     * @return
     */
    public boolean isEmpty() {
        return top == -1;
    }


    /**
     * 查看栈顶的元素
     */
    public Object peek() {
        if (!isEmpty()) {
            return oStack[top];
        } else {
            throw new IndexOutOfBoundsException("the stack is empty");
        }
    }
}

注意点:初始值top=-1,是根据代码做的修改。push时oStack[++top],++top =>先+1后赋值;pop时,oStack[top--],top-- => 先赋值后-1;还有判断栈是否为空,条件是top==-1而不是size=0,因为一开始初始化的时候根据要操作的数据的长度创建的栈,而在压栈和弹栈期间,其size并没有变化。


例子1:字符串反转;字符串反转就是输入一字符串,把字符串中的一个个元素从头压入栈,然后弹出栈时就是倒序。

/**
 * 测试字符串反转
 */

public class ReverseStrTest {
    private static YezhuStack yezhuStack;

    public static void main(String[] args) throws IOException {
        System.out.println("please import str:");
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String str = br.readLine();

        System.out.println("print reversed str:" + reverseStr(str));

    }

    /**
     * 字符串反转后
     *
     * @param str
     */
    private static String reverseStr(String str) {
        String out = "";

        if (str != null) {
            yezhuStack = new YezhuStack(str.length());
        }

        //1.字符串放入栈中
        for (int i = 0; i < str.length(); i++) {
            yezhuStack.push(str.charAt(i));
        }

        //2.栈直接输出即可
        for (int i = 0; i < str.length(); i++) {
            out = out + yezhuStack.pop();
        }
        return out;
    }
}

运行效果:



例子2:分隔符匹配;分隔符匹配程序从字符串中不断的读取字符,每次读取一个字符。若发现他是左分隔符,将他压入栈。当从输入中读取一个右分隔符,弹出栈顶的左分隔符,并且查看是否和右分隔符相匹配。比如eclipes编译器,当你少一个“}”时,会报错,其原理就是如此。

/**
 * 分隔符匹配问题
 * 用栈结构模拟的分隔符匹配;
 * 编译器报错的 比如当少{[(   )]} 左分隔符 压入栈,遇到右分隔符时 与栈顶元素进行匹配, 不匹配则编译器报错
 */

public class SeparatorMatchTest {
    private static YezhuStack yezhuStack;

    public static void main(String[] args) throws IOException {

        System.out.println("please import str:");
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String str = br.readLine();

        checkSeparator(str);

    }

    /**
     * 检查输入的字符串
     *
     * @param str
     */
    private static void checkSeparator(String str) {

        if (str != null) {
            yezhuStack = new YezhuStack(str.length());
        }

        //1.整个字符串中遇到左匹配符号就入栈 因此循环入栈的时候要匹配条件
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            switch (ch) {
                case '{':
                case '(':
                case '[':
                    //以上是左匹配符 遇到左匹配符入栈
                    yezhuStack.push(ch);
                    break;
                case '}':
                case ')':
                case ']':
                    //遇到右匹配符然后出栈 做对比
                    if (!yezhuStack.isEmpty()) {
                        char chRight = (char) yezhuStack.pop();
                        if ((ch == '}' && chRight != '{')
                                || (ch == '(' && chRight != ')')
                                || (ch == ']' && chRight != '[')) {
                            //ch是循环拿字符串中匹配符 chRight是栈中的左匹配符
                            System.out.println("Error: " + ch + " at " + i);
                        }
                    }
                    break;
                default:
                    break;

            }

        }

        //最后做一次判断 正常来说要是左右分隔符能全部匹配成功的话 此时栈中已经空了 如果不为空 说明右分隔符少了
        if (!yezhuStack.isEmpty()) {
            System.out.println("输入含有左右分隔符的字符串缺少右分隔符");
        }
    }
}
运行效果:



以上就是两个基本使用栈的例子,如果有问题可以私信我。

补充在java基础讲过的栈和堆内存的概念:

1.栈内存用来存储局部变量和方法调用,堆内存用来存储java中new出来的对象。

2.栈内存属於单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见;而堆内存中的对象对所有线程可见,可以被所有线程访问。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章