数据结构与应用

中缀表达式(我们日常使用的式子)的简易计算器

public class Calculator {
    public static void main(String[] args) {
        String str = "360*360*9+265/360-360";
        Calculator calculator = new Calculator();
        StringBuffer keepNum = new StringBuffer();
        keepNum.append("结果:");
        System.out.println(keepNum);//lang包下的自动调用toString,下面的keepNum用StringBuffer,要显示调用
        System.out.println(calculator.Calculator(str));
    }

    //中缀表达式的简易计算器
    public int Calculator(String expression) {
        LinkStack numStack = new LinkStack();
        LinkStack operStack = new LinkStack();
        int index = 0;//扫描指针
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int result = 0;
        char ch;//每次扫描表达式得到的char保存到这里
        String keepNum = "";//用于拼接多位数
        //扫描式子
        while (true) {
            ch = expression.charAt(index++);
            if (!isOper(ch)) {//判断是不是数
                keepNum += ch;//单位数的可能

                //注意越界的问题,字符串下标从0开始
                for (; index < expression.length() && !isOper(expression.charAt(index)); ++index) {
                    ch = expression.charAt(index);
                    keepNum += ch;
                }
                numStack.push(Integer.parseInt(keepNum));//记得转会十进制数在放进栈
                //numStack.push(ch-48);//要根据ASCII表将字符转回数字,这个只适合单位数
                keepNum = "";//清空,供下一轮使用
            } else {
                if (!operStack.isEmpty()) {
                    //符号栈不为空,比较符号的优先级
                    //如果当前运算符的优先级小于或等于栈中的运算符,要从数栈pop出两数,符号栈中pop出符号,先进行运算
                    if (priority(ch) <= priority((Character) operStack.peek())) {
                        num1 = (int) numStack.pop();
                        num2 = (int) numStack.pop();
                        oper = (char) operStack.pop();
                        result = calculate(num1, num2, oper);
                        numStack.push(result);//把结果压入栈
                        operStack.push(ch);//把当前运算符压入栈
                    } else {
                        operStack.push(ch);
                    }
                } else {
                    //符号栈为空直接入栈
                    operStack.push(ch);
                }
            }
            //判断是否扫描到最后
            if (index >= expression.length()) break;
        }
        //扫描完,开始计算
        while (true) {
            if (operStack.isEmpty()) break;
            num1 = (int) numStack.pop();
            num2 = (int) numStack.pop();
            oper = (char) operStack.pop();
            result = calculate(num1, num2, oper);
            numStack.push(result);//把结果压入栈
        }
        return (int) numStack.pop();
    }

    //判断是不是一个运算符
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    //用于计算的方法
    public int calculate(int num1, int num2, int oper) {
        int result = 0;
        switch (oper) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num2 - num1;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                result = num2 / num1;
                break;
        }
        return result;
    }

    //自定义一个运算符的优先级,1表示乘除,0表示加减,其他返回-1
    public int priority(char oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1;
        }

    }
}

逆波兰计算器
中缀表达式转后缀表达式
在这里插入图片描述

public class CalculatorPro {

    //为了后面的方便,先把中缀表达式存到List
    public static List<String> toList(String s) {
        List<String> list = new ArrayList<String>();
        int index = 0;//扫描字符串的指针
        String str;//用于拼接多位数
        char c;
        do {
            //如果不是数
            if ((c = s.charAt(index)) < 48 || (c = s.charAt(index)) > 57) {
                list.add("" + c);
                index++;
            } else {//是数的话考虑多位数拼接
                str = "";
                while (index < s.length() && (c = s.charAt(index)) > 48 && (c = s.charAt(index)) < 57) {
                    str += c;
                    index++;
                }
                list.add(str);
            }
        } while (index < s.length());
        return list;
    }

    //把中缀表达式的List转为后缀表达式的List
    public static List<String> parseSuffix(List<String> list) {
        Stack<String> stack = new Stack<>();//符号栈
        List<String> s = new ArrayList<>();//中间结果的栈改为List,更灵活
        for (String s1 : list) {
            //如果是一个数直接放进s
            if (s1.matches("\\d+")) {
                s.add(s1);
            } else if (s1.equals("(")) {
                stack.push(s1);//入符号栈
            } else if (s1.equals(")")) {
                while (!stack.peek().equals("(")) {
                    s.add(stack.pop());
                }
                stack.pop();//去除"("
            } else {
                //比较当前符号的优先级,如果符号栈的优先级高,将其取出存到s,再把当前符号入栈
                while (stack.size() != 0 && Operation.priority(stack.peek()) >= Operation.priority(s1)) {
                    s.add(stack.pop());
                }
                stack.push(s1);
            }
        }
        //遍历完之后,把符号栈剩下的运算符,也存到s
        while (stack.size() != 0) {
            s.add(stack.pop());
        }
        return s;//最后得到逆波兰表达式
    }

