数据结构Java

本文深入探讨了数据结构中的排序算法,包括冒泡排序、二分查找、插入排序、哈希表、堆排序、二叉树和平衡树(如B树、B+树、B*树)。还介绍了查找算法,如线性查找、二分查找,以及树结构如二叉排序树和多路查找树。此外,文章讲解了递归和哈希表在实际问题中的应用,如谷歌面试题中的赫夫曼树。最后,总结了排序算法的时间复杂度,强调了如何通过优化数据结构提高检索速度。
摘要由CSDN通过智能技术生成

-----参考-----

1)网址

https://blog.csdn.net/oneby1314/category_10231585.html

2)视频

https://www.bilibili.com/video/BV1E4411H73v?p=108&spm_id_from=pageDriver

一.稀疏数组与队列

1)稀疏数组

1.举例说明

2.代码实现

public class LessArr {
    public static void main(String[] args) {
        //11*11棋盘,1白棋,2黑棋
        int[][] chessArr = new int[11][11];
        chessArr[1][2] = 1;
        chessArr[2][3] = 2;

        //1.遍历有效数量
        int validNums = 0;
        for (int[] ints : chessArr) {
            for (int item : ints) {
                System.out.print(item + "\t");
                if (item != 0){
                    validNums++;
                }
            }
            System.out.println();
        }
        System.out.println("有效数据个数:" + validNums);
        //2.创建稀疏数组
        int[][] arr2 = new int[validNums + 1][3];
        arr2[0][0] = chessArr.length;
        arr2[0][1] = chessArr.length;
        arr2[0][2] = validNums;

        int sort = 1;
        for (int i = 0; i < chessArr.length; i++) {
            for (int j = 0; j < chessArr.length; j++) {
                if (chessArr[i][j] != 0){
                    arr2[sort][0] = i;
                    arr2[sort][1] = j;
                    arr2[sort][2] = chessArr[i][j];
                    sort++;
                }
            }
        }
        //遍历
        for (int i = 0; i < arr2.length; i++) {
            System.out.printf("%d\t%d\t%d\t\n", arr2[i][0], arr2[i][1], arr2[i][2]);
        }
        System.out.println("================");
        //3.恢复为二维数组
        int[][] originArr = new int[arr2[0][0]][arr2[0][1]];
        for (int i = 1; i < arr2.length; i++) {
            originArr[arr2[i][0]][arr2[i][1]] = arr2[i][2];
        }
        for (int[] ints : originArr) {
            for (int item : ints) {
                System.out.print(item + "\t");
            }
            System.out.println();
        }

    }
}

//输出
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
有效数据个数:2
11	11	2	
1	2	1	
2	3	2	
================
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	

2)队列

1.队列介绍

  • maxSize :队列容量(数组的长度)
  • arr :模拟队列的数组
  • front :指向队列头部元素的前一个元素,初始值为 -1
  • rear :指向队列尾部元素,初始值为 -1
队列判空:front == rear
队列判满:rear == (maxSize - 1) ,即 rear 是否已经指向了数组的最后一个位置
队列元素个数:rear - front
队列入队:队列不满才能入队,arr[++rear] = value
队列出队:队列不空才能出队,return arr[front++]

2.代码实现

public class ArrQueue {
    public static void main(String[] args) {
        Queue queue = new Queue(3);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.print("输入操作(+或者-):");
            String s = scanner.nextLine();
            if ("+".equals(s)){
                System.out.print("输入入队数据:");
                String data = scanner.nextLine();
                System.out.println(queue.pushQueue(data));
            }else {
                System.out.println(queue.popQueue());
            }
            queue.showQueue();
        }
    }
}
class Queue{
    private int maxSize; // 表示数组的最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private String[] arr; // 该数据用于存放数据, 模拟队列
    //1.初始化队列
    public Queue(int maxSize) {
        this.maxSize = maxSize;
        this.front = -1;
        this.rear = -1;
        this.arr = new String[maxSize];
    }
    //2.判满
    public boolean isFull(){
        return rear == maxSize - 1;
    }
    //3.判空
    public boolean isEmpty(){
        return rear == front;
    }
    //4.入队
    public String pushQueue(String data){
        if (isFull()){
            return "队列已满!!!";
        }
        rear++;
        arr[rear] = data;
        return "入队成功!!";
    }
    //5.出队
    public String popQueue(){
        if (isEmpty()){
            return "队列为空!!";
        }
        front++;
        String i = arr[front];
        return i + ":出队成功!!";
    }
    //6.显示所有数据
    public void showQueue(){
        if (isEmpty()){
            return ;
        }
        System.out.print("队列数据:\t");
        for (int i = front+1; i <= rear; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

3.循环队列

public class CircleQueue {
    public static void main(String[] args) {
        CirQueue queue = new CirQueue(4);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.print("输入操作(+或者-):");
            String s = scanner.nextLine();
            if ("+".equals(s)){
                System.out.print("输入入队数据:");
                String data = scanner.nextLine();
                System.out.println(queue.pushQueue(data));
            }else {
                System.out.println(queue.popQueue());
            }
            queue.showQueue();
        }
    }
}

class CirQueue{
    private int maxSize; // 表示数组的最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private String[] arr; // 该数据用于存放数据, 模拟队列
    //1.初始化队列
    public CirQueue(int maxSize) {
        this.maxSize = maxSize;
        this.front = 0;
        this.rear = 0;
        this.arr = new String[maxSize];
    }
    //2.判满
    public boolean isFull(){
        return (rear+1) % maxSize == front;
    }
    //3.判空
    public boolean isEmpty(){
        return rear == front;
    }
    //4.入队
    public String pushQueue(String data){
        if (isFull()){
            return "队列已满!!!";
        }
        arr[rear] = data;
        rear = (rear+1)%maxSize;
        return "入队成功!!";
    }
    //5.出队
    public String popQueue(){
        if (isEmpty()){
            return "队列为空!!";
        }
        String i = arr[front];
        front = (front+1)%maxSize;
        return i + ":出队成功!!";
    }
    //6.显示所有数据
    public void showQueue(){
        if (isEmpty()){
            return ;
        }
        System.out.print("队列数据:\t");
        for (int i = front; i < front+(rear+maxSize-front)%maxSize; i++) {
            System.out.print(arr[i%maxSize] + "\t");
        }
        System.out.println();
    }
}

二.链表

1)单向链表

1.示意图

2.代码

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 hero03 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//        // 创建要给链表
//        SingleLinkedList singleLinkedList = new SingleLinkedList();
//        //1. 尾插法
//        singleLinkedList.add(hero1);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero4);
//
//        singleLinkedList.showList();
        System.out.println("-----------------------");

        SingleLinkedList singleLinkedList1 = new SingleLinkedList();
        //2. 顺序添加
        singleLinkedList1.addByOrder(hero3);
        singleLinkedList1.addByOrder(hero1);
        singleLinkedList1.addByOrder(hero4);
        singleLinkedList1.addByOrder(hero03);
        singleLinkedList1.addByOrder(hero2);

