数据结构java版本 队列,链表,栈

数据结构和算法的关系

  • 数据结构是一门研究组织数据方式的学科,
  • 程序=数据结构+算法
  • 数据结构是算法的基础

数据结构结构:

  • 线性结构:数组,队列,链表,栈
    • 顺序存储结构
    • 链式存储结构
  • 非线性结构:二维数组,多维数组,广义表,树,图

稀疏数组的概念

当一个数组中大部分元素为0时,或者为同一个值的数组时,可以使用
记录数组一共有几行几列,有多少个不同的数值
把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
二维数组转为稀疏数组:

  • 遍历原始的二维数组,得到有效数据的个数sum
  • 根据sum可以创建稀疏数组,三列,sum+1行
  • 将二维数组的有效数据存入稀疏数组,第一行存储行列,个数,

稀疏数组转原始二维数组:

  • 先读取稀疏数组的第一行,根据第一行的数据,创建二维数组
  • 再读取稀疏数组后面行的数组,并赋值给原始二维数组即可

下面是演示如何将一个二维数组转换为稀疏数组

public class SparseArray {

    public static void main(String[] args) throws IOException {
        //创建一个原始的二维数组11*11
        //0:表示没有棋子,1表示黑子,2表示白子
        int chessArray1[][] = new int[11][11];
        chessArray1[1][2] = 1;
        chessArray1[2][3] = 2;
        //输出原始的二维数组
        System.out.println("原始的二维数组");
        for (int[] row:chessArray1) {
            for (int data : row) {
                System.out.print(data+"\t");
            }
            System.out.println();
        }
        //将二维数组压缩为稀疏数组
        //先遍历二维数组,得到非0数据的个数
        int sum = 0;
        for (int i=0;i<11;i++){
            for (int j=0;j<11;j++){
                if (chessArray1[i][j]!=0){
                    sum++;
                }
            }
        }

        //创建对应的稀疏数组
        int sparseArr[][] = new int[sum+1][3];
        //给稀疏数组赋值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        //以上是给第一行进行赋值,声明原来数组中值的个数和大小

        //遍历二维数组,将非0的值存放到稀疏数组中
        int count=0;//用于记录是第几个非0数据
        for (int i=0;i<11;i++){
            for (int j=0;j<11;j++){
                if (chessArray1[i][j]!=0){
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArray1[i][j];
                }
            }
        }

        //输出稀疏数组的形式
        System.out.println("得到的稀疏数组为");
        for (int[] row:sparseArr) {
            for (int data : row) {
                System.out.print(data+"\t");
            }
            System.out.println();
        }

        //稀疏数组存入文件
        File file = new File("C:\\Users\\10505\\Desktop\\arr.txt");
        FileWriter fw = new FileWriter(file);
        for (int[] row:sparseArr) {
            for (int data : row) {
                fw.write(data+"\t");
            }
            fw.write("\r\n");
        }
        fw.close();
        //将稀疏数组恢复为原始的二维数组
        //1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
        //稀疏数组第一行第一列就是二维数组行数;稀疏数组第一行第二列为二维数组列数
        int chessArray2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];

        for (int index=1;index<sparseArr.length;index++){
            chessArray2[sparseArr[index][0]][sparseArr[index][1]] = chessArray2[index][2];
        }
        //输出恢复后的二维数组
        System.out.println("恢复后的二维数组");

        for (int[] row:chessArray1) {
            for (int data : row) {
                System.out.print(data+"\t");
            }
            System.out.println();
        }

        //读取文件中的稀疏数组
        BufferedReader reader = new BufferedReader(new FileReader(file));
        String line ;
        int row = 0;
        //先定义一个数组来承接读取的数据
        int sparseArr2[][] = new int[3][3];
        while ((line=reader.readLine())!=null){
            String [] temp = line.split("\t");
            for (int i = 0; i < temp.length; i++) {
                sparseArr2[row][i] = Integer.parseInt(temp[i]);
            }
            row++;
        }
        reader.close();
        System.out.println("打印从文件中读取的稀疏数组");
        for (int[] rows:sparseArr2) {
            for (int data : rows) {
                System.out.print(data+"\t");
            }
            System.out.println();
        }
    }
}