    //把后缀表达式进行计算
    public static int Calculate(String string) {

        List<String> list = toList(string);
        List<String> stringList = parseSuffix(list);


        Stack<String> stack = new Stack<>();
        for (String s : stringList) {
            if (s.matches("\\d+")) {
                stack.push(s);
            } else {
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int result = 0;
                switch (s) {
                    case "+":
                        result = num1 + num2;
                        break;
                    case "-":
                        result = num2 - num1;
                        break;
                    case "*":
                        result = num1 * num2;
                        break;
                    case "/":
                        result = num2 / num1;
                        break;
                }
                stack.push("" + result);
            }
        }
        return Integer.parseInt(stack.pop());
    }
    
class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    //
    public static int priority(String oper) {
        int result = 0;
        switch (oper) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("不存在改运算符");
                break;
        }
        return result;
    }
}

递归与回溯

迷宫

public class Maze {
    public static void main(String[] args) {
        //创建一个二维数组,模拟迷宫地图
        int[][] map = new int[8][7];
        //1表示墙,上下置为一
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //左右置为一 、
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        //设置内部的挡板
        map[3][1] = 1;
        map[3][2] = 1;
//        map[1][2] = 1;
//        map[2][2] = 1;

        System.out.println("地图的情况");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
        //使用递归回溯给小球找路
        setWay(map, 1, 1);
//        setWay2(map, 1, 1);
        //输出新的地图, 小球走过,并标识过的递归
        System.out.println("小球走过,并标识过的 地图的情况");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }

    /*找到通路,返回true,否则返回false
       约定: 当 map[i][j] 为 0 表示该点没有走过 当为 1 表示墙 ; 2 表示通路可以走 ; 3 表示该点已经
        走过,但是走不通
        按照策略 下->右->上->左 走
     */

    public static boolean setWay(int[][] map, int i, int j) {
        if (map[6][5] == 2) {
            return true;//走到出口
        } else {
            if (map[i][j] == 0) {//还没走过
                map[i][j] = 2;
                if (setWay(map, i + 1, j)) {
                    return true;
                } else if (setWay(map, i, j + 1)) {
                    return true;
                } else if (setWay(map, i - 1, j)) {
                    return true;
                } else if (setWay(map, i, j - 1)) {
                    return true;
                } else {
                    map[i][j] = 3;//已经走过,或者是死路
                    return false;
                }
            } else {
                //走过,是墙,死路
                return false;
            }
        }
    }
}

八皇后

public class Queue8 {
    //定义一个 max 表示共有多少个皇后
    int max = 8;
    //定义数组 array, 保存皇后放置位置的结果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    static int count = 0;
    static int judgeCount = 0;

    public static void main(String[] args) {
        //测试一把 , 8 皇后是否正确
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d 解法", count);
        System.out.printf("一共判断冲突的次数%d 次", judgeCount); // 1.5w
    }