        singleLinkedList1.showList();
        //3.修改
        singleLinkedList1.update(new HeroNode(3, "无用--", "智多星"));
        System.out.println("修改后:");
        singleLinkedList1.showList();
        //4.删除
        singleLinkedList1.delete(2);
        System.out.println("删除后:");
        singleLinkedList1.showList();
        //5.求节点个数
        System.out.println("节点个数:");
        System.out.println(count(SingleLinkedList.header));
        //6.反转单链表
        System.out.println("反转:");
        reverse(SingleLinkedList.header);
        singleLinkedList1.showList();
        //7.反转单链表(栈)
        System.out.println("反转(栈):");
        reverseStack(SingleLinkedList.header);

    }
    public static int len = 0;
    public static int count(HeroNode header){
        if (header.next == null){
            return len;
        }
        header = header.next;
        len++;
        count(header);

        return len;
    }
    public static void reverse(HeroNode oldLinked){
        if (oldLinked.next == null || oldLinked.next.next == null){
            return ;
        }
        HeroNode newLinked = new HeroNode(0, "", "");
        HeroNode curr = oldLinked.next;
        HeroNode next = null;
        while (curr != null){
            next = curr.next;//保存下一个节点
            curr.next = newLinked.next;
            newLinked.next = curr;
            curr = next;
        }
        oldLinked.next = newLinked.next;

    }
    public static void reverseStack(HeroNode oldLinked){
        if (oldLinked.next == null){
            return ;
        }
        Stack<HeroNode> stack = new Stack<>();
        HeroNode curr = oldLinked.next;
        while (curr != null){
            stack.push(curr);
            curr = curr.next;
        }
        while (stack.size()>0){
            HeroNode pop = stack.pop();
            System.out.println(pop);
        }
    }
}
class SingleLinkedList{
    public static HeroNode header;

    public SingleLinkedList(){
        header = new HeroNode(0, "", "");
    }

    public void add(HeroNode heroNode){
        HeroNode temp = header;
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = heroNode;
    }
    public void addByOrder(HeroNode heroNode){
        HeroNode temp = header;
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.id > heroNode.id) {
                heroNode.next = temp.next;
                break;
            }
            if (temp.next.id == heroNode.id){
                System.out.println("%d重复不能添加!!" + heroNode);
                return;
            }
            temp = temp.next;
        }
        temp.next = heroNode;

    }
    public void update(HeroNode heroNode){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        HeroNode temp = header;
        while (true){
            if (temp.next == null){
                System.out.println("修改的数据没找到");
                return;
            }
            if (temp.next.id == heroNode.id){
                temp.next.name = heroNode.name;
                temp.next.nickName = heroNode.nickName;
                return;

            }
            temp = temp.next;
        }
    }
    public void delete(int id){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        HeroNode temp = header;
        while (true){
            if (temp.next == null){
                System.out.println("删除数据没找到!!!");
                return;
            }
            if (temp.next.id == id){
                temp.next = temp.next.next;
                return;
            }
            temp = temp.next;
        }
    }
    public void showList(){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        HeroNode temp = header.next;
        while (true){
            if (temp.next == null){
                System.out.println(temp);
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}

//定义HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode {
    public int id;
    public String name;
    public String nickName;
    public HeroNode next; // 指向下一个节点

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

    @Override
    public String toString() {
        return "HeroNode{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}
//输出
-----------------------
%d重复不能添加!!HeroNode{id=3, name='吴用', nickName='智多星'}
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{id=3, name='吴用', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
修改后:
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
删除后:
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
节点个数:
3
反转:
HeroNode{id=4, name='林冲', nickName='豹子头'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=1, name='宋江', nickName='及时雨'}
反转(栈):
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}

2)双向链表

1.示意图

2.代码

package day2_linkedList;


import java.util.Stack;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        // 先创建节点
        DoubleHeroNode hero1 = new DoubleHeroNode(1, "宋江", "及时雨");
        DoubleHeroNode hero2 = new DoubleHeroNode(2, "卢俊义", "玉麒麟");
        DoubleHeroNode hero3 = new DoubleHeroNode(3, "吴用", "智多星");
        DoubleHeroNode hero03 = new DoubleHeroNode(3, "吴用", "智多星");
        DoubleHeroNode hero4 = new DoubleHeroNode(4, "林冲", "豹子头");

        System.out.println("-----------------------");

        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        //2. 顺序添加
        doubleLinkedList.addByOrder(hero3);
        doubleLinkedList.addByOrder(hero1);
        doubleLinkedList.addByOrder(hero4);
        doubleLinkedList.addByOrder(hero03);
        doubleLinkedList.addByOrder(hero2);

        doubleLinkedList.showList();
        //3.修改
        doubleLinkedList.update(new DoubleHeroNode(3, "无用--", "智多星"));
        System.out.println("修改后:");
        doubleLinkedList.showList();
        //4.删除
        doubleLinkedList.delete(2);
        System.out.println("删除后:");
        doubleLinkedList.showList();
        //5.求节点个数
        System.out.println("节点个数:");
        System.out.println(count(DoubleLinkedList.header));
        //6.反转单链表
        System.out.println("反转:");
        reverse(DoubleLinkedList.header);
        doubleLinkedList.showList();
        //7.反转单链表(栈)
        System.out.println("反转(栈):");
        reverseStack(DoubleLinkedList.header);

    }
    public static int len = 0;
    public static int count(DoubleHeroNode header){
        if (header.next == null){
            return len;
        }
        header = header.next;
        len++;
        count(header);

        return len;
    }
    public static void reverse(DoubleHeroNode oldLinked){
        if (oldLinked.next == null || oldLinked.next.next == null){
            return ;
        }
        DoubleHeroNode newLinked = new DoubleHeroNode(0, "", "");
        DoubleHeroNode curr = oldLinked.next;
        DoubleHeroNode next = null;
        while (curr != null){
            next = curr.next;//保存下一个节点
            curr.next = newLinked.next;
            newLinked.next = curr;
            curr = next;
        }
        oldLinked.next = newLinked.next;

    }
    public static void reverseStack(DoubleHeroNode oldLinked){
        if (oldLinked.next == null){
            return ;
        }
        Stack<DoubleHeroNode> stack = new Stack<>();
        DoubleHeroNode curr = oldLinked.next;
        while (curr != null){
            stack.push(curr);
            curr = curr.next;
        }
        while (stack.size()>0){
            DoubleHeroNode pop = stack.pop();
            System.out.println(pop);
        }
    }
}

class DoubleLinkedList{
    public static DoubleHeroNode header;

    public DoubleLinkedList(){
        header = new DoubleHeroNode(0, "", "");
    }

