• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
在赶路的我
博客园    首页    新随笔    联系   管理    订阅  订阅

Java数据结构和算法(更新中)

Java数据结构和算法

几个常见的经典面试题

  • 字符串匹配问题
    1. 暴力匹配
    2. KMP算法
  • 汉诺塔游戏
  • 八皇后问题
    1. 分治算法
  • 马踏棋盘算法
    1. 图的深度优化遍历算法(DFS)+贪心算法优化

数据结构和算法的重要性

  1. 算法是程序的灵魂,优秀的程序员可以在海量数据计算时,依然保持高速计算
  2. 一般来讲,程序会使用了内存计算框架(比如Spark)和缓存技术(比如Redis)来优化程序
  3. 现在程序员的门槛越来越高,数据结构和算法必须会

数据结构和算法的关系

  1. 数据结构是研究组织数据方式的学科,有了编程语言也就有了数据结构,学号数据结构可以编写出更加漂酿,更加有效率的代码
  2. 程序=数据结构+算法
  3. 数据结构是算法的基础,学号数据结构不一定能写出好的算法,但是要想学号算法必须学号数据结构

数据结构的分类

  • 数据结构包括线性结构和非线性结构

线性结构

  • 线性结构,其特点是数据元素之间存在一对一的线性关系
  • 线性结构有两种不同的存储结构,顺序存储结构和链式存储结构
  • 顺序存储的线性表称为顺序表,顺序表中的元素是连续的
  • 链式存储的线性表称为链表,链表中的元素不一定连续,元素节点中存储数据元素和相邻元素的地址信息
  • 线性结构常见的有:数组,队列,链表,栈等

非线性结构

  • 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构

稀疏数组

  • 当一个数组中大部分元素为0,或者为同一个值得数组时,可以使用稀疏数组来保存该数组
  • 处理方法
    1. 记录数组一共有几行几列,有多少个不同的值
    2. 把不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
  • 示例:

二维数组转换成稀疏数组

  1. 读取二维数组的有效数据个数
  2. 利用有效数据个数来生成稀疏数组
  3. 将二维数组中的有效数据存入稀疏数组

稀疏数组转换成二维数组

  1. 读取第一行的数据,创建二维数组
  2. 读取稀疏数组中的有效数据恢复到二维数组中

代码实现(二维数组->稀疏数组->恢复二维数组)

package com.chen.Deom1;

public class Demo1 {
    public static void main(String[] args) {
        //定义一个二维数组
        int[][] array = new int[11][11];
        array[1][2] = 1;
        array[2][3] = 2;

        System.out.println("原始的二维数组");
        for(int[] array1:array){
            for(int date:array1){
                System.out.printf("%d\t",date);
            }
            System.out.println();
        }

        //转换成稀疏数组
        int sum = 0;
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[i].length;j++){
                if (array[i][j]!=0){
                    sum++;
                }
            }
        }

        System.out.println("稀疏数组");
        int[][] array2 = new int[sum+1][3];
        array2[0][0]=11;
        array2[0][1]=11;
        array2[0][2]=sum;

        int count = 0;
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[i].length;j++){
                if (array[i][j]!=0){
                    count++;
                    array2[count][0]=i;
                    array2[count][1]=j;
                    array2[count][2]=array[i][j];
                }
            }
        }
        for(int[] array1:array2){
            for(int date:array1){
                System.out.printf("%d\t",date);
            }
            System.out.println();
        }

        //恢复二维数组
        System.out.println("恢复后的二维数组");
        int[][] array3 = new int[array2[0][0]][array2[0][1]];
        for (int i=1;i<array2.length;i++){
            for(int j=0;j<array2[i].length;j++){
                array3[array2[i][0]][array2[i][1]]=array2[i][2];
            }
        }
        for(int[] array1:array3){
            for(int date:array1){
                System.out.printf("%d\t",date);
            }
            System.out.println();
        }
    }
}

队列

  • 队列是一个有序列表,可以使用数组或是链表实现
  • 先进先出原则