    //编写一个方法,放置第 n 个皇后
    //特别注意: check 是 每一次递归时,进入到 check 中都有 for(int i = 0; i < max; i++),因此会有回溯
    private void check(int n) {
        if (n == max) { //n = 8是第九个皇后, 其实 8 个皇后就放好
            print();
            return;
        }
        //依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
            //先把当前这个皇后 n , 放到该行的第 1 列
            array[n] = i;
            //判断当放置第 n 个皇后到 i 列时,是否冲
            if (judge(n)) { // 不冲突
                //接着放 n+1 个皇后,即开始递归
                check(n + 1); //
            }
            //如果冲突,就继续执行 array[n] = i; 即将第 n 个皇后,放置在本行得 后移的一个位置
        }
    }
    //查看当我们放置第 n 个皇后, 就去检测该皇后是否和前面已经摆放的皇后冲突

    /**
     * @param n 表示第 n 个皇后
     * @return
     */
    private boolean judge(int n) {
        judgeCount++;
        for (int i = 0; i < n; i++) {
            // 说明
            //1. array[i] == array[n] 表示判断 第 n 个皇后是否和前面的 n-1 个皇后在同一列
            //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判断第 n 个皇后是否和第 i 皇后是否在同一斜线
            // n = 1 放置第 2 列 1 n = 1 array[1] = 1
            // Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
            //3. 判断是否在同一行, 没有必要,n 每次都在递增
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
                return false;
            }
        }
        return true;
    }

    //写一个方法,可以将皇后摆放的位置输出
    private void print() {
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

查找

二分查找的递归写法

public static int BinarySearch(int[] arr, int left, int right, int target) {
        System.out.println("1111");
        int mid = (left + right) >>> 1;
        int midval = arr[mid];
        if (left > right) return -1;
        if (midval < target) {
            return BinarySearch(arr, mid + 1, right, target);
        } else if (midval > target) {
            return BinarySearch(arr, left, mid - 1, target);
        } else {
            return mid;
        }
    }
 //优化,有多个重复的情况
    public static ArrayList<Integer> BinarySearch2(int[] arr, int left, int right, int target) {
        System.out.println("2222");
        int mid = (left + right) >>> 1;
        int midval = arr[mid];
        if (left > right) return new ArrayList();
        if (midval < target) {
            return BinarySearch2(arr, mid + 1, right, target);
        } else if (midval > target) {
            return BinarySearch2(arr, left, mid - 1, target);
        } else {
            ArrayList<Integer> list = new ArrayList<>();
            int temp = mid - 1;
            while (true) {
                if (temp < 0 || arr[temp] != target) break;
                list.add(temp);
                temp--;
            }
            list.add(mid);
            temp = mid + 1;
            while (true) {
                if (temp > arr.length || arr[temp] != target) break;
                list.add(temp);
                temp++;
            }
            return list;
        }
    }

二分查找改版(插值查找)

插值查找,其实就是mid改用一个公式
在这里插入图片描述

//自适应的二分查找,要求,数组有序,分布均匀,这时比较快,一般一次就找出,
    //如果,分布不均匀,一数超大,一个很小(一个几十,一个几千)就比普通的二分要慢
    //数组无序会越界异常
    public static int BinarySearch3(int[] arr, int left, int right, int target) {
        System.out.println("33333");
        int mid = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left]);
        int midval = arr[mid];
        if (left > right) return -1;
        if (midval < target||target < arr[0]||target > arr[arr.length-1]) {
            return BinarySearch3(arr, mid + 1, right, target);
        } else if (midval > target) {
            return BinarySearch3(arr, left, mid - 1, target);
        } else {
            return mid;
        }
    }

哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
在这里插入图片描述
gogle 公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址.),当输入该员工的 id 时, 要求查找到该员工的 所有信息. 要求:

  1. 不使用数据库,速度越快越好=>哈希表(散列)
  2. 添加时,保证按照 id 从低到高插入 [思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到
    高,怎么解决?]
  3. 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
public class HashTabDemo {
    public static void main(String[] args) {
        //创建哈希表
        HashTab hashTab = new HashTab(7);
        //写一个简单的菜单
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("add: 添加雇员");
            System.out.println("list: 显示雇员");
            System.out.println("find: 查找雇员");
            System.out.println("exit: 退出系统");
            key = scanner.next();
            switch (key) {
                case "add":
                    System.out.println("输入 id");
                    int id = scanner.nextInt();
                    System.out.println("输入名字");
                    String name = scanner.next();
                    //创建 雇
                    Emp emp = new Emp(id, name);
                    hashTab.add(emp);
                    break;
                case "list":
                    hashTab.list();
                    break;
                case "find":
                    System.out.println("请输入要查找的 id");
                    id = scanner.nextInt();
                    hashTab.findEmpById(id);
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                default:
                    break;
            }
        }
    }
}

//创建 HashTab 管理多条链表
class HashTab {
    private EmpLinkedList[] empLinkedListArray;
    private int size; //表示有多少条链表

    //构造器
    public HashTab(int size) {
        this.size = size;
        //初始化 empLinkedListArray
        empLinkedListArray = new EmpLinkedList[size];
        //?留一个坑, 这时不要分别初始化每个链表
        for (int i = 0; i < size; i++) {
            empLinkedListArray[i] = new EmpLinkedList();
        }
    }

    //添加雇员
    public void add(Emp emp) {
        //根据员工的 id ,得到该员工应当添加到哪条链表
        int empLinkedListNO = hashFun(emp.id);
        //将 emp 添加到对应的链表中
        empLinkedListArray[empLinkedListNO].add(emp);
    }

    //遍历所有的链表,遍历 hashtab
    public void list() {
        for (int i = 0; i < size; i++) {
            empLinkedListArray[i].list(i);
        }
    }

    //根据输入的 id,查找雇员
    public void findEmpById(int id) {
        //使用散列函数确定到哪条链表查找
        int empLinkedListNO = hashFun(id);
        Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
        if (emp != null) {//找到
            System.out.printf("在第%d 条链表中找到 雇员 id = %d\n", (empLinkedListNO + 1), id);
        } else {
            System.out.println("在哈希表中,没有找到该雇员~");
        }
    }

    //编写散列函数, 使用一个简单取模法
    public int hashFun(int id) {
        return id % size;
    }
}

//表示一个雇员
class Emp {
    public int id;
    public String name;
    public Emp next; //next 默认为 null