    public void add(DoubleHeroNode heroNode){
        DoubleHeroNode temp = header;
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = heroNode;
    }
    public void addByOrder(DoubleHeroNode heroNode){
        DoubleHeroNode temp = header;
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.id > heroNode.id) {
                heroNode.next = temp.next;
                heroNode.pre =temp;
                break;
            }
            if (temp.next.id == heroNode.id){
                System.out.println("%d重复不能添加!!" + heroNode);
                return;
            }
            temp = temp.next;
        }
        temp.next = heroNode;
        temp.next.pre = heroNode;

    }
    public void update(DoubleHeroNode heroNode){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        DoubleHeroNode temp = header;
        while (true){
            if (temp.next == null){
                System.out.println("修改的数据没找到");
                return;
            }
            if (temp.next.id == heroNode.id){
                temp.next.name = heroNode.name;
                temp.next.nickName = heroNode.nickName;
                return;

            }
            temp = temp.next;
        }
    }
    public void delete(int id){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        DoubleHeroNode temp = header.next;
        while (true){
            if (temp.next == null){
                System.out.println("删除数据没找到!!!");
                return;
            }
            if (temp.next.id == id){
                temp.pre.next = temp.next;
                if (temp.next != null){
                    temp.next.pre = temp.pre;
                }
                return;
            }
            temp = temp.next;
        }
    }
    public void showList(){
        if (header.next == null){
            System.out.println("链表为空!!");
            return;
        }
        DoubleHeroNode temp = header.next;
        while (true){
            if (temp.next == null){
                System.out.println(temp);
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}

//定义HeroNode , 每个HeroNode 对象就是一个节点
class DoubleHeroNode {
    public int id;
    public String name;
    public String nickName;
    public DoubleHeroNode next; // 指向下一个节点
    public DoubleHeroNode pre;

    public DoubleHeroNode(int id, String name, String nickName) {
        this.id = id;
        this.name = name;
        this.nickName = nickName;
    }

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

3)约瑟夫环

1.说明

  • Josephu 问题为: 设编号为 1, 2, … n 的 n 个人围坐一圈, 约定编号为 k(1<=k<=n) 的人从 1 开始报数, 数到 m 的那个人出列, 它的下一位又从 1 开始报数, 数到 m 的那个人又出列, 依次类推, 直到所有人出列为止, 由此产生一个出队编号的序列。

2.代码

public class JosephuDemo {
    public static void main(String[] args) {
        Josephu josephu = new Josephu();
        //创建18人的循环单链表
        josephu.create(18);
        //每次报数到3打印
        josephu.outMan(3);
    }
}
class Josephu{
    private static Man first;
    private Man current;
    
    public Man create(int count){
        Man man1 = new Man(1);
        first = man1;
        current = first;
        for (int i = 2; i <= count; i++) {
            Man man = new Man(i);
            current.next = man;

            current = current.next;
        }
        current.next = first;
        return first;
    }
    public void outMan(int num){
        Man result = first;
        while (result.next != result){
            for (int i = 1; i <= num; i++) {
                if (i == num-1){
                    System.out.print(result.next.no + " => ");
                    result.next  = result.next.next;
                    result = result.next;
                    break;
                }
                result = result.next;
            }
        }
        System.out.print(result.no);
    }
}
class Man{
    int no;
    Man next;
    public Man(int no){
        this.no = no;
    }
    @Override
    public String toString() {
        return "Man{" +
                "no=" + no +
                '}';
    }
}

三.栈

1)基本使用

1.栈的应用场景

1子程序的调用: 在跳往子程序前, 会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出, 以回到原来的程序中。
2处理递归调用: 和子程序的调用类似, 只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入栈中。
3表达式的转换:[中缀表达式转后缀表达式]与求值(实际解决)。
4二叉树的遍历。
5图形的深度优先(depth 一 first)搜索法。

2.数组 栈

maxSize :栈的大小(数组的大小)
arr :用来模拟栈的数组
top :指向当前栈顶元素,初始值为 -1 ,表示栈空
判断栈满:top == maxSize ,即已经到达数组最后一个位置
判断栈空:top == -1
入栈:arr[++top] = arr;
出栈:return arr[top–] ;
public class ArrayStackDemo {
    public static void main(String[] args) {
        ArrayStack stack = new ArrayStack(3);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.print("操作(1.pop   2.push):");
            int i = scanner.nextInt();
            switch (i){
                case 1:
                    stack.pop();
                    stack.show();
                    break;
                case 2:
                    System.out.println("输入入栈数据:");
                    stack.push(scanner.nextInt());
                    stack.show();
                    break;
            }
            System.out.println();
        }
    }
}
class ArrayStack{
    int maxSize;
    int[] stack;
    int top;

    public ArrayStack(int maxSize){
        top = -1;
        this.maxSize = maxSize;
        this.stack = new int[maxSize];
    }
    public boolean isEmpty(){
        if (top == -1){
            return true;
        }
        return false;
    }
    public boolean isFull(){
        if (top == maxSize - 1){
            return true;
        }
        return false;
    }
    public void push(int data){
        if (isFull()) {
            System.out.println("栈满!!!!");
            return;
        }
        top++;
        stack[top] = data;
    }
    public int pop(){
        if (isEmpty()) {
            System.out.println("栈空!!!!");
            return -1;
        }
        int value = stack[top];
        top--;
        return value;
    }
    public void show(){
        if (isEmpty()) return;
        System.out.print("栈数据:");
        for (int i = 0; i <= top; i++) {
            System.out.print(stack[i] + "\t");
        }
    }
}

2).表达式计算(中缀表达式)

1.示意图

2.代码

public class ComputeStackDemo {
    public static void main(String[] args) {
        String expre = "1+212/3/10*1-10";
        Method method = new Method();
        System.out.println(method.useStack(expre));
    }
}
class Method{
    public int getPriority(char value){
        if ('*'==value || '/'==value){
            return 2;
        }else if ('+'==value || '-'==value){
            return 1;
        }else return 0;
    }
    public boolean isOperator(char value){
        return '*'==value || '/'==value || '+'==value || '-'==value;
    }
    public int compute(int a, int b, char operator){
       switch (operator){
           case '+':
               return b + a;
           case '-':
               return b - a;
           case '*':
               return b * a;
           case '/':
               return b / a;
       }
       return -1;
    }

    public int useStack(String expression) {
        ComputeStack intStack = new ComputeStack(10);
        ComputeStack operStack = new ComputeStack(10);

        String str = "";
        for (int i = 0; i < expression.length(); i++) {

            char c = expression.charAt(i);
            if (isOperator(c)) {
                if (operStack.isEmpty()) {
                    operStack.push(c);
                } else {
                    if (getPriority(operStack.getTopData()) >= getPriority(c)) {
                        int compute = compute(intStack.pop(), intStack.pop(), operStack.getTopData());
                        intStack.push(compute);
                        operStack.pop();
                    }
                    operStack.push(c);
                }
            } else {
                if (i == expression.length() - 1 && str.equals("")) {
                    intStack.push(c-48);
                    break;
                }
                if (i!=expression.length()-1 && !isOperator(expression.charAt(i + 1))) {
                    str += c;
                } else {
                    if ("".equals(str)){
                        intStack.push(c-48);
                    }else {
                        intStack.push(Integer.parseInt(str + c));
                        str = "";
                    }
                }
            }
        }
        while (true) {
            if (operStack.isEmpty()) {
                break;
            }
            int compute = compute(intStack.pop(), intStack.pop(), (char) operStack.pop());
            intStack.push(compute);// 入栈
        }
        return intStack.pop();
    }
}
class ComputeStack {
    int maxSize;
    int[] stack;
    int top;
    public ComputeStack(int maxSize) {
        top = -1;
        this.maxSize = maxSize;
        this.stack = new int[maxSize];
    }
    public char getTopData(){
        return (char)stack[top];
    }
    public boolean isEmpty() {
        if (top == -1) {
            return true;
        }
        return false;
    }
    public boolean isFull() {
        if (top == maxSize - 1) {
            return true;
        }
        return false;
    }
    public void push(int data) {
        if (isFull()) {
            System.out.println("栈满!!!!");
            return;
        }
        top++;
        stack[top] = data;
    }
    public int pop() {
        if (isEmpty()) {
            System.out.println("栈空!!!!");
            return -1;
        }
        int value = stack[top];
        top--;
        return value;
    }
    public void show() {
        if (isEmpty()) return;
        System.out.print("栈数据:");
        for (int i = 0; i <= top; i++) {
            System.out.print(stack[i] + "\t");
        }
    }
}

3)逆波兰表达式(后缀表达式)

1.介绍

2.代码