以上就是稀疏数组的压缩和解压缩,原理其实很简单,只是代码写起来有些麻烦,还要文件的写入和写出,就当是顺带复习一下把

队列

先入先出,
这里使用数组模拟队列,创建一个类,类中定义一个数组代表队列,再定义头指针和尾指针,

public class ArrayQueueDemo {

    public static void main(String[] args) {
        //测试
        ArrayQueue queue = new ArrayQueue(3);
        char key = ' ';//接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单
        while (loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列取出数据");
            System.out.println("h(head):查看队列头数据");
            key = scanner.next().charAt(0); //接收一个字符
            switch (key){
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数字");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g':   //取出数据
                    try {
                        int res = queue.getQueue();
                        System.out.println("取出的数据是"+res);
                    } catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':   //查看队列头部数据
                    try {
                        int res = queue.headQueue();
                        System.out.println("取出的头部数据:"+res);
                    } catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':   //退出
                    scanner.close();
                    loop = false;
                    break;
                 default:
                     break;
            }
        }
        System.out.println("程序退出");
    }
}
//使用数组模拟队列,编写一个ArrayQueue类
class ArrayQueue{
    private int maxSize;//数组的最大容量
    private int front; //指向队列头
    private int rear; //队列的尾部
    private int[] arr; //该数组用于存放数据,模拟队列

    //创建队列的构造器
    public ArrayQueue (int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize]; //初始化的时候创建一个数组,这样可以往里面存储数据
        front = -1;    //初始值头和尾都是-1,指向队列头的前一个位置
        rear = -1;  //指向队列尾的具体位置,即就是队列最后一个数据的下标
    }

    //判断队列是否满
    public boolean isFull(){
        return rear == maxSize - 1;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int n){
        if (isFull()){
            System.out.println("队列满,无法加入");
            return;
        }
        rear++; //让rear后移
        arr[rear] = n;
    }

    //数据出队列
    public int getQueue(){
        //判断队列是否为空
        if (isEmpty()){
            throw new RuntimeException("队列为空,无法取出数据");
        }
        front ++; //front往后移,然后将这个下标的元素移出
        return arr[front];
    }

    //显示队列的所有数据
    public void showQueue(){
        if (isEmpty()) {
            System.out.println("队列为空,无数据");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println("a["+i+"] = "+arr[i]);
        }
    }

    //显示队列的头数据,
    public int headQueue(){
        //判断为空
        if (isEmpty()) {
            throw new RuntimeException("队列为空,没有数据");
        }
        return arr[front+1];
    }
}

但是这个存在一些问题,不是动态的,可能会造成空间的浪费,

接下来对队列进行改造

思路如下:

  • 1.front指向队列的第一个元素,
  • 2.rear指向对了最后一个元素的后一个值
  • 3.队列满的条件 (rear+1)%maxSize = front
  • 4.队列判空,rear==front
  • 5.队列中的数据的个数 (rear+maxSize-front)%maxSize
  • 6.可以在原来的队列上修改

需要把原来的工具类修改一下
//使用数组模拟队列,编写一个ArrayQueue类

class CircleArray {
    private int maxSize;//数组的最大容量
    private int front; //指向队列头
    private int rear; //队列的尾部的后一个值
    private int[] arr; //该数组用于存放数据,模拟队列

    //创建队列的构造器
    public CircleArray(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize]; //初始化的时候创建一个数组,这样可以往里面存储数据
        rear = 0;
        front = 0;
    }

    //判断队列是否满
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int n) {
        if (isFull()) {
            System.out.println("队列满,无法加入");
            return;
        }
        arr[rear] = n;
        rear = (rear + 1) % maxSize; //让rear后移,因为有可能循环,所以需要取模
    }

    //数据出队列
    public int getQueue() {
        //判断队列是否为空
        if (isEmpty()) {
            throw new RuntimeException("队列为空,无法取出数据");
        }
        //这里需要分析出front是指向队列的第一个元素
        //先取出保存为临时变量,将front后移,将临时变量返回
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }

    //显示队列的所有数据
    public void showQueue() {
        if (isEmpty()) {
            System.out.println("队列为空,无数据");
            return;
        }
        //从front开始遍历,遍历多少个元素
        for (int i = front; i <= front + size(); i++) {
            System.out.println("a[" + i % maxSize + "] = " + arr[i % maxSize]);
        }
    }

    //求出当前队列有效数据个数
    public int size() {
        return (rear + maxSize - front) % maxSize;
    }

    //显示队列的头数据,
    public int headQueue() {
        //判断为空
        if (isEmpty()) {
            throw new RuntimeException("队列为空,没有数据");
        }
        return arr[front];
    }
}