利用数组来实现队列

  • 思路:

    • 队列是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下
    • 使用两个变量front和rear分别表示队列的前后的下标,front会随着数据的输出而改变,rear会随着数据的添加而改变
  • 模拟入队

    • 将尾指针往后移:rear+1,当front=rear表示对空
    • 如果尾指针rear小于队列的最大下标maxSize-1时候,将元素添加到指定下标rear的数组中,当rear=maxSize-1时候表示对满
  • 代码实现

    • package com.chen.Deom1;
      import java.util.Scanner;
      
      public class ArrayQueue {
      
          private static MyQueue myQueue = new MyQueue(3);
      
          public static void main(String[] args) {
      
              boolean flag = true;
              Scanner scanner = new Scanner(System.in);
      
              while(flag){
                  System.out.println("功能:");
                  System.out.println("(A)添加元素");
                  System.out.println("(B)删除元素");
                  System.out.println("(C)显示队头元素");
                  System.out.println("(D)显示队列所有元素");
                  System.out.println("(E)退出程序");
      
                  System.out.println("请输入功能项:");
                  char choose = scanner.next().charAt(0);
                  switch (choose){
                      case 'A':
                          System.out.println("element");
                          String in = scanner.next();
                          myQueue.put(in);
                          break;
                      case 'B':
                          myQueue.get();
                          break;
      
                      case 'C':
                          System.out.println(myQueue.getHead());
                          break;
      
                      case 'D':
                          myQueue.getAll();
                          break;
                      case 'E':
                          scanner.close();
                          flag = false;
                          break;
                  }
      
              }
      
              System.out.println("程序已经退出");
      
          }
      }
      
      //使用数组模拟队列
      class MyQueue{
      
          //用于存储队列数据
          Object[] list = null;
          //定义队列的大小
          int queueMaxSize = 0;
          //定义队列前端的指针
          int front = -1;
          //定义队列后端的指针
          int rear = -1;
      
          public MyQueue(int queueMaxSize){
              this.queueMaxSize=queueMaxSize;
              list = new Object[queueMaxSize];
          }
      
          //判断是否对空
          public boolean isEmpty(){
              return front==rear;
          }
      
          //判断是否对满
          public boolean isFull(){
              return rear==queueMaxSize-1;
          }
      
          //入队方法
          public void put(Object element){
              if (!isFull()){
                  list[++rear] = element;
              }else{
                  System.out.println("队满");
              }
          }
      
          //出队方法
          public void get(){
              if (!isEmpty()){
                  front++;
              }else{
                  System.out.println("队空");
              }
          }
      
          //显示队头的元素
          public Object getHead(){
              if (isEmpty()){
                  System.out.println("队空");
              }
              return list[front+1];
          }
      
          //显示所有队内元素
          public void getAll(){
              if (isEmpty()){
                  System.out.println("队空");
              }
              for (int i = front+1; i <= rear; i++) {
                  System.err.println(list[i]);
              }
          }
      }
      
      

使用数组模拟环形队列

  • 思路

    1. front变量指向队列的第一个元素,rear指向队列最后一个元素的后一个位置
    2. 队满条件:(rear+1)%maxSize=front
    3. 队空条件:rear=front
    4. 队列中有效的数据个数:(rear+maxSize-front)%maxSize
  • 环形队列理解图

  • 代码实现

    • package com.chen.Deom1;
      import java.util.Scanner;
      
      public class ArrayQueue {
      
          private static MyQueue myQueue = new MyQueue(4);
      
          public static void main(String[] args) {
      
              boolean flag = true;
              Scanner scanner = new Scanner(System.in);
      
              while(flag){
                  System.out.println("功能:");
                  System.out.println("(A)添加元素");
                  System.out.println("(B)删除元素");
                  System.out.println("(C)显示队头元素");
                  System.out.println("(D)显示队列所有元素");
                  System.out.println("(E)退出程序");
      
                  System.out.println("请输入功能项:");
                  char choose = scanner.next().charAt(0);
                  switch (choose){
                      case 'A':
                          System.out.println("element");
                          String in = scanner.next();
                          myQueue.put(in);
                          break;
                      case 'B':
                          myQueue.get();
                          break;
      
                      case 'C':
                          System.out.println(myQueue.getHead());
                          break;
      
                      case 'D':
                          myQueue.getAll();
                          break;
                      case 'E':
                          scanner.close();
                          flag = false;
                          break;
                  }
      
              }
      
              System.out.println("程序已经退出");
      
          }
      }
      
      //使用数组模拟队列
      class MyQueue{
      
          //用于存储队列数据
          Object[] list = null;
          //定义队列的大小
          int queueMaxSize = 0;
          //定义队列前端的指针
          int front = 0;
          //定义队列后端的指针
          int rear = 0;
      
          public MyQueue(int queueMaxSize){
              this.queueMaxSize=queueMaxSize;
              list = new Object[queueMaxSize];
          }
      
          //判断是否对空
          public boolean isEmpty(){
              return front==rear;
          }
      
          //判断是否对满
          public boolean isFull(){
              return (rear+1)%queueMaxSize==front;
          }
      
          //入队方法
          public void put(Object element){
              if (!isFull()){
                  list[rear] = element;
                  rear = (rear+1)%queueMaxSize;
              }else{
                  System.err.println("队满");
              }
          }
      
          //出队方法
          public void get(){
              if (!isEmpty()){
                  front=(front+1)%queueMaxSize;
              }else{
                  System.err.println("队空");
              }
          }
      
          //显示队头的元素
          public Object getHead(){
              if (isEmpty()){
                  System.err.println("队空");
              }
              return list[front];
          }
      
          //显示所有队内元素
          public void getAll(){
              if (isEmpty()){
                  System.out.println("队空");
              }
              for (int i = front; i < front+(rear+queueMaxSize-front)%queueMaxSize; i++) {
                  System.err.println(list[i%queueMaxSize]);
              }
          }
      }
      