public class SuffixExpressionDemo {
    public static void main(String[] args) {
        String exp = "1+((2+3)*4)-5";
        //1.字符串转list
        List<String> list = toList(exp);
        System.out.println(list);
        //2.前缀转后缀
        List<String> sufList = toSufExpress(list);
        System.out.println(sufList);
        //3.输出结果
        System.out.println(compute(sufList));
    }
    public static List<String> toSufExpress(List<String> preExp){
        Stack<String> expStack = new Stack<>();
        ArrayList<String> resList = new ArrayList<>();

        for (String s : preExp) {
            if (isNum(s.charAt(0))){
                resList.add(s);
            }else if ("(".equals(s)){
                expStack.push(s);
            } else if (")".equals(s)){
                while (!"(".equals(expStack.peek())){
                    resList.add(expStack.pop());
                }
                expStack.pop();
            }else {
                while (true){
                    if (expStack.isEmpty() ||
                            "(".equals(expStack.peek()) || getPriority(s)>getPriority(expStack.peek())){
                        expStack.push(s);
                        break;
                    }else {
                        resList.add(expStack.pop());
                    }
                }
            }
        }
        while (!expStack.isEmpty()){
            resList.add(expStack.pop());
        }
        return resList;
    }
    public static int getPriority(String s){
        if ("*".equals(s) || "/".equals(s)){
            return 1;
        }else {
            return 0;
        }
    }
    public static List<String> toList(String exp){
        ArrayList<String> list = new ArrayList<>();
        int temp = 0;
        for (int i = 0; i < exp.length(); i++) {
            if (!isNum(exp.charAt(i)) && i!=temp){
                list.add(exp.substring(temp, i));
                list.add(String.valueOf(exp.charAt(i)));
                temp = i+1;
            }else if (!isNum(exp.charAt(i))){
                list.add(String.valueOf(exp.charAt(i)));
                temp++;
            }else if (i == exp.length()-1){
                list.add(exp.substring(temp));
            }
        }
        return list;
    }
    public static int compute(List<String> sufExp){
        Stack<String> stack = new Stack<>();

        for (String s : sufExp) {
            if (isNum(s.charAt(0))){
                stack.push(s);
            }else {
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int res = 0;
                if ("+".equals(s)){
                    res = num2+num1;
                }else if ("-".equals(s)){
                    res = num2-num1;
                }else if ("*".equals(s)){
                    res = num2*num1;
                }else if ("/".equals(s)){
                    res = num2/num1;
                }
                stack.push(String.valueOf(res));
            }
        }
        return Integer.parseInt(stack.pop());
    }
    public static boolean isNum(char c){
        return c>=48 && c<=57;
    }
}

四.递归

1)简介

1.递归能解决什么问题

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

2.递归需遵循的规则

  • 执行一个方法时, 就创建一个新的受保护的独立空间(一个线程有自己独立的一个栈空间,每个方法调用对应着一个栈帧)
  • 方法的局部变量是独立的, 不会相互影响, 比如 n 变量
  • 如果方法中使用的是引用类型变量(比如数组), 就会共享该引用类型的数据
  • 递归必须向退出递归的条件逼近, 否则就是无限递归,出现 StackOverflowError, 死龟了 😃
  • 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

2)迷宫问题(待完善)

1.代码思路

  • 使用二维数组 map[][] 模拟迷宫
  • 约定: 当 map[i][j] 为 0 表示该点没有走过;当为 1 表示;2 表示通路可以走 ;3 表示该点已经走过,但是走不通
  • setWay() 方法用于找路,true 表示该路可以走通,false 表示该路走不通
  • 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 一步一步向前试探,如果该点走不通,再回溯
  • 每当走到一个点时,将该点置为 2 ,暂时假设该路能走通,至于到底走不走得通,得看后面有没有找到通路
    • 如果后面的路能走通,从最后一个点开始返回,整个 setWay() 递归调用链都返回 true
    • 如果后面的路不能走通,那么将当前的点设置为 3 ,表示是死路,走不通,回溯至上一个点,看看其他方向能不能走通

2.代码

public class MazeGameDemo {
    public static void main(String[] args) {
        //1.创建迷宫
        int[][] maze = {{1,1,1,1,1,1,1,1},
                        {1,0,0,0,0,0,0,1},
                        {1,0,0,0,0,0,0,1},
                        {1,1,1,0,0,0,0,1},
                        {1,0,0,0,0,0,0,1},
                        {1,0,0,0,0,0,0,1},
                        {1,0,0,0,0,0,0,1},
                        {1,1,1,1,1,1,1,1}};
        swap(maze, 1, 1);
        for (int i = 0; i < maze.length; i++) {
            for (int j = 0; j < maze[i].length; j++) {
                System.out.print(maze[i][j] + "  ");
            }
            System.out.println();
        }
    }
    public static boolean swap(int[][] maze, int i, int j){
        if (maze[6][6] == 2){
            return true;
        }else {
            if (maze[i][j] == 0) {
                maze[i][j] = 2;
                //下右上左
                if (swap(maze, i+1, j)){
                    return true;
                }else  if (swap(maze, i, j+1)){
                    return true;
                }else  if (swap(maze, i-1, j)){
                    return true;
                }else  if (swap(maze, i, j-1)){
                    return true;
                }else{
                    maze[i][j] = 3;
                    return false;
                }
            }else {
                return false;
            }
        }
    }
}

3)八皇后问题(回溯-待优化)

1.思路

2.代码

public class EightQueenDemo {
    static int num = 0;
    static int count = 0;
    public static void main(String[] args) {
        int[] queens = new int[8];
        check(queens, 0);
        System.out.println("总共有:" + num);
        System.out.println("冲突次数:" + count);
    }
    public static boolean isConflict(int[] queens, int n){
        for (int i = 0; i < n; i++) {
            //说明:判断列&斜线是否冲突
            if (queens[n]==queens[i] || Math.abs(n-i)==Math.abs(queens[n]-queens[i])){
                count++;
                return false;
            }
        }
        return true;
    }
    //递归调用
    public static void check(int[] queens, int n){
        if (n == queens.length){
            System.out.println(Arrays.toString(queens));
            num++;
        }else {
            for (int i = 0; i < queens.length; i++) {
                queens[n] = i;
                if (isConflict(queens, n)){
                    check(queens, n+1);
                }
            }
        }
    }
}

五.排序算法

1)介绍

1.排序算法的分类

2.平均和最坏时间复杂度

2)算法的复杂度

1.时间复杂度

①时间频度
一个算法中的语句执行次数称为语句频度或时间频度。 记为 T(n)
②时间复杂度
  • 一般情况下, 算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数, 用 T(n)表示, 若有某个辅助函数 f(n), 使得当 n 趋近于无穷大时, T(n) / f(n) 的极限值为不等于零的常数, 则称 f(n)是 T(n)的同数量级函数。记作 T(n)=O ( f(n) ), 称O ( f(n) ) 为算法的渐进时间复杂度, 简称时间复杂度。
  • T(n) 不同, 但时间复杂度可能相同。 如: T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的 T(n) 不同, 但时间复杂度相同, 都为 O(n²)
  • 计算时间复杂度的方法
    1.用常数 1 代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1
    2.修改后的运行次数函数中, 只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²
    3.去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)
③常见时间复杂度
1常数阶 O(1)
2对数阶 O(log2n)
3线性阶 O(n)
4线性对数阶 O(nlog2n)
5平方阶 O(n^2)
6立方阶 O(n^3)
7k 次方阶 O(n^k)
8指数阶 O(2^n)

结论:
	常见的算法时间复杂度由小到大依次为: Ο (1)<Ο (log2n)<Ο (n)<Ο (nlog2n)<Ο (n2)<Ο (n3)< Ο (nk) < Ο (2n) , 随着问题规模 n 的不断增大, 上述时间复杂度不断增大, 算法的执行效率越低
从图中可见, 我们应该尽可能避免使用指数阶的算法

2.空间复杂度

  • 类似于时间复杂度的讨论, 一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间, 它也是问题规模 n 的函数。
  • 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。 有的算法需要占用的临时工作单元数与解决问题的规模 n 有关, 它随着 n 的增大而增大, 当 n 较大时, 将占用较多的存储单元, 例如快速排序和归并排序算法, 基数排序就属于这种情况
  • 在做算法分析时, 主要讨论的是时间复杂度。 从用户使用体验上看, 更看重的程序执行的速度。 一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间

3)冒泡排序

1.介绍