好像有些bug,但是我也懒得改了

链表

public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //先创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
        
        //创建一个链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //加入
//        singleLinkedList.add(hero1);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero4);

        //打乱编号插入
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero3);
        singleLinkedList.addByOrder(hero4);
        singleLinkedList.addByOrder(hero2);

        //显示
        singleLinkedList.list();

        //测试修改节点的代码
        HeroNode newHeroNode = new HeroNode(2,"小王八","麒麟");
        singleLinkedList.update(newHeroNode);
        //显示
        singleLinkedList.list();

        //删除一个节点
        singleLinkedList.del(1);
        singleLinkedList.del(4);
        singleLinkedList.del(3);
        singleLinkedList.del(3);
        System.out.println("删除之后");
        singleLinkedList.list();
    }
}

//定义SingleLinkedList 管理链表
class SingleLinkedList{
    //先初始化一个头节点,头节点不要动,不存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到我们的单向链表
    public void add (HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next!=null){
            temp = temp.next;
        }
        temp.next = heroNode;

        /*while (true){
            if (temp.next == null){
                break;
            }
            //如果没有找到最后,temp后移
            temp = temp.next;
        }
        //退出while循环是,temp指向链表的最后
        temp.next = heroNode;*/
    }

    //第二种方式添加链表元素,将元素插入到指定的位置
    public void addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们仍然通过一个辅助指针来帮助找到添加的位置
        //因为是单链表,因此我们找的temp是位于添加位置的前一个节点,否则插入不了
        HeroNode temp = head;
        boolean flag = false; //标识添加的编号是否存在,默认为false,表示可以插入
        while (true) {
            if (temp.next == null) {//说明temp已经在链表的最后,
                break;  //应该要把添加的节点放到末尾了
            }
            if (temp.next.no > heroNode.no) {

                break;
            } else if (temp.next.no == heroNode.no){//说明位置已经存在
                flag = true; //说明编号存在
                break;
            }
            temp = temp.next;
        }
        //判断flag的值
        if (flag) {//不能添加,编号存在
            System.out.println("准备插入的英雄的编号"+heroNode.no+"已经存在了,不能加入 ");
        } else {
            //插入到链表中
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //修改节点的信息,根据no来修改,即编号不能被修改
    public void update(HeroNode heroNode) {

        //判断是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点,根据no编号,先定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break; //表示已经遍历完了链表
            }
            if (temp.no == heroNode.no) {
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }

        //根据flag判断是否找到需要进行修改的节点
        if (flag) {
            //temp = heroNode;
            temp.name = heroNode.name;
            temp.nickName = heroNode.nickName;
        } else {
            System.out.println("没有找到编号" + heroNode.no + "的节点");
        }

    }

    //删除一个节点
    //1.head不能动,因此需要一个temp辅助节点找到待删除节点的前一个节点
    //2.说明我们在比较时,是temp.next.no和需要删除的节点的no进行比较
    public void del (int no){
        HeroNode temp = head;
        boolean flag = false; //标识是否找到待删除节点的前一个节点
        while (true) {
            if (temp.next == null) { //已经到了链表的最后
                break;
            }
            if (temp.next.no == no) {
                //找到了待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;   //temp后移,遍历
        }
        if (flag) { //找到
            //可以删除
            temp.next = temp.next.next;
        }else {
            System.out.println("需要删除的节点"+no+"不存在");
        }
    }

    //遍历显示链表
    public void list(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,因此我们需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while (true) {
            //判断是否到链表最后
            if (temp == null) {
                break;
            }
            //
            System.out.println(temp);
            temp = temp.next;
        }
    }
}

//定义一个HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next; //指向下一个节点
    //构造器
    public HeroNode(int hNo,String name,String nickName){
        this.no = hNo;
        this.name = name;
        this.nickName = nickName;
    }

    //重写toString方法

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

具体的就是根据指针进行增删改查

下面是对链表的一些扩展