单链表

  • 介绍
  1. 链表是以节点的方式来存储
  2. 每个节点包含data域,next域:指向下一个节点
  3. 链表的各个节点不一定是连续存储的
  4. 链表分为带头结点和不带头结点的,需要根据实际需求来定
  • 代码模拟单链表(带有头节点的,且链表的顺序就是添加时候的顺序)

    • 思路:
      1. 创建一个头结点,用于连接整个链表
      2. 添加节点的时候直接往链表的最后一个节点添加,节点的next为空则为最后一个节点
    • 代码实现
    package com.chen.Deom1;
    
    import javax.jws.soap.SOAPBinding;
    import java.sql.Time;
    import java.util.Stack;
    import java.util.concurrent.TimeUnit;
    
    public class LinkedListDemo {
    
        public static void main(String[] args) {
    
            LinkedList list = new LinkedList();
            LinkedList list1Order = new LinkedList();
    
            UserNode userNode1 = new UserNode(1,"clt1","123");
            UserNode userNode2 = new UserNode(2,"clt2","123");
            UserNode userNode3 = new UserNode(3,"clt3","123");
            UserNode userNode4 = new UserNode(4,"clt4","123");
            //测试没有顺序的添加节点
    //        list.addNode(userNode2);
    //        list.addNode(userNode1);
    //        list.addNode(userNode3);
    //        list.addNode(userNode4);
    //        list.allNode();
    
            //测试有顺序的添加节点(根据用户id排序且不会重复添加相同id的用户)
            System.out.println("排序后");
            list1Order.addNode(userNode2,true);
            list1Order.addNode(userNode1,true);
            list1Order.addNode(userNode1,true);
            list1Order.addNode(userNode3,true);
            list1Order.addNode(userNode4,true);
            list1Order.allNode();
            System.out.println("修改后");
            UserNode userNode5 = new UserNode(2,"clt22","123");
            list1Order.updateNode(userNode5);
            list1Order.allNode();
            System.out.println("反转后");
            list1Order.reserve();
    
            list1Order.allNode();
    
            System.out.println("===========");
    
            list1Order.printReverse();
    
    
    
    
    
        }
    
    
    }
    
    
    
    
    //用户节点
    class UserNode {
        public int id;
        public String username;
        public String password;
        public UserNode nextNode;
    
        public UserNode(int id,String username,String password){
            this.id=id;
            this.username=username;
            this.password=password;
        }
    
        @Override
        public String toString() {
            return "UserNode{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    
    //链表
    class LinkedList {
        //定义一个头节点
        private UserNode headNode = new UserNode(0,null,null);
        //定义一个指针用于遍历链表
        private UserNode temp = headNode;
        //按照添加顺序添加节点
        public void addNode(UserNode element){
            //遍历链表找到最后一个节点
            while (true){
                if (temp.nextNode==null) break;
                temp = temp.nextNode;
            }
            temp.nextNode = element;
        }
        //新添加一个方法可以按照用户编号来排序(无关添加顺序)
        public void addNode(UserNode element,boolean isOrder){
            if (!isOrder){
                addNode(element);
            }
    
            //设置标志,链表中是否有重复编号的值
            boolean flag = false;
    
            //遍历寻找添加元素的位置
            temp = headNode;
            while(true){
                if (temp.nextNode==null) break;
                if (temp.nextNode.id>element.id){
                    break;
                }
                if (temp.nextNode.id==element.id) {
                    flag = true;
                    break;
                }
                temp = temp.nextNode;
            }
            if (flag) return;
            element.nextNode = temp.nextNode;
            temp.nextNode = element;
        }
    
        //修改节点
        public void updateNode(UserNode updateNode){
    
            UserNode temp = headNode;
    
            if (temp.nextNode==null)return;
    
            boolean hasUpdateNode = false;
    
            while(true){
    
                if (temp.nextNode==null) break;
                if (temp.nextNode.id==updateNode.id){
                    hasUpdateNode = true;
                    break;
                }
    
                temp = temp.nextNode;
    
            }
    
            if (hasUpdateNode){
                temp.nextNode.username=updateNode.username;
                temp.nextNode.password=updateNode.password;
            }
    
        }
        //删除节点
        public void deleteNode(int id){
            UserNode temp = headNode;
            if (temp.nextNode==null) return;
            boolean hasDeleteNode = false;
            while(true){
                if (temp.nextNode==null) break;
                if (temp.nextNode.id==id) {
                    hasDeleteNode=true;
                    break;
                }
                temp = temp.nextNode;
            }
            if (!hasDeleteNode) return;
            UserNode deleteNode = temp.nextNode;
            temp.nextNode=deleteNode.nextNode;
            deleteNode.nextNode=null;
        }
    
        //遍历所有的节点
        public void allNode(){
            //为了防止由于添加造成的指针的位移导致遍历不出所有节点
            temp = headNode;
            //判断是否队空
            if (headNode.nextNode==null) return;
            //遍历
            while (true){
                if (temp.nextNode==null){
                    break;
                }
                System.out.println(temp.nextNode);
                temp = temp.nextNode;
            }
    
        }
    
        //获取链表的长度
        public int getLength(){
            int length = 0;
            UserNode temp = headNode;
            if (temp.nextNode==null) return 0;
            while (true){
                if (temp.nextNode==null) break;
                length++;
                temp = temp.nextNode;
            }
            return length;
        }
    
        //获取链表的倒数第几个节点
        public UserNode getNode(int lastIndex){
    
            if (lastIndex<1 || lastIndex>getLength()) return null;
    
            UserNode temp = headNode.nextNode;
    
            for (int i=1;i<=getLength()-lastIndex;i++){
                temp = temp.nextNode;
            }
    
            return temp;
        }
    
        //链表的反转
        /**
         * 思路:
         * 1.将最后一个节点的指向前一个节点,并将前一个节点的指向置为空
         * 2.循环,最后将第一个节点的指向置为空,将头结点指向原来的最后一个节点
         * */
        public void reserve(){
            if (getLength()==1) allNode();
    
            UserNode firstNode = getNode(getLength());
            UserNode endNode = getNode(1);
    
            for (int i=1;i<=getLength()-1;i=1){
                UserNode node = getNode(i);
                UserNode lastSecondNode = getNode(i+1);
                node.nextNode=lastSecondNode;
                lastSecondNode.nextNode=null;
            }
            firstNode.nextNode=null;
            headNode.nextNode=endNode;
        }
    
        //在不改变数据结构的情况下反序打印
        public void printReverse(){
            UserNode temp = headNode.nextNode;
            Stack<UserNode> userNodeStack = new Stack<>();
            while (true){
                if (temp==null) break;
                userNodeStack.push(temp);
                temp=temp.nextNode;
            }
    
            while (userNodeStack.size()>0) System.out.println(userNodeStack.pop());
    
    
    
        }
    }
    
    

单链表的常见面试题

  1. 求单链表的有效节点个数

  2. 查找单链表的倒数第k个节点

  3. 单链表的反转

    •     /**
           * 思路:
           * 1.将最后一个节点的指向前一个节点,并将前一个节点的指向置为空
           * 2.循环,最后将第一个节点的指向置为空,将头结点指向原来的最后一个节点
           * */
          public void reserve(){
              if (getLength()==1) allNode();
      
              UserNode firstNode = getNode(getLength());
              UserNode endNode = getNode(1);
      
              for (int i=1;i<=getLength()-1;i=1){
                  UserNode node = getNode(i);
                  UserNode lastSecondNode = getNode(i+1);
                  node.nextNode=lastSecondNode;
                  lastSecondNode.nextNode=null;
              }
              firstNode.nextNode=null;
              headNode.nextNode=endNode;
          }
      
    • 思路二

      1. 先再定义一个头结点
      2. 从头到尾遍历原来的链表,没遍历一个节点,就将其取出,并放在新的头结点的最前端
      3. 再将原来的头结点换成新定义的头结点即可
  4. 从尾到头打印单链表(方式一:反向遍历,方式二:stack栈)

    • 思路一:先将单链表进行反转操作,然后再遍历即可,但是这样会破坏原来的单链表结构
    • 思路二:可以使用栈这个数据结构来入栈和出栈
  5. 合并两个有序的单链表,合并之后的链表依然有序

双向链表

双链表对比单链表

  • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  • 单向链表不能自我删除,需要辅助节点,而双向链表可以自我删除

双向链表的代码实现CRUD

package com.chen.Deom1;

public class DoubleLindList {


    public static void main(String[] args) {
        DoubleLinkList doubleLinkList = new DoubleLinkList();
        UserNode1 userNode1 = new UserNode1(1,"clt1","123");
        UserNode1 userNode2 = new UserNode1(2,"clt2","1231");
        doubleLinkList.addNode(userNode1);
        doubleLinkList.addNode(userNode2);
        doubleLinkList.nodeList();

        System.out.println("修改之后的");
        UserNode1 userNode3 = new UserNode1(1,"clt11","123");
        doubleLinkList.updateNode(userNode3);
        doubleLinkList.nodeList();

        System.out.println("删除之后的");
        doubleLinkList.deleteNode(userNode2);
        doubleLinkList.nodeList();

    }
    
}
class DoubleLinkList{

    //头结点
    private UserNode1 headNode = new UserNode1(0,null,null);


    //Add
    public void addNode(UserNode1 userNode1){

        UserNode1 temp = headNode;

        while(true){
            if (temp.nextNode==null) break;
            temp = temp.nextNode;
        }

        temp.nextNode = userNode1;
        userNode1.preNode = temp;

    }

    //foreach
    public void nodeList(){
        if (headNode.nextNode==null) return;

        UserNode1 temp = headNode.nextNode;

        while(true){
            if (temp==null) break;
            System.out.println(temp);
            temp = temp.nextNode;
        }
    }

    //update
    public void updateNode(UserNode1 updateNode){
        if (headNode.nextNode==null) return;

        UserNode1 temp = headNode.nextNode;
        boolean isFind = false;
        while(true){
            if (temp==null) break;

            if (temp.id==updateNode.id) {
                isFind = true;
                break;
            }

            temp = temp.nextNode;

        }

        if (isFind){
            temp.username = updateNode.username;
            temp.password = updateNode.password;
        }

    }

    //delete
    public void deleteNode(UserNode1 deleteNode){

        if (headNode.nextNode==null) return;

        UserNode1 temp = headNode.nextNode;
        boolean isFind = false;
        while (true){

            if (temp==null) break;

            if (temp.id==deleteNode.id) {
                isFind = true;
                break;
            }

            temp = temp.nextNode;

        }
        if (isFind){
            temp.preNode.nextNode=temp.nextNode;
            if (temp.nextNode!=null)
            temp.nextNode.preNode=temp.preNode;
        }

    }
}

class UserNode1 {
    public int id;
    public String username;
    public String password;
    public UserNode1 nextNode;
    public UserNode1 preNode;

    public UserNode1(int id,String username,String password){
        this.id=id;
        this.username=username;
        this.password=password;
    }

    @Override
    public String toString() {
        return "UserNode{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

环形链表

  • Josephu(约瑟夫问题)
    • Josephu问题为:设编号为1,2,...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,他的下一个又从1开始报数,数到m的那个人又出列,以此类推,直到所有人出列为止,由此产生一个出队编号的序列
    • 使用循环链表来处理,先构成一个有n个节点的单循环链表,然后由k节点从1开始计数,计到m时候,对应节点从链表中删除,然后再从被删除节点的下一个节点由从1开始计数,直到最后一个节点从链表中删除算法结束
    • 解决方法:
      • 单向环形链表
      • 数组取模模拟的环形

代码实现单向环形链表并解决约舍夫问题

  • 思路

    1. 先创建一个节点,让first指向该节点,并形成环形
    2. 每添加一个新的节点,就把该节点,加入到已有的环形链表中即可
  • 代码实现

    package com.chen.Deom1;
    public class CircleLinkedListDemo {
        public static void main(String[] args) {
            CircleLinkedList circleLinkedList = new CircleLinkedList();
            circleLinkedList.addBoy(25);
            circleLinkedList.count(23,6);
        }
    }
    
    class CircleLinkedList {
        //设置第一个节点
        private Boy firstBoy = null;
        //设置单链表的长度
        private int length = 0;
        //创建节点
        public void addBoy(int number){
            Boy temp = null;
            length = number;
            for(int i=1;i<=length;i++){
                if (firstBoy==null){
                    firstBoy = new Boy(i,null);
                    firstBoy.nextBoy = firstBoy;
                    temp = firstBoy;
                }else{
                    temp.nextBoy = new Boy(i,firstBoy);
                    temp = temp.nextBoy;
                }
            }
        }
        //遍历节点
        public void boyList(){
            if (firstBoy==null) return;
            Boy temp = firstBoy;
            while(true){
                System.out.println("小孩的编号为"+temp.no);
                if (temp.nextBoy==firstBoy) break;
                temp = temp.nextBoy;
            }
        }
        //遍历出圈顺序
        public void count(int startNo,int count){
            if (startNo<0||startNo>length||firstBoy==null) return;
            //定义开始游戏的小孩
            Boy startBoy = null;
            //定义被选中小孩的后一个小孩
            Boy afterEndBoy = null;
            //寻找到开始游戏的小孩
            Boy temp = firstBoy;
            boolean isFind = false;
            while(true){
                if (temp.nextBoy==firstBoy) break;
                if (temp.no==startNo) {
                 isFind = true;
                 startBoy = temp;
                 break;
                }
                temp = temp.nextBoy;
            }
            //找到开始游戏
            if (!isFind) return;
            while (true){
                if (length==0) break;
                afterEndBoy = startBoy;
                //循环指向将要出局的小孩的后一位小孩
                for(int i=1;i<=count-2;i++){
                    afterEndBoy = afterEndBoy.nextBoy;
                }
                //将开始新一局游戏的开始小孩定位
                startBoy = afterEndBoy.nextBoy.nextBoy;
                System.out.println("出局的小孩编号为"+afterEndBoy.nextBoy.no);
                afterEndBoy.nextBoy = startBoy;
                length--;
            }
        }
    }
    
    class Boy {
        public int no;
        public Boy nextBoy;
        public Boy(int no, Boy nextBoy) {
            this.no = no;
            this.nextBoy = nextBoy;
        }
        @Override
        public String toString() {
            return "Boy{" +
                    "no=" + no +
                    ", nextBoy=" + nextBoy +
                    '}';
        }
    }
    

栈(stack)

  • 栈是一个先进先出的有序列表
  • 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊的线性表。运行插入和删除的一端,为变化的一端,称为栈顶,另外一端不变化的一端是栈底。

栈解决的问题

  • 对于一个计算表达式,计算机是如何计算的,对于计算机来说它接收的是一个字符串,此时可以使用栈来解决这个问题
  • 子程序的调用
  • 递归调用
  • 表达式转换(中缀表达式转换成后缀表达式)与求值
  • 二叉树的遍历
  • 图形的深度优先(depth-first)搜索法

代码实现栈

  • 思路:

    1. 使用数组来模拟栈stack
    2. 定义一个top指针来表示栈顶,初始化为-1
    3. 入栈操作:top++;stack[top]=value
    4. 出栈操作:int popvalue = stack[top];top--;return popvlaue
  • 代码实现

package com.chen.Deom1;

import java.util.Scanner;

public class MyStackDemo {

    public static void main(String[] args) {
        MyStack myStack = new MyStack(3);

        Scanner scanner = new Scanner(System.in);
        boolean flag = true;

        while(flag){
            System.out.println("POP");
            System.out.println("PUSH");
            System.out.println("LIST");
            System.out.println("EXIT");
            System.out.println("请输入需要执行的命令(以;作为结束符)->");
            String commend = scanner.next();
            switch (commend){
                case "PUSH;":
                    System.out.println("Element->");
                    myStack.push(scanner.nextInt());
                    break;
                case "POP;":
                    System.out.println(myStack.pop());
                    break;
                case "LIST;":
                    myStack.list();
                    break;
                case "EXIT;":
                    flag = false;
                    break;
            }
        }
        System.out.println("程序结束");

    }


}


class MyStack {

    private int maxSize;
    private int[] stack;
    private int top = -1;

    public MyStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    //判断栈满
    public boolean isFull(){
        return top==maxSize-1;
    }
    //判断栈空
    public boolean isEmpty(){
        return top==-1;
    }
    //入栈
    public void push(int element){
        if (isFull()) {
            System.err.println("栈满");
            return;
        }
        top++;
        stack[top] = element;
    }
    //出栈
    public int pop(){
        if (isEmpty()) throw new RuntimeException("栈空");
        int temp = stack[top];
        top--;
        return temp;
    }
    //遍历
    public void list(){
        int temp = top;
        while (true){
            if (temp==-1) break;
            System.err.println(stack[temp]);
            temp--;
        }
    }
}

使用栈解决表达式运算(中缀表达式)

  • 思路:
    • 通过一个index值(索引),来遍历我们的表达式
    • 如果发现是一个数字,就直接入数栈
    • 如果发现的是一个符号,就直接如符号栈
    • 如果符号栈中有操作符
      • 如果当前的操作符的优先级小于等于栈中的操作符,就需要从数栈中pop两个数,从符号栈中pop出一个符号,进行运算,将结果重新入数栈,然后再将当前操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
    • 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出响应的数字和符号进行运算
    • 当运算结束后数栈中只有一个数字,就是表达式的结果
  • 代码实现
package com.chen.Deom1;

import java.util.ArrayList;

import java.util.List;

public class MyStackDemo {
    public static void main(String[] args) {
         String expression = "100234/10*2+101+1+22";
         Expression expression1 = new Expression(
                 expression,
                 new MyStack(expression.length()),
                 new MyStack(expression.length()),
                 new MyStack(expression.length()));
        System.out.println(expression1.getResult());
    }
}


class MyStack {

    private int maxSize;
    private int[] stack;
    private int top = -1;

    public MyStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    //判断栈满
    public boolean isFull(){
        return top==maxSize-1;
    }
    //判断栈空
    public boolean isEmpty(){
        return top==-1;
    }
    //入栈
    public void push(int element){
        if (isFull()) {
            System.err.println("栈满");
            return;
        }
        top++;
        stack[top] = element;
    }
    //出栈
    public int pop(){
        if (isEmpty()) throw new RuntimeException("栈空");
        int temp = stack[top];
        top--;
        return temp;
    }
    //遍历
    public void list(){
        int temp = top;
        while (true){
            if (temp==-1) break;
            System.err.println(stack[temp]);
            temp--;
        }
    }
    //获取栈顶元素
    public int getTopElement(){
        return stack[top];
    }
}

//利用栈实现表达式的计算(支持整数的运算)
@SuppressWarnings("all")
class Expression {
    //表达式
    private String expression = null;
    //传入数栈和符号栈
    private MyStack numberStack;
    private MyStack charStack;
    public MyStack expressionStack;
    //
    private final String P = "1000000000";


    //构造器传入表达式
    public Expression(String expression,MyStack numberStack,MyStack charStack,MyStack expressionStack){
        this.expression = expression;
        this.numberStack = numberStack;
        this.charStack = charStack;
        this.expressionStack = expressionStack;
    }

    //提取表达式中的数字和字符压入表达式栈中(倒序)
    public void pushExpressionStack (){
        //while循环遍历将表达式解析压入表达式栈
        int expressionLength = expression.length();
        int flagLength = 0;
        List<Integer> list = new ArrayList<>();
        while (true){

            if (flagLength>=expressionLength) break;

            //获取多位数字
            char ziFu = expression.substring(flagLength,flagLength+1).charAt(0);
            if((ziFu!='+'&&ziFu!='-'&&ziFu!='*'&&ziFu!='/')){
                list.add(ziFu-48);
            }else {
                //这里有待改进支持更多位数的数字计算
//                if (list.size()==2){
//                    int temp = 0;
//                    temp = list.get(0)*10 + list.get(1)*1;
//                    expressionStack.push(temp);
//                }else if (list.size()==1){
//                    expressionStack.push(list.get(0)*1);
//                }
                //改进的
                int temp = 0;
                for(int i=0;i<list.size();i++){
                    temp += list.get(i) * Integer.valueOf(P.substring(0,list.size()-i));
                }
                expressionStack.push(temp);

                list.clear();
                expressionStack.push(ziFu);
            }
            flagLength++;
            if (flagLength==expressionLength){
//                if (list.size()==2){
//                    int temp = 0;
//                    temp = list.get(0)*10 + list.get(1)*1;
//                    expressionStack.push(temp);
//                }else if (list.size()==1){
//                    expressionStack.push(list.get(0)*1);
//                }
                int temp = 0;
                for(int i=0;i<list.size();i++){
                    temp += list.get(i) * Integer.valueOf(P.substring(0,list.size()-i));
                }
                expressionStack.push(temp);
                list.clear();
            }
        }
    }

    //将表单式栈中的数据恢复正常表达式顺序
    public void reserveExpressionStack(){
        List<Integer> list = new ArrayList<>();
        while(!expressionStack.isEmpty()){
            list.add(expressionStack.pop());
        }
        for (Integer integer : list) {
            expressionStack.push(integer);
        }
    }

    //获取运算符号的优先级
    public int getPriority(char computed){

        if (computed=='+'||computed=='-') return 0;
        if (computed=='*'||computed=='/') return 1;
        return -1;

    }

    //运算
    public int getResult(){
        pushExpressionStack();
        reserveExpressionStack();
        //将表达式中的数字和运算符压入指定的栈中
        while (!expressionStack.isEmpty()){
            int temp = expressionStack.pop();
            //判断是否是符号
            if (temp!='+'&&
                temp!='-'&&
                temp!='*'&&
                temp!='/'){

                numberStack.push(temp);

            }else{
                if (charStack.isEmpty()) {
                    charStack.push(temp);
                }else {
                    if (getPriority((char) temp)<=getPriority((char) charStack.getTopElement())){
                        int num1 = numberStack.pop();
                        int num2 = numberStack.pop();
                        int result = 0;
                        switch (charStack.pop()){
                            case '*':
                                result = num2*num1;
                                break;
                            case '/':
                                result = num2/num1;
                                break;
                            case '+':
                                result = num2+num1;
                                break;
                            case '-':
                                result = num2-num1;
                                break;
                        }
                        numberStack.push(result);
                        charStack.push(temp);
                    }else{
                        charStack.push(temp);
                    }
                }

            }
        }

        //运算
        while(!charStack.isEmpty()){
            int num1 = numberStack.pop();
            int num2 = numberStack.pop();
            int result = 0;
            switch (charStack.pop()){
                case '*':
                    result = num2*num1;
                    break;
                case '/':
                    result = num2/num1;
                    break;
                case '+':
                    result = num2+num1;
                    break;
                case '-':
                    result = num2-num1;
                    break;
            }
            numberStack.push(result);
        }
        return numberStack.pop();
    }

}

使用栈来解决逆波兰表达式(后缀表达式)

  • 逆波兰表达式的运算规则

    • 从左往右依次扫描逆波兰表达式,将其压入栈中,遇到数字直接入栈,遇到运算符号直接弹出两个数计算,然后将结果再压入栈中,继续。
  • 思路和上面的中缀表达式一样的,相对来说简单,实现的代码在下面总结了

中缀表达式转后缀表达式

  • 思路

    1. 初始化两个栈,运算符栈s1和存储中间结果的栈s2
    2. 从左到右扫描中缀表达式
    3. 遇到操作数时候,将其压到s2
    4. 遇到运算符时候,比较与s1栈顶运算符的优先级
      1. 如果s1为空,或者栈顶运算符为左括号,则直接入栈
      2. 如果优先级高于栈顶符号,直接入栈
      3. 否则s1弹出栈顶元素压入s2中,继续回到第一个步骤比较
    5. 遇到括号时候
      1. 如果是左括号,则直接压入s1
      2. 如果是右括号,则依次弹出s1中的运算符,并压入s2,直到遇到左括号为止,并将左右括号去掉
    6. 重复2-5,直到表达式的最右边
    7. 将s1中剩余的运算符依次弹出并压入s2
    8. 然后弹出s2中的元素并输出,结果的逆序就是后缀表达式
  • 代码实现

package com.chen.Deom1;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class SuffixDemo {

    public static void main(String[] args) {
        Suffix suffix = new Suffix("1+((2+3)*4)-5");
        int result = suffix.getResult();
        System.out.println(result);
    }

}

class Suffix {

    private String expression;

    public Suffix(String expression) {
        this.expression=expression;
    }

    //将中缀表达式转换为list集合,避免一个个扫描
    public List<String> suffixList(){
        int tempLength = 0;
        char tempChar ;
        List<String> list = new ArrayList<>();
        String buffer = "";
        do{
            if ((tempChar=expression.charAt(tempLength))<48 || (tempChar=expression.charAt(tempLength))>57){
                if (buffer != "") list.add(buffer);
                list.add(tempChar+"");
                buffer = "";
            }else{
                buffer += tempChar;
            }
            tempLength++;
            if (tempLength==expression.length()) list.add(buffer);
        }while (tempLength<expression.length());
        return list;
    }

    //将中缀表达式转换为后缀表达式
    public List<String> getSuffixExpression(){
        List<String> suffixList = new ArrayList<>();
        Stack<String> tempStack = new Stack<>();
        List<String> stringList = suffixList();
        //对表达式进行遍历
        for (String s : stringList) {
            //对于数字直接入栈
            if (s.matches("\\d+")){
                suffixList.add(s);
            }else{
                //对于符号

                //对于栈空直接入栈
                if(tempStack.isEmpty()){
                    tempStack.push(s);
                }else{
                    //对于栈有符号

                    //如果是右括号,直接入栈
                    if(s.equals("(")){
                        tempStack.push(s);
                    }else{
                        //如果是左括号
                        if(s.equals(")")){
                            while (true){
                                if (tempStack.peek().equals("(")) break;
                                suffixList.add(tempStack.pop());
                            }
                            tempStack.pop();
                        }else{
                            //运算符登记高于栈顶元素直接入栈
                            while (true){
                                if ((tempStack.isEmpty() ||
                                     getGrade(s)>getGrade(tempStack.peek())) ||
                                     (tempStack.peek().equals("("))){
                                    tempStack.push(s);
                                    break;
                                }else{
                                    //运算符低于栈顶符号
                                    suffixList.add(tempStack.pop());
                                }
                            }
                        }
                    }
                }
            }

        }
        //残留的符号
        while (true){
            if (tempStack.isEmpty()) break;
            suffixList.add(tempStack.pop());
        }
        return suffixList;
    }

    //获取等级
    public int getGrade(String s){

        if (s.equals("+")){
            return 1;
        }else if (s.equals("-")){
            return 1;
        }else if (s.equals("*")){
            return 2;
        }else if (s.equals("/")){
            return 2;
        }
        return 0;
    }


    //计算
    public int getResult(){
        List<String> suffixExpression = getSuffixExpression();
        Stack<String> strings = new Stack<>();
        int result = 0;
        for (String s : suffixExpression) {
            if (s.matches("\\d+")){
                strings.push(s);
            }else{
                int num1 = Integer.parseInt(strings.pop());
                int num2 = Integer.parseInt(strings.pop());
                switch (s){
                    case "*":
                        result = num2*num1;
                        break;
                    case "/":
                        result = num2/num1;
                        break;
                    case "-":
                        result = num2-num1;
                        break;
                    case "+":
                        result = num2+num1;
                        break;
                }
                strings.push(result+"");
            }
        }
        return result;
    }
}

递归算法

  • 程序自己调用自己,每一次传入的值都不一样
  • 对应的虚拟机栈如图

递归需要遵守的重要规则

  1. 执行一个方法时候,就创建一个新的受保护的独立空间(栈帧)
  2. 方法的局部变量是独立的(JVM的知识局部变量表)
  3. 如果方法中使用的是引用类型,就会共享该引用类型的数据
  4. 递归必须向退出递归的条件逼近,否则就是无限递归
  5. 当一个方法执行完毕,或者遇到return时候就会返回,遵守谁调用的,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

递归能解决的问题

  • 各种数学问题:8皇后问题,汉罗塔,阶乘,迷宫,球和篮子(google编程大赛)
  • 各种算法中也会使用到递归,如快排,归并排序,二分查找,分治算法等
  • 需要用栈解决的问题,使用递归比较简介

使用递归解决迷宫问题

package com.chen.Deom1;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Demo111 {

    public static void main(String[] args) {

        Maze maze = new Maze();
        maze.getMaze();
        maze.getWay(1,1);
        maze.look();
    }


}

class Maze {

    /**
     * 作用:找出一条走出迷宫的路线
     * 规则:能走通的用2表示
     *      围墙用1表示
     *      走不通的用3表示
     *      待走的用0表示
     *      用二维数组表示迷宫结构
     * */

    //定义二维数组迷宫
    int [][] maze = new int[10][10];
    //定义迷宫结构
    public void getMaze(){
        for(int i=0;i<maze[0].length;i++){
            maze[0][i] = 1;
        }
        for(int i=0;i<maze[9].length;i++){
            maze[9][i] = 1;
        }
        for(int i=0;i<maze.length;i++){
            maze[i][0] = 1;
        }
        for(int i=0;i<maze.length;i++){
            maze[i][9] = 1;
        }
        for(int i=2;i<maze.length-1;i++){
            maze[i][2]=1;
        }
        for(int i=4;i<maze[2].length;i++){
            maze[2][i]=1;
        }
        for(int i=4;i<maze.length-1;i++){
            maze[i][4]=1;
        }
    }
    //查看迷宫
    public void look(){
        for (int[] ints : maze) {
            for (int i : ints){
                System.out.print(i);
                System.out.print("  ");
            }
            System.out.println();
        }
    }
    //递归查找有效路线
    public boolean getWay(int startX,int startY){
        //定义的maze[8][8]的地方为2后说明找到了有效路线
        if(maze[8][8]==2){
            return true;
        }
        //各个方向寻找有效路线
        if(maze[startX][startY]==0){
            maze[startX][startY]=2;
            //寻找规则为上下左右
            if (getWay(startX-1,startY)){
                return true;
            }else if(getWay(startX,startY+1)){
                return true;
            }else if(getWay(startX+1,startY)){
                return true;
            }else if(getWay(startX-1,startY)){
                return true;
            }else{
                maze[startX][startY]=3;
                return false;
            }
        }
        return false;
    }
}

使用递归解决八皇后问题

  • 思路
    1. 第一个皇后先放第一行第一列
    2. 第二个皇后放在第二行第一列,然后判断是否冲突,如果冲突继续放在第二列。。。直到找到不冲突的位子
    3. 第三个皇后也是像第二个皇后一样,直到找到第八个皇后
    4. 当得到一个正确的解时候,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到
    5. 然后第一个皇后继续放在第二列,重复前四个步骤
    6. 使用一个空间为8的数组来记录八个皇后的位置信息
posted @ 2023-08-03 22:21  在赶路的我  阅读(82)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3