2.代码

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            for (int j = 0; j < arr.length-i-1; j++) {
                if (arr[j+1]<arr[j]){
                    int temp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
}

3)优化代码

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void bubbleSort(int[] arr){
        boolean flag = true;
        for (int i = 0; i < arr.length-1; i++) {
            for (int j = 0; j < arr.length-i-1; j++) {
                if (arr[j+1]<arr[j]){
                    flag = false;
                    int temp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
            if (flag){
                break;
            }else {
                flag = true;
            }
        }
    }
}

4)选择排序

1.介绍

2.代码

public class SelectSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        selectSort(arr);
        System.out.println(Arrays.toString(arr));

    }
    public static void selectSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            int temp = arr[i];
            int index = i;
            for (int j = i; j < arr.length; j++) {
                if (temp > arr[j]){
                    temp = arr[j];
                    index = j;
                }
            }
            arr[index] = arr[i];
            arr[i] = temp;
        }
    }
}

5)插入排序

1.介绍

2.代码

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0, 3,4};
        insertSort(arr);
        System.out.println(Arrays.toString(arr));

    }
    public static void insertSort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            int temp = arr[i];
            int index = i;
            while (index > 0 && arr[index-1] > temp){
                arr[index] = arr[index-1];
                index--;
            }
            arr[index] = temp;
        }
    }
}

6)希尔排序

1.介绍

2.代码

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        shellSort(arr, 2);
        System.out.println(Arrays.toString(arr));

    }
    //优化:插入希尔
    public static void shellSort(int[] arr, int step){
        for (int i = arr.length/step; i > 0 ; i /= step) {
            for (int j = i; j < arr.length; j++) {
                int index = j;
                int temp = arr[j];
                while (index - i >= 0 && arr[index - i] > temp){
                    arr[index] = arr[index - i];
                    index -= i;
                }
                arr[index] = temp;
            }
        }
    }
}

7)快速排序

1.介绍

2.代码

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        quickSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));

    }
    public static void quickSort(int[] arr, int start, int end){
        if (start < end){
            int partition = partition(arr, start, end);
            quickSort(arr, start, partition-1);
            quickSort(arr, partition+1, end);
        }
    }
    public static int partition(int[] arr, int start, int end){
        int temp = arr[start];
        while (start < end){
            while (start < end && arr[end] >= temp){
                end--;
            }
            arr[start] = arr[end];
            while (start < end && arr[start] <= temp){
                start++;
            }
            arr[end] = arr[start];
        }
        arr[start] = temp;
        return start;
    }
}

8)归并排序

1.介绍

2.代码

public class MargeSort {
    public static void main(String[] args) {
        int[] arr = {4,5,66,33,-23,0,4};
        mergeSort(arr, 0, arr.length - 1, new int[arr.length]);
        System.out.println(Arrays.toString(arr));

    }
    public static void mergeSort(int[] arr, int left, int right, int[] temp){
        if (left < right) {
            int mid = (left + right) / 2; // 中间索引
            // 向左递归进行分解
            mergeSort(arr, left, mid, temp);
            // 向右递归进行分解
            mergeSort(arr, mid + 1, right, temp);
            // 合并
            merge(arr, left, mid, right, temp);
        }
    }
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {

        int i = left; // 初始化i, 左边有序序列的初始索引
        int j = mid + 1; // 初始化j, 右边有序序列的初始索引
        int t = 0; // 指向temp数组的当前索引

        // (一)
        // 先把左右两边(有序)的数据按照规则填充到temp数组
        // 直到左右两边的有序序列,有一边处理完毕为止
        while (i <= mid && j <= right) {// 继续
            // 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            // 即将左边的当前元素,填充到 temp数组
            // 然后 t++, i++
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else { // 反之,将右边有序序列的当前元素,填充到temp数组
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
        // (二)
        // 把有剩余数据的一边的数据依次全部填充到temp
        while (i <= mid) { // 左边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        while (j <= right) { // 右边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }
        // (三)
        // 将temp数组的元素拷贝到arr
        // 注意,并不是每次都拷贝所有
        t = 0;
        int tempLeft = left; //
        // 第一次合并 tempLeft = 0 , right = 1 //第二次: tempLeft = 2 right = 3 //第三次: tL=0 ri=3
        // 最后一次 tempLeft = 0 right = 7
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }
}

9)基数排序

1.介绍

2.代码

public class RadixSort {
	public static void main(String[] args) {
		int arr[] = { 53, 3, 542, 748, 14, 214 };
		radixSort(arr);
		System.out.println("基数排序后 " + Arrays.toString(arr));

	}
	// 基数排序方法
	public static void radixSort(int[] arr) {
		//根据前面的推导过程,我们可以得到最终的基数排序代码	
		//1. 得到数组中最大的数的位数
		int max = arr[0]; //假设第一数就是最大数
		for(int i = 1; i < arr.length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		//得到最大数是几位数
		int maxLength = (max + "").length();	
		//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
		//说明
		//1. 二维数组包含10个一维数组
		//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
		//3. 名明确,基数排序是使用空间换时间的经典算法
		int[][] bucket = new int[10][arr.length];
		//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
		//可以这里理解
		//比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数
		int[] bucketElementCounts = new int[10];	
		// n=1 表示处理个位,n=10表示处理十位,n=100表示处理百位 ......
		for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
			//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
			for(int j = 0; j < arr.length; j++) {
				//取出每个元素的对应位的值
				int digitOfElement = arr[j] / n % 10;
				//放入到对应的桶中
				bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
				bucketElementCounts[digitOfElement]++;
			}
			//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
			int index = 0;
			//遍历每一桶,并将桶中的数据,放入到原数组
			for(int k = 0; k < bucketElementCounts.length; k++) {
				//如果桶中,有数据,我们才放入到原数组
				// 遍历第k个桶(即第k个一维数组), 将桶中的数据放回原数组中
				for (int l = 0; l < bucketElementCounts[k]; l++) {
					// 取出元素放入到arr
					arr[index++] = bucket[k][l];
				}
				//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
				bucketElementCounts[k] = 0;				
			}
			System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));
		}
	}	
}

10)堆排序(见九树应用)

11)排序总结

1.介绍

六.查找算法

1)线性查找

1.代码

public class LineSearch {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println(lineSearch(arr, 4));
    }
    public static int lineSearch(int[] arr, int value){
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == value){
                return i;
            }
        }
        return -1;
    }
}

2)二分查找

1.介绍

2.代码

public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println(binarySearch(arr, 0, arr.length-1, 1));
    }
    public static int binarySearch(int[] arr, int left, int right, int value){
        if (left > right) {
            return -1;
        }
        int mid = (left+right)/2;
        int midVal = arr[mid];
        if (value > midVal){
            return binarySearch(arr, mid+1, right, value);
        }else if (value < midVal){
            return binarySearch(arr, left, mid-1, value);
        }else {
            return mid;
        }
    }
}

3)插值查找

1.介绍

2.代码

public class InsertSearch {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println(insertSearch(arr, 0, arr.length-1, 4));
    }
    public static int insertSearch(int[] arr, int left, int right, int value){
        if (left > right){
            return -1;
        }
        int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);;
        int midVal = arr[mid];
        if (value > midVal){
            return insertSearch(arr, mid+1, right, value);
        }else if (value < midVal) {
            return insertSearch(arr, left, mid - 1, value);
        }else {
            return mid;
        }
    }
}

4)斐波那契查找

1.介绍

2.代码