//获取单链表的节点的个数
    public static int getLength(HeroNode head){
        if (head.next == null) {
            return 0;
        }
        int length = 0;
        //定义一个辅助变量
        HeroNode cur = head.next;
        while (cur != null) {
            length++;
            cur = cur.next;
        }
        return length;
    }

查找单链表中的倒数第k个节点[新浪面试题]

//1.编写一个方法,接收head节点,同时接收一个index,表示倒数多少个
    //3.先把链表从头到尾遍历,看有多少个节点,然后再计算正向是第几个节点
    //4.得到size之后,从链表的第一个开始遍历,遍历size-index,就可以得到
    //5.如果找到了,就返回该节点,否则返回null
    public static HeroNode findLastIndexNode(HeroNode head,int index){
        //判断链表是否为空
        if (head.next == null) {
            return null;
        }
        //第一次遍历得到链表的长度(节点个数)
        int size = getLength(head);
        //第二次,遍历去求解 size-index,就是要求的节点
        //先校验
        if (index <= 0 || index > size) {
            return null;
        }
        //定义辅助变量
        HeroNode cur = head.next;
        //从第一个节点开始,移动size-index个节点就是了
        for (int i=0;i<size-index;i++){
            cur = cur.next;
        }
        return cur;
    }

采用头插法进行单链表反转

public static void reverse(HeroNode head){
        //如果当前链表为空或者只有一个节点,就无需反转
        if (head.next == null || head.next.next == null) {
            return;
        }
        //这是我自己写的,可以使用
        /*HeroNode reverseHead = new HeroNode(0,"","") ;
        HeroNode temp ;
        while (head.next != null) {
            temp = head.next;
            head.next = temp.next;
            temp.next = reverseHead.next;
            reverseHead.next = temp;
        }
        head.next = reverseHead.next;*/

        //定义一个辅助的指针,帮助我们遍历原来的链表
        HeroNode cur = head.next;
        HeroNode hNext = null; //指向当前节点的下一个节点
        HeroNode reverseHead = new HeroNode(0,"","") ;
        //遍历原来的链表,并取出节点,头插加入新的链表
        while (cur != null) {
            hNext = cur.next; //先暂时保留当前节点的下一个节点,因为后面需要使用
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = hNext;
        }
        head.next = reverseHead.next;
    }

接下来将单向链表改造为双向链表

遍历双向链表方法

public void list(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,因此我们需要一个辅助变量来遍历
        HeroNode2 temp = head.next;
        while (true) {
            //判断是否到链表最后
            if (temp == null) {
                break;
            }
            //
            System.out.println(temp);
            temp = temp.next;
        }
    }

双向链表添加

public void add (HeroNode2 heroNode){
        HeroNode2 temp = head;
        while (temp.next!=null){
            temp = temp.next;
        }
        temp.next = heroNode;
        heroNode.pre = temp;

        /*while (true){
            if (temp.next == null){
                break;
            }
            //如果没有找到最后,temp后移
            temp = temp.next;
        }
        //退出while循环是,temp指向链表的最后
        temp.next = heroNode;*/
    }

修改双向链表一个节点

public void update(HeroNode2 heroNode) {

        //判断是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点,根据no编号,先定义一个辅助变量
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break; //表示已经遍历完了链表
            }
            if (temp.no == heroNode.no) {
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }

        //根据flag判断是否找到需要进行修改的节点
        if (flag) {
            //temp = heroNode;
            temp.name = heroNode.name;
            temp.nickName = heroNode.nickName;
        } else {
            System.out.println("没有找到编号" + heroNode.no + "的节点");
        }

    }

双向链表中删除一个节点

public void del (int no){
        if (head.next == null) {
            System.out.println("链表为空,不能删除");
            return;
        }
        HeroNode2 temp = head.next;
        boolean flag = false; //标识是否找到待删除节点
        while (true) {
            if (temp == null) { //已经到了链表的最后节点的next
                break;
            }
            if (temp.no == no) {
                //找到了待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) { //找到
            //可以删除
            temp.pre.next = temp.next;
            //如果是最后一个节点,就不需要执行下面的语句,否则会空指针
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }
        }else {
            System.out.println("需要删除的节点"+no+"不存在");
        }
    }

按照顺序往里面插入节点