    public Emp(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
}

//创建 EmpLinkedList ,表示链表
class EmpLinkedList {
    //头指针,执行第一个 Emp,因此我们这个链表的 head 是直接指向第一个 Emp
    private Emp head; //默认 null

    //添加雇员到链表
    //说明
    //1. 假定,当添加雇员时,id 是自增长,即 id 的分配总是从小到大
    // 因此我们将该雇员直接加入到本链表的最后即可
    public void add(Emp emp) {
        //如果是添加第一个雇员
        if (head == null) {
            head = emp;
            return;
        }
        //如果不是第一个雇员,则使用一个辅助的指针,帮助定位到最后
        Emp curEmp = head;
        while (true) {
            if (curEmp.next == null) {//说明到链表最后
                break;
            }
            curEmp = curEmp.next; //后移
        }
        //退出时直接将 emp 加入链表
        curEmp.next = emp;
    }

    //遍历链表的雇员信息
    public void list(int no) {
        if (head == null) { //说明链表为空
            System.out.println("第 " + (no + 1) + " 链表为空");
            return;
        }
        System.out.print("第 " + (no + 1) + " 链表的信息为");
        Emp curEmp = head; //辅助指针
        while (true) {
            System.out.printf(" => id=%d name=%s\t", curEmp.id, curEmp.name);
            if (curEmp.next == null) {//说明 curEmp 已经是最后结点
                break;
            }
            curEmp = curEmp.next; //后移,遍历
        }
        System.out.println();
    }

    //根据 id 查找雇员
    //如果查找到,就返回 Emp, 如果没有找到,就返回 null
    public Emp findEmpById(int id) {
        //判断链表是否为空
        if (head == null) {
            System.out.println("链表为空");
            return null;
        }
        //辅助指针
        Emp curEmp = head;
        while (true) {
            if (curEmp.id == id) {//找到
                break;//这时 curEmp 就指向要查找的雇员
            }
            //退出
            if (curEmp.next == null) {//说明遍历当前链表没有找到该雇员
                curEmp = null;
                break;
            }
            curEmp = curEmp.next;//以后
        }
        return curEmp;
    }
}

树结构基础

一、二叉树

为什么需要树这种数据结构

  1. 数组存储方式的分析
    优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
    缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低
  2. 链式存储方式的分析
    优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,
    删除效率也很好)。
    缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
  3. 树存储方式的分析
    能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tre),既可以保证数据的检索速度,同时也
    可以保证数据的插入,删除,修改的速度。

二叉树的概念

在这里插入图片描述

  1. 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
  2. 二叉树的子节点分为左节点和右节点
  3. 示意图
    在这里插入图片描述
  4. 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
    在这里插入图片描述
  5. 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二
    层的叶子节点在右边连续,我们称为完全二叉树
    在这里插入图片描述

二叉树的遍历

  1. 前序遍历: 先输出父节点,再遍历左子树和右子树
  2. 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
  3. 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
  4. 小结: 看输出父节点的顺序,就确定是前序,中序还是后序

例子:

public class BinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node1 = new HeroNode(2, "林冲");
        HeroNode node2 = new HeroNode(3, "李达");
        HeroNode node3 = new HeroNode(4, "卢俊义");
        HeroNode node4 = new HeroNode(6, "吴用");
        HeroNode node5 = new HeroNode(5, "沙和尚");
        HeroNode node6 = new HeroNode(7, "黑旋风");
        BinaryTree binaryTree = new BinaryTree(root);
        root.setLeft(node1);
        root.setRight(node2);
        node1.setLeft(node3);
        node1.setRight(node5);
        node2.setRight(node6);
        node2.setLeft(node4);

        binaryTree.preOrder();
        System.out.println("------------------------");
        binaryTree.infixOrder();
        System.out.println("-------------------------");
        binaryTree.postOrder();

    }

}

//定义二叉树
class BinaryTree {
    private HeroNode root;

    public BinaryTree(HeroNode root) {
        this.root = root;
    }

    public void preOrder(){
        if (this.root != null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空");
        }
    }
    public void infixOrder(){
        if (this.root != null){
            this.root.infixOrder();
        }else {
            System.out.println("为叉树为空");
        }
    }
    public void postOrder(){
        if (this.root != null){
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空");
        }
    }

}


class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;


    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    public int getNo() {
        return no;
    }

    public String getName() {
        return name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public HeroNode getRight() {
        return right;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("\"no\":")
                .append(no);
        sb.append(",\"name\":\"")
                .append(name).append('\"');
        sb.append('}');
        return sb.toString();
    }

    //前序遍历
    public void preOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    //后序遍历
    public void postOrder() {
        if (this.left != null) {
            this.left.postOrder();
        }
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值