public class FibonacciSearch {
    public static int maxSize = 20;
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println("index=" + fibSearch(arr, 5));
    }
    // 因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
    // 非递归方法得到一个斐波那契数列
    public static int[] fib() {
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }
    // 编写斐波那契查找算法
    // 使用非递归的方式编写算法
    /**
     *
     * @param a   数组
     * @param key 我们需要查找的关键码(值)
     * @return 返回对应的下标,如果没有-1
     */
    public static int fibSearch(int[] a, int key) {
        int low = 0;
        int high = a.length - 1;
        int k = 0; // 表示斐波那契分割数值的下标
        int mid = 0; // 存放mid值
        int f[] = fib(); // 获取到斐波那契数列
        // 获取到斐波那契分割数值的下标
        while (high > f[k] - 1) {
            k++;
        }
        // 因为 f[k] 值 可能大于 a 的 长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp[]
        // 不足的部分会使用0填充
        int[] temp = Arrays.copyOf(a, f[k]);
        // 实际上需求使用a数组最后的数填充 temp
        // 举例:
        // temp = {1,8, 10, 89, 1000, 1234, 0, 0} => {1,8, 10, 89, 1000, 1234, 1234,
        // 1234,}
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = a[high];
        }

        // 使用while来循环处理,找到我们的数 key
        while (low < high) { // 只要这个条件满足,就可以找
            mid = low + f[k - 1] - 1;
            if (key < temp[mid]) { // 我们应该继续向数组的前面查找(左边)
                high = mid - 1;
                // 为甚是 k--
                // 说明
                // 1. 全部元素 = 前面的元素 + 后边元素
                // 2. f[k] = f[k-1] + f[k-2]
                // 因为 前面有 f[k-1]个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3]
                // 即 在 f[k-1] 的前面继续查找 k--
                // 即下次循环 mid = f[k-1-1]-1
                k--;
            } else if (key > temp[mid]) { // 我们应该继续向数组的后面查找(右边)
                low = mid + 1;
                // 为什么是k -=2
                // 说明
                // 1. 全部元素 = 前面的元素 + 后边元素
                // 2. f[k] = f[k-1] + f[k-2]
                // 3. 因为后面我们有f[k-2] 所以可以继续拆分 f[k-1] = f[k-3] + f[k-4]
                // 4. 即在f[k-2] 的前面进行查找 k -=2
                // 5. 即下次循环 mid = f[k - 1 - 2] - 1
                k -= 2;
            } else { // 找到
                // 需要确定,返回的是哪个下标
                if (mid <= high) {
                    return mid;
                } else {
                    return high;
                }
            }
        }
        if(a[low]==key) {
            return low;
        }
        else {
            return -1;
        }
    }
}

七.哈希表

1)介绍

1.哈希表

  • 散列表(Hash table, 也叫哈希表) ,是根据关键**码值(Key value)**而直接进行访问的数据结构。

  • 它通过把关键码值映射到表中一个位置来访问记录, 以加快查找的速度。 这个映射函数叫做散列函数, 存放记录的数组叫做散列表。

  • 哈希表的核心:private EmpLinkedList[] empLinkedListArray;

  • 哈希表编程思路:

    ​ 1.先根据对象的信息将其散列,得到 hashCode
    ​ 2.根据对象的 hashCode 值,找到对应的数组下标,其实就是找到存储对象的链表
    ​ 3.在链表中进行相应的增删改查操作

2.示意图

2)应用

1.说明

  • 看一个实际需求, google 公司的一个上机题:

  • 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id, 性别, 年龄, 住址…),当输入该员工的 id 时,要求查找到该员工的所有信息

  • 要求:不使用数据库,尽量节省内存,速度越快越好 => 哈希表(散列)

2.代码

public class HashTableDemo {
    public static void main(String[] args) {
        HashTable hashTable = new HashTable(5);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.print("请输入操作(1.添加 2.查找):");
            int i = scanner.nextInt();
            if (i == 1){
                System.out.print("请输入id:");
                int id = scanner.nextInt();
                System.out.print("请输入name:");
                String name = scanner.next();
                Emp emp = new Emp(id, name);
                hashTable.add(emp);
                hashTable.show();
            }else {
                System.out.print("请输入id:");
                int id = scanner.nextInt();
                hashTable.selectEmpById(id);
            }
        }
    }
}
class HashTable{
    private EmpLinkedList[] empLinkedLists;
    public int size;

    public HashTable(int size){
        this.size = size;
        empLinkedLists = new EmpLinkedList[size];
        for (int i = 0; i < empLinkedLists.length; i++) {
            empLinkedLists[i] = new EmpLinkedList();
        }
    }
    public void add(Emp emp){
        int pos = getHashCode(emp.id);
        empLinkedLists[pos].add(emp);
    }
    public void show(){
        for (int i = 1; i <= empLinkedLists.length; i++) {
            empLinkedLists[i-1].show(i);
        }
    }
    public void selectEmpById(int id){
        int pos = getHashCode(id);
        empLinkedLists[pos].selectEmpById(id);
    }
    public int getHashCode(int id){
        return id%size;
    }
}
class EmpLinkedList{
    private Emp head;

    public void add(Emp emp){
        if (head == null){
            head = emp;
        }else {
            Emp temp = head;
            while (temp.next != null){
                temp = temp.next;
            }
            temp.next = emp;
        }
    }
    public void show(int no){
        if (head == null){
            System.out.println("空链表!!");
            return;
        }
        System.out.println(no + "号链表:" + head);
    }
    public void selectEmpById(int id){
        if (head == null){
            System.out.println("空链表!!");
            return;
        }
        Emp temp = head;
        while (temp != null){
            if (temp.id == id){
                System.out.println("id=" + temp.id + ", name=" + temp.name);
                return;
            }
            temp = temp.next;
        }
        System.out.println("没找到!!");
    }
}
class Emp{
    public int id;
    public String name;
    public Emp next;

    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", next=" + next +
                '}';
    }
}

八.树结构基础

1)二叉树介绍

1.为什么需要二叉树

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

2.树的常用术语

  • 节点
  • 根节点
  • 父节点
  • 子节点
  • 叶子节点 (没有子节点的节点)
  • 节点的权(节点值)
  • 路径(从 root 节点找到该节点的路线)
  • 子树
  • 树的高度(最大层数)
  • 森林 :多颗子树构成森林
  • 满二叉树:如果该二叉树的所有叶子节点都在最后一层, 并且结点总数= 2^n -1, n 为层数
  • 完全二叉树:如果该二叉树的所有叶子节点都在最后一层或者倒数第二层, 而且最后一层的叶子节点在左边连续, 倒数第二层的叶子节点在右边连续

3.二叉树的遍历

①前中后序遍历思路
  • 前序遍历: 先输出父节点, 再遍历左子树和右子树
  • 中序遍历: 先遍历左子树, 再输出父节点, 再遍历右子树
  • 后序遍历: 先遍历左子树, 再遍历右子树, 最后输出父节点

②代码
public class BinaryTreeDemo {
    public static void main(String[] args) {
        Node root = new Node(1, "宋江");
        Node node2 = new Node(2, "吴用");
        Node node3 = new Node(3, "卢俊义");
        Node node4 = new Node(4, "林冲");
        root.left = node2;
        root.right = node3;
        node3.right = node4;

        BinaryTree binaryTree = new BinaryTree(root);
        System.out.println("-----先序遍历------");
        binaryTree.preOrder();
        System.out.println("------中序遍历-----");
        binaryTree.inOrder();
        System.out.println("-----后序遍历------");
        binaryTree.postOrder();
        System.out.println("-----先序查找------");
        System.out.println(binaryTree.preOrderSearch(4));
        System.out.println("-----删除------");
        binaryTree.delete(2);
        binaryTree.preOrder();
    }
}
class BinaryTree{
    public Node root;
    public BinaryTree(Node root){
        this.root = root;
    }