public void addByOrder(HeroNode2 heroNode){
        if (head.next == null) {
            head.next = heroNode;
            heroNode.pre = head;
        }
        HeroNode2 temp = head.next;
        while (temp != null) {
            if (temp.no > heroNode.no) {
                heroNode.pre = temp.pre;
                temp.pre.next = heroNode;
                heroNode.next = temp;
                temp.pre = heroNode;
                break;
            }
            temp = temp.next;
        }
    }

约瑟夫问题

约瑟夫需要创建一个单向的循环链表这样更加容易解决问题,

class CircleLinkedList{

    private Boy first = null;

    //添加小孩节点,构建成一个环形链表
    public void addBoy(int nums){
        //nums做校验
        if (nums < 1) {
            System.out.println("nums的值不正确");
            return;
        }
        Boy cur = null; //辅助变量,帮助构建环形链表
        //使用for循环来创建我们的环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号,创建小孩节点
            Boy boy = new Boy(i);
            if (i == 1) {
                first = boy;
                first.next = first; //构成一个环状
                cur = first;
            } else {
                cur.next = boy;
                boy.next = first;
                cur = cur.next;
            }
        }
    }

    //遍历当前环形链表
    public void showBoy(){
        //判断链表是否为空
        if (first == null) {
            System.out.println("链表为空");
            return;
        }

        //因为first不能动,定义一个辅助指针,完成遍历
        Boy cur = first;
        while (true) {
            System.out.println("小孩的编号"+cur.no);
            if (cur.next == first) {
                System.out.println("遍历完毕");
                break;
            }
            cur = cur.next;
        }
    }
}

然后求解的时候也有些复杂,
根据用户的输入,看有多少个小孩,然后计算出小孩出去的顺序

  • 需要创建一个辅助的指针,因为我们刚开始的时候是从第一个孩子数起,所以使用辅助指针
  • 辅助指针最开始指向头节点的前一个,
  • 我们每查两次都会删除一个,意思就是辅助指针的next。next节点被删除
  • 我们需要一个指针去往前走两步找到需要删除的节点,但是节点删除之后这个指针指向的位置就很尴尬
  • 所以我们每次都靠辅助指针来决定起始位置
  • 当小孩报数时,让first和helper同时移动m-1次,
  • 这时将first指向的节点进行删除,
  • 具体操作:first节点向前移动,辅助节点的next = first即可 ,
  • 这样被删除的节点就没有任何引用了,然后就会被jvm垃圾回收
  • 辅助指针向后移动一个,这样它还是会指向下次开始节点的前一个节点
/**
     * 根据用户的输入,计算出出圈的顺序
     * @param startNo 表示从第几个小孩开始数
     * @param countNum 表示数几次删除一个
     * @param nums 表示最初有多少小孩在圈中
     */
    public void countBoy(int startNo,int countNum,int nums){
        //对数据进行校验
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("数据输入有误");
            return;
        }
        //先创建一个辅助指针,帮助完成小孩出圈
        Boy helper = first;
        //让helper指向环形队列的最后节点
        while (helper.next != first) {//helper指向最后的节点
            helper = helper.next;
        }
        //小孩报数前,先让first和helper移动到指定的节点
        for (int j=0;j<startNo-1;j++){
            first = first.next;
            helper = helper.next;
        }

        //当小孩报数时,让first和helper指针同时移动m-1次,然后出圈
        //这时一个循环的操作,直到圈中只有一个节点
        while (true){
            if (helper == first){ //这时说明权中只有一个节点了
                break;
            }
            //让first和helper同时移动countNum-1次,然后出圈
            for (int j=0;j<countNum-1;j++){
                first = first.next;
                helper = helper.next;
            }
            //这时first指向要出圈的小孩节点
            System.out.println(first.no+"小孩出圈");
            first = first.next;
            helper.next = first;
        }
        System.out.println("最后留在圈中的小孩"+first.no);
    }

  • 先入后出
  • 变化的一端为栈顶,不变的一端为栈底
  • 出栈pop,入栈push

栈的应用场景:

  • 子程序调用
  • 递归调用
  • 表达式转换
  • 二叉树遍历
  • 图的深度优化

使用数组来模拟栈

class ArrayStack{
    private int maxSize; //栈的容量
    private int[] stack; //数组,数组模拟栈,数据就放在该数组
    private int top = -1; //top表示栈顶,初始化为-1