    public void preOrder(){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("二叉树为空!!!");
        }
    }
    public void inOrder(){
        if (root != null){
            root.inOrder();
        }else {
            System.out.println("二叉树为空!!!");
        }
    }
    public void postOrder(){
        if (root != null){
            root.postOrder();
        }else {
            System.out.println("二叉树为空!!!");
        }
    }
    public Node preOrderSearch(int no){
        if (root != null){
            return root.preOrderSearch(no);
        }
        return null;
    }
    public void delete(int no){
        if (root != null){
            if (root.no == no){
                root = null;
            }else {
                root.delete(no);
            }
        }else {
            System.out.println("空二叉树!!");
        }
    }
}
class Node{
    public int no;
    public String name;
    public Node left;
    public Node right;
    public Node(int no, String name){
        this.no = no;
        this.name = name;
    }
    public void preOrder(){
        System.out.println(this);
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }

    }
    public void inOrder(){
        if (this.left != null){
            this.left.inOrder();
        }
        System.out.println(this);
        if (this.right != null){
            this.right.inOrder();
        }
    }
    public void postOrder(){
        if (this.left != null){
            this.left.postOrder();
        }
        if (this.right != null){
            this.right.postOrder();
        }
        System.out.println(this);
    }
    public Node preOrderSearch(int no){
        //1.根
        if (no == this.no){
            return this;
        }
        //1.左
        Node temp = null;
        if (this.left != null){
            temp = this.left.preOrderSearch(no);
        }
        if (temp != null){
            return temp;
        }
        //3.右
        if (this.right != null){
            temp = this.right.preOrderSearch(no);
        }
        return temp;
    }
    public void delete(int no){
        if (this.left != null && this.left.no == no){
            this.left = null;
            return;
        }
        if (this.right != null && this.right.no == no){
            this.right = null;
            return;
        }
        if (this.left != null){
            this.left.delete(no);
        }
        if (this.right != null){
            this.right.delete(no);
        }
    }
    @Override
    public String toString() {
        return "Node{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

4.二叉树的查找

①思路

②代码
  • 八 - 1)- 3二叉树的遍历

5.二叉树的删除

①思路

②代码
  • 八 - 1)- 3二叉树的遍历

2)顺序存储二叉树

1.思路

2.代码

public class ArrBinTreeDemo {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
        ArrBinTree arrBinTree = new ArrBinTree(arr);
        arrBinTree.preOrder(0);
    }
}
class ArrBinTree{
    private int[] arr;
    public ArrBinTree(int[] arr){
        this.arr = arr;
    }
    public void preOrder(int rootIndex){
        if (arr == null || arr.length == 0){
            System.out.println("二叉树为空!!!");
        }
        System.out.println(arr[rootIndex]);
        if (rootIndex*2+1 < arr.length){
            preOrder(rootIndex*2+1);
        }
        if (rootIndex*2+2 < arr.length){
            preOrder(rootIndex*2+2);
        }
    }

}

3)线索二叉树

1.介绍
  • n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。 利用二叉链表中的空指针域, 存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")

2.代码
public class ThreadBinTreeDemo {
    public static void main(String[] args) {
        ThreadNode root = new ThreadNode(1, "tom");
        ThreadNode node2 = new ThreadNode(3, "jack");
        ThreadNode node3 = new ThreadNode(6, "smith");
        ThreadNode node4 = new ThreadNode(8, "mary");
        ThreadNode node5 = new ThreadNode(10, "king");
        ThreadNode node6 = new ThreadNode(14, "dim");
        root.left = node2;
        root.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        // 中序线索化
        ThreadBinTree threadedBinaryTree = new ThreadBinTree(root);
        threadedBinaryTree.inOrderThread(root);
        // 测试
        ThreadNode leftNode = node5.left;
        ThreadNode rightNode = node5.right;
        System.out.println("10号结点的前驱结点是 " + leftNode); // 3
        System.out.println("10号结点的后继结点是 " + rightNode); // 1
        //中序遍历
        threadedBinaryTree.inOrderResult(root); // 8, 3, 10, 1, 14, 6
    }
}
class ThreadBinTree{
    public ThreadNode root;
    public ThreadNode pre;  //前驱指针
    public ThreadBinTree(ThreadNode root){
        this.root = root;
        this.pre = null;
    }
    public void inOrderResult(ThreadNode root){
        ThreadNode temp = root;
        while (temp !=null){
            while (temp.leftThread != 1){
                temp = temp.left;
            }
            System.out.println(temp);
            while (temp.rightThread == 1){
                temp = temp.right;
                System.out.println(temp);
            }
            temp = temp.right;
        }


    }
    public void inOrderThread(ThreadNode node){
        if (node == null){
            return;
        }
        inOrderThread(node.left);
        if (node.left == null){
            node.left = pre;
            node.leftThread = 1;
        }
        if (pre != null && pre.right == null){
            pre.right = node;
            pre.rightThread = 1;
        }
        pre = node;
        inOrderThread(node.right);
    }
}
class ThreadNode{
    public int no;
    public String name;
    public ThreadNode left;
    public ThreadNode right;
    public int leftThread;     //0左子树,1前驱
    public int rightThread;    //0右子树,1后继


    public ThreadNode(int no, String name){
        this.no = no;
        this.name = name;
        this.leftThread = 0;
        this.rightThread = 0;
    }

    @Override
    public String toString() {
        return "ThreadNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", leftThread=" + leftThread +
                ", rightThread=" + rightThread +
                '}';
    }
}

九.树结构应用

1) 堆排序

1.说明

  • ①首先构造初始堆,将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆),假设原始数组为 [4, 6, 8, 5, 9]

  • ②此时我们从最后一个非叶子结点开始(叶子结点自然不用调整,第一个非叶子结点arr.length/2-1=5/2-1=1,也就是下面的 6 结点),从左至右,从下至上进行调整。

  • ③找到第二个非叶节点 4,由于[4,9,8]中 9 元素最大,4 和 9 交换

  • ④这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中 6 最大,交换 4 和 6

  • ⑤将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换,步骤如下:
  • ⑥将堆顶元素 9 和末尾元素 4 进行交换

  • ⑦照着之前的方法重新调整结构:将栈顶元素 4 与节点 8 互换,使其继续满足堆定义

  • ⑧再将堆顶元素 8 与末尾元素 5 进行交换,得到第二大元素 8

  • ⑨后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

2.代码

public class HeapSortDemo {
    public static void main(String[] args) {
        int arr[] = {4, 6, 8, 5, 9, 5325, 46, 646};
        heapSort(arr);
        System.out.println("排序后=" + Arrays.toString(arr));
    }
    public static void heapSort(int[] arr){
        for (int i = arr.length/2 - 1; i >= 0 ; i--) {
            adjustHeap(arr, i, arr.length);
        }
        int temp = 0;
        for (int j = arr.length - 1; j > 0 ; j--) {
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            adjustHeap(arr, 0, j);
        }
    }
    public static void adjustHeap(int[] arr, int index, int len){
        int temp = arr[index];
        for (int i = index * 2 + 1; i < len; i = index * 2 + 1) {
            if (i+1 < len && arr[i] < arr[i+1]){
                i++;
            }
            if (arr[i] > temp){
                arr[index] = arr[i];
                index = i;
            }else {
                break;
            }
        }
        arr[index] = temp;
    }
}

2) 赫夫曼树

1.说明

2.代码

3)赫夫曼编码

1.
2.代码

4)二叉排序树

1.说明
  • 二叉排序树:BST(Binary Sort(Search) Tree) ,对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大