    //构造器
    public ArrayStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    //栈满
    public boolean isFull(){
        return top >= maxSize-1;
    }

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

    //入栈
    public void push(int value){
        //先判断栈是否满
        if (isFull()) {
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }

    //出栈,将栈顶的数据返回
    public int pop(){
        //先判断栈是否空
        if (isEmpty()) {
            //抛出异常
            throw new RuntimeException("栈为空");
        }
        int value = stack[top];
        top--;
        return value;
    }

    //显示栈的情况(遍历栈),遍历时,从栈顶开始
    public void list() {
        if (isEmpty()) {
            System.out.println("栈空,没有数据");
            return;
        }
        for (int i=top;i>=0;i--){
            System.out.println("第"+i+"个数据"+stack[i]);
        }
    }
}

使用栈来完成算术表达式的思想

  • 通过一个index索引来遍历我们的表达式,
  • 如果我们发现是数字,就直接进入数栈
  • 如果是符号,分为如下的情况
  • dan当发现符号栈为空,z直接入栈,
  • 符号栈有操作符,就进行比较,如果当前d的操作符的优先级小于等于栈中的操作符就从数栈中pop出两个数,再从符号栈中pop出一个符号,然后进行运算,然后再把运算结构push入数栈,把新的符号push入符号栈
  • 如果符号栈有符号,但是当前符号的优先级大于栈中的符号,那么久把当前符号进栈
public class Calculator {

    public static void main(String[] args) {
        //根据前面的思路,完成表达式的解析运算
        String expression = "170+2*6-4";
        //创建两个栈,一个是数栈,一个是符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);

        //定义需要的相关变量
        int index = 0; //用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; //将每次扫描得到的char保存到ch
        String keepNum = ""; //用于拼接多位数
        //开始循坏的扫描expression,
        while (true) {
            //依次得到每一个expression的字符
            ch = expression.substring(index,index+1).charAt(0);
            //判断ch是什么
            if (operStack.isOper(ch)){//如果是一个运算符
                //判断当前的符号栈是否为空
                if (!operStack.isEmpty()){
                    //如果符号栈有操作符,就进行比较,
                    if (operStack.priority(ch)<=operStack.priority(operStack.peek())){
                        // 如果当前的操作符的优先级小于等于栈中操作符 pop两个数
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        //符号栈pop一个符号,进行运算,
                        oper = operStack.pop();
                        res = numStack.cal(num1,num2,oper);
                        //得到结果,入数栈,
                        numStack.push(res);
                        //将当前操作符入符号栈
                        operStack.push(ch);
                    } else {
                        //如果当前操作符的优先级大于栈中的操作符,直接入符号栈
                        operStack.push(ch);
                    }
                } else {
                    //如果当前符号栈为空,直接入栈
                    operStack.push(ch);
                }
            } else {//如果是数,直接入数栈

                //numStack.push(ch-48); //因为ASIC码和实际的数字不太对应,有些偏差
                //分析,当我们处理多位数时,不能发现一个数就立即入栈,
                //因为这可能是多位数,
                //所以我们在处理数的时候,需要向expression的表达式的index后再看一位,
                //因此我们需要定义一个变量 字符串,用于拼接,


                //处理多位数
                keepNum += ch;

                //如果ch已经是expression的最后一位,直接入栈
                if (index == expression.length()-1){
                    numStack.push(Integer.parseInt(keepNum));
                }else {
                    //判断下一个字符是不是数字,如果是数字,继续扫描,如果是运算符,则入栈
                    //只是往后看一位
                    if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
                        //如果后一位是运算符,则入栈
                        numStack.push(Integer.parseInt(keepNum));
                        //需要把keepNum清空,
                        keepNum = "";
                    }
                }

            }
            //让index+1,并判断是否扫描到expression最后
            index++;
            if (index>=expression.length()){
                break;
            }
        }


        while (true) {
            //如果符号栈为空,则计算到最后的结果,数栈中只有一个数字,
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        //将数栈最后的数pop出来
        System.out.println("表达式"+expression+"= "+numStack.pop());
    }
}

这也太繁琐了

猜你喜欢

转载自blog.csdn.net/weixin_42755322/article/details/107769939