删除:

  • 第一种情况:待删除的节点为叶子节点,直接删除该叶子节点即可

    • 怎么样才算是叶子节点?targetNode.left == null && targetNode.right == null:左右节点都为空
    • 怎么删除?
      • 如果 parentNode.left != null && parentNode.left.value == value:即待删除的节点是 parentNode 的左子节点,则删除 parentNode 的左节点:parentNode.left = null;
      • 如果 parentNode.right!= null && parentNode.right.value == value:即待删除的节点是 parentNode 的右子节点,则删除 parentNode 的右节点:parentNode.right= null;
  • 第二种情况:待删除的节点只有一颗子树,直接将其子树接在 parentNode 左边或右边即可

    • 怎么判断节点只有一颗子树?targetNode.left 和 targetNode.right 中有且仅有一个为 null

    • 怎么删除?四种情况

      • 如果 targetNode 只有左子结点,则证明子树挂在 targetNode 的左边,现在来看看 target 挂在 parentNode 的哪边?
        • 如果 target 挂在 parentNode 的左边,直接将 target 的子树挂在 parentNode 的左边:parentNode.left = target.left
        • 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.left
      • 如果 targetNode 只有右子结点,则证明子树挂在 targetNode 的右边,现在来看看 target 挂在 parentNode 的哪边?
        • 如果 target 挂在 parentNode 的左边,直接将 target 的子树挂在 parentNode 的左边:parentNode.left = target.right
        • 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.right
    • 以上逻辑有个 Bug ~~~ 当待删除的节点为根节点时 , parentNode == null,这时候我们直接用根节点 root 来操作即可

  • 第三种情况:待删除的节点具有两棵颗子树

    • 从 targetNode 的左子树种找到值最大的节点(一直往右遍历),或者从从 targetNode 的右树种找到值最小的节点(一直往左遍历),假设最小值为 temp ,最小值所在的节点为 minNode
    • 此时 minNode 肯定为叶子节点,删除 minNode 节点
    • 将 targetNode.value 设置为 temp ,这样以 targetNode 根节点的子树又是一棵二叉排序树
2.代码(未完成)
public class BinSortTreeDemo {
    public static void main(String[] args) {
        int[] arr = { 7, 3, 10, 12, 5, 1, 9, 2 };
        BinSortTree binSortTree = new BinSortTree();
        for (int i = 0; i < arr.length; i++) {
            binSortTree.add(new Node(arr[i]));
        }
        binSortTree.inOrder();
    }
}
class BinSortTree{
    Node root;
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }
    public void inOrder(){
        if (root == null){
            System.out.println("空二叉树!");
        }else {
            root.inOrder();
        }
    }
}
class Node{
    int value;
    Node left;
    Node right;
    public Node(int value){
        this.value = value;
    }
    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }
    public void add(Node node){
        if (node == null){
            return;
        }
        if (node.value < this.value){
            if (this.left == null){
                this.left = node;
            }else {
                this.left.add(node);
            }
        }else {
            if (this.right == null){
                this.right = node;
            }else {
                this.right.add(node);
            }
        }
    }
    public void inOrder(){
        if (this.left != null){
            this.left.inOrder();
        }
        System.out.println(this);
        if (this.right != null){
            this.right.inOrder();
        }
    }
}

5)平衡二叉树

1.说明
  • 左旋转

  • 右旋转

  • 双旋转

2.代码
public class AVLTreeDemo {
    public static void main(String[] args) {

        int[] arr = { 10, 11, 7, 6, 8, 9 };
        AVLTree avlTree = new AVLTree();
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node1(arr[i]));
        }
        avlTree.inOrder();
    }
}
class AVLTree{
    public Node1 root;
    public void add(Node1 node){
        if (this.root == null){
            this.root = node;
        }else {
            root.add(node);
        }
    }
    public void inOrder(){
        if (root == null){
            System.out.println("isNull");
        }else {
            root.inOrder();
        }
    }
}
class Node1 {
    int value;
    Node1 left;
    Node1 right;
    public Node1(int value) {
        this.value = value;
    }
    public int height(){
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }
    public int leftHeight(){
        if (left == null){
            return 0;
        }
        return left.height();
    }
    public int rightHeight(){
        if (right == null){
            return 0;
        }
        return right.height();
    }
    public void leftRotate(){
        Node1 temp = new Node1(value);
        temp.left = left;
        temp.right = right.left;
        value = right.value;
        left = temp;
        right = right.right;
    }
    public void rightRotate(){
        Node1 temp = new Node1(value);
        temp.left = left.right;
        temp.right = right;
        value = left.value;
        left = left.left;
        right = temp;
    }
    public void add(Node1 node){
        if (node == null){
            return;
        }
        if (this.value < node.value){
            if (this.right == null){
                this.right = node;
            }else {
                this.right.add(node);
            }
        }else {
            if (this.left == null){
                this.left = node;
            }else {
                this.left.add(node);
            }
        }
        if(rightHeight() - leftHeight() > 1){
            if (right != null && right.leftHeight() > right.rightHeight()){
                right.rightRotate();
                leftRotate();
            }else {
                leftRotate();
            }
            return;
        }
        if (leftHeight() - rightHeight() > 1){
            if (left != null && left.leftHeight() < left.rightHeight()){
                left.leftRotate();
                rightRotate();
            }else {
                rightRotate();
            }
        }
    }
    // 中序遍历
    public void inOrder() {
        if (this.left != null) {
            this.left.inOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.inOrder();
        }
    }
    @Override
    public String toString() {
        return "Node1{" +
                "value=" + value +
                '}';
    }
}

十.多路查找树

1)介绍

1.二叉树问题

  • 问题1:在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响
  • 问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度

2.多叉树介绍

3.B树

  • B树通过重新组织节点,降低树的高度,并且减少i/o读写次数来提升效率
  • 如图B树通过重新组织节点, 降低了树的高度。
  • 文件系统及数据库系统的设计者利用了磁盘预读原理将一个节点的大小设为等于一个页(页的大小通常为4k),这样每个节点只需要一次I/O就可以完全载入
  • 将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素,B树(B+)广泛应用于文件存储系统以及数据库系统中

2)2-3树

1.特点

  • 2-3树的所有叶子节点都在同一层(只要是B树都满足这个条件)
  • 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点
  • 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点

2.插入

3)B树

1.说明

  • B树的阶(度):节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4

  • B树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点

  • 关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据

  • 搜索有可能在非叶子结点结束

  • 其搜索性能等价于在关键字全集内做一次二分查找

2.图解

3)B+树

1.说明

  • B+树的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找

  • 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。

  • 不可能在非叶子结点命中

  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层

  • B+树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录

  • B+树更适合文件索引系统,B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然

2.图解

3)B*树

1.介绍

  • B* 树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。
  • B* 树的说明:
    • B* 树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而B+树的块的最低使用率为B+树的1/2。
    • 从第1个特点我们可以看出,B* 树分配新结点的概率比B+树要低,空间使用率更高

2.图解

十一.图

1)

1.

2.

2)

1.

2.

十二.常见算法

1)

1.

2.

2)

1.

2.

NTQwMzgyLnBuZw?x-oss-process=image/format,png)

3)B树

1.说明

  • B树的阶(度):节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4

  • B树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点

  • 关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据

  • 搜索有可能在非叶子结点结束

  • 其搜索性能等价于在关键字全集内做一次二分查找

2.图解

3)B+树

1.说明

  • B+树的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找

  • 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。

  • 不可能在非叶子结点命中

  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层

  • B+树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录

  • B+树更适合文件索引系统,B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然

2.图解

3)B*树

1.介绍

  • B* 树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。
  • B* 树的说明:
    • B* 树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而B+树的块的最低使用率为B+树的1/2。
    • 从第1个特点我们可以看出,B* 树分配新结点的概率比B+树要低,空间使用率更高

2.图解

十一.图

1)

1.

2.

2)

1.

2.

十二.常见算法

1)

1.

2.

2)

1.

2.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值