数据结构与算法

数据结构

线性结构:

  1. 数据元素之间一对一的线性关系
  2. 可以分为顺序存储结构和链式存储结构
  3. 顺序存储的线性表称为顺序表 存储元素地址连续
  4. 链式存储称为链表 存储元素不一定是连续的 节点存放元素及相邻元素地址信息
  5. 包括数组、队列、链表和栈

非线性结构:
包括二位数组、多维数组、表、树和图

稀疏数组

当一个数组大部分元素为0或同一个值 用稀疏数组保存该数组
| row | col | val
0 | 11 | 11 | 2
1 | 1 | 2 | 1
2 | |2 | 3 | 2
二位数组 转 稀疏矩阵:

  • 遍历二维数组,得到有效数据个数sum
  • 根据sum创建细稀疏数组 int[sum+1][3]
  • 将二维数组的有效数据存入系数矩阵
    稀疏数组 转 二维数组:
  • L先读取稀疏数组的第一行 根据第一行的数据创建二维数组 Arr = int [11] [11]
  • 再读取后几行的数据,并赋值给原始的二维数组

队列

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

数组模拟队列:

  1. front为队列首元素的前一位,rear为队列最后一位有效元素下标
  2. front == rear 队列为空 rear == Size-1 队列为满
public class ArrayQueue{
    private int Size;
    private int front,rear;
    private int[] arr;

    public ArrayQueue(int Size){
        front = -1;
        rear = -1;
        this.Size = Size;
        this.arr = new int [Size];
    }

    public boolean isEmpty(){ return rear == front;}
    public boolean isFull(){return rear == Size - 1;}

    public void add(int n){
        if(isFull()){
            System.out.println("内存已满,添加请求失败");
            return;
        }
        rear++;
        arr[rear] = n;
        System.out.println("数据"+n+"添加成功!");
    }
    public void dele(){
        if(isEmpty()){
            throw new RuntimeException("内存已空,删除请求失败!");
        }
        arr[front+1] = 0;
        front++;
        System.out.println("数据删除成功!");
    }
    public void showQueue(){
        if(isEmpty()){
            System.out.println("空队列,无数据!");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            if(arr[i]!=0){
                System.out.println("arr["+i+"]"+arr[i]);
            }
        }
    }
    public int headQueue(){
        if(isEmpty()){
            throw new RuntimeException("空队列,无数据!");
        }
        return  arr[front+1];
    }

    public static void main(String[] args) {
        CircleArray arr = new CircleArray(5);
        arr.add(1);
        arr.add(2);
        arr.add(2);
        arr.add(2);
        arr.showQueue();
    }
}

数组模拟环形队列:

  1. Lfront指向队列的第一个元素下标,rear指向最后一个元素的后一地址(判断为空或是满列)
  2. (rear + 1)%Size == front -----满列
  3. rear == front ------空
  4. 有效元素个数------(rear + Size - front)%Size
import javafx.scene.shape.Circle;

public class CircleArray{
    private int Size;
    private int front,rear;
    private int[] arr;

    public CircleArray(int Size){
        front = 0;
        rear = 0;
        this.Size = Size;
        this.arr = new int [Size];
    }

    public boolean isEmpty(){return rear == front;}
    public boolean isFull(){ return (rear+1)%Size == front;}

    public void add(int n){
        if(isFull()){
            System.out.println("内存已满,添加请求失败");
            return;
        }
        arr[rear] = n;
        rear = (rear + 1)%Size;
        System.out.println("数据"+n+"添加成功!");
    }
    public void dele(){
        if(isEmpty()){
            throw new RuntimeException("内存已空,删除请求失败!");
        }
        arr[front+1] = 0;
        front = (front+1)%Size;
        System.out.println("数据删除成功!");
    }
    public void showQueue(){
        if(isEmpty()){
            System.out.println("空队列,无数据!");
            return;
        }
        for (int i = front; i < ((rear + Size - front)%Size); i++) {
                System.out.println("arr["+i%Size+"]"+arr[i%Size]);
        }
    }
    public int headQueue(){
        if(isEmpty()){
            throw new RuntimeException("空队列,无数据!");
        }
        return  arr[front+1];
    }

    public static void main(String[] args) {
        CircleArray arr = new CircleArray(5);
        arr.add(1);
        arr.add(2);
        arr.add(2);
        arr.add(2);
        System.out.println("首元素为:" + arr.headQueue());
    }
}

链表 Linked List

单向链表

  1. 链表是以节点的方式来存储
  2. 每个节点包含data,next. next指向下一个节点
  3. 各个节点不一定是连续存储

带head的单链表模拟水浒排行榜 删除-查找-修改

//一个HeroNode就是一个节点
class HeroNode {
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;

    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }
    //重写toString
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

class SingleLinkedList {
    private HeroNode head = new HeroNode(0, "", "");
    //尾插
    public void add(HeroNode heroNode) {
        HeroNode temp = head;
        //遍历 找到最后的节点
        while (true) {
            if (temp.next == null) {
                break;
            } else {
                temp = temp.next;
            }
        }
        temp.next = heroNode;
    }
    //按序号添加
    public void addByOrder(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = true;
        while(true){
            if(temp.next == null){
                break;
            }
            if(temp.next.no > heroNode.no){
                break;
            }else if(temp.next.no == heroNode.no){
                flag = false;
                break;
            }else{
                temp = temp.next;
            }
        }
        if(flag){
            heroNode.next = temp.next;
            temp.next = heroNode;
        }else{
            System.out.println("编号"+heroNode.no+"存在,加入失败!!");
        }
    }
    //根据编号no修改信息
    public void update(HeroNode heroNode){
        HeroNode temp = head;
        boolean flag = false;
        if(head.next == null){
            System.out.println("链表为空");
        }
        while(true){
            if(temp.next==null){
                break;
            }
            if(temp.next.no == heroNode.no){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if(flag){
            temp.next.name = heroNode.name;
            temp.next.nickName = heroNode.nickName;
        }else{
            System.out.println("未找到指定编号,修改失败!!!");
        }
    }
    //根据编号删除节点
    public void dele(int num){
        boolean flag = false;
        HeroNode temp = head;
        if (head.next == null){
            System.out.println("链表为空");
        }
        while(true){
            if(temp.next==null){
                break;
            }
            if(temp.next.no == num){
                flag = true;
                break;
            }else{
                temp = temp.next;
            }
        }
        if(flag){
            temp.next = temp.next.next;
        }
    }
    //显示链表
    public void show() {
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        HeroNode temp = head.next;
        while (true) {
            if (temp == null) {
                System.out.println("链表遍历完成!");
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
    public static void main(String[] args) {
        HeroNode heroNode1 = new HeroNode(1, "武松", "行者");
        HeroNode heroNode2 = new HeroNode(2, "宋江", "及时雨");
        HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
        HeroNode heroNode4 = new HeroNode(1, "cc", "帅哥");

        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(heroNode1);
        list.addByOrder(heroNode3);
        list.addByOrder(heroNode2);
        list.addByOrder(heroNode2);
        list.show();
        list.update(heroNode4);
        list.show();
        list.dele(2);
        list.show();
    }
}

练习:

  1. 单链表有效节点的个数
    遍历所有节点head.next != null,index++
  2. 单链表中倒数第k个节点
    遍历的到有效节点个数n,遍历到第n-k个节点
  3. 单链表的反转:头插 新建一个链表的头结点head 遍历旧链表 每遍历一个节点 加入到新链表的head.next
public class ListNode {
      int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
      @Override
      public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
      }
}
class Solution {
      public static void main(String[] args) {

        ListNode head = new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,newListNode(5,null)))));

        if(head == null || head.next == null){
                  System.out.println(-1);
            }

        ListNode cur = head;
        ListNode reverseList = new ListNode(0,null);

        while(true){
                ListNode next = cur.next;
                ListNode temp = reverseList.next;
                if(next == null){
                    cur.next = temp;
                    reverseList.next = cur;
                    break;
                }else{
                    cur.next = temp;
                    reverseList.next = cur;
                }
                cur = next;
            }
            ListNode res = reverList.next; 
            System.out.println(reverseList);
      }
}
  1. 从尾到头,打印单链表 方式1 链表反转再打印(破坏单链表的结构) 方式2 利用栈的结构 将各个节点压入栈 先入后出
public class Stack{
	public static void main(String[] args){
		Stack <String> stack = new Stack();
 		//入栈
		stack.add("jack");
		stack.add("tom");
		stack.add("smith");
		//出栈
		while(stack.size()>0){
			System.out.println(stack.pop());
		}
	}
}
  1. 合并两个有序链表 递归 对比两个链表头 返回头等于或者较小的链 不断递归
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        if(l1.val < l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}
约瑟夫环
  1. 1,2,…n个人围一圈,编号为k的人从1开始报数,数到m的人出列;下一位又从1报数,数到m的人出列;知道所有人出列为止,产生一个出编号的序列。
  2. 数组取模或者用环形列表

(1)创建一个单向环形列表

class CircleSingleLinedList {
      //第一个first节点,当前没有编号
      private Boy first = new Boy(-1);
      //辅助指针cur
      Boy cur = null;
      //添加节点,构建环形链表
    public void addBoy(int nums){
        if(nums<1){
            System.out.println("输入的值不正确,请重新输入!!!");
            return;
        }
        for (int i = 0; i < nums; i++) {
            if(first.getNext()==null){
                Boy boy = new Boy(1);
                first = boy;
                first.setNext(first);
                cur = boy;
            }else {
                Boy boy = new Boy(cur.getNo()+1);
                cur.setNext(boy);
                boy.setNext(first);
                cur = boy;
            }
        }
    }

    public void showBoy(){
        //判断是否为空
        if(first==null){
            System.out.println("链表没有任何值!!");
            return;
        }
        Boy cur = first;
        while (true){
            System.out.println("对象编号为:"+cur.getNo());
            if(cur.getNext()==first){
                System.out.println("遍历完成!!");
                break;
            }
            cur = cur.getNext();
        }
    }
}

class Boy{
    private int no;
    private Boy next;

    public Boy(){}

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() { return no; }
    public void setNo(int no) { this.no = no; }
    public Boy getNext() { return next; }
    public void setNext(Boy next) { this.next = next; }
}

(2)按指定数出队列,生成一个出圈顺序,n=5共五个人,m=2每次第二个人出圈

  • cur指向最后一个节点,first指向第一个节点
  • 小孩报数时,让first和cur同时移动m-1次
  • first = first.next cur.next = first
 //从第k个小孩开始数 count第几个出圈
    public void countBoy(int k ,int count){
        if(first==null){
            System.out.println("没有对象,请输入!!");
        }
        //确保cur指向最后节点
        while(true){
            if(cur.getNext()==first){
                break;
            }else{
                cur = cur.getNext();
            }
        }
        //报数前 让first和cur移动k-1次
        for (int i = 0; i < k-1; i++) {
            first=first.getNext();
            cur=cur.getNext();
        }
        //循环直到圈里只有一个人
        while (true){
            if(first==cur){
                System.out.println("剩余最后的编号为:"+first.getNo());
                break;
            }
            for (int i = 0; i < count-1; i++) {
                first=first.getNext();
                cur=cur.getNext();
            }
            System.out.println("对象"+first.getNo()+"出队列!!");
            first = first.getNext();
            cur.setNext(first);
        }
    }

(3)测试

class Solution {
      public static void main(String[] args) {
          CircleSingleLinedList list = new CircleSingleLinedList();
          list.addBoy(5);
          list.showBoy();

          list.countBoy(1,3);
      }
}

双向链表

  1. 遍历与单向链表一样,只是可以向前也可以向后查找
  2. 添加 默认添加到链表最后 cun.next = new new.pre = temp都要添加
  3. 修改 与单向链表一样
  4. 删除 可以实现删除某个自我节点 cur.pre.next = cur.next cur.next.pre = cur.pre

单向环形链表

  1. 创建第一个节点first,并形成环形
  2. 创建新的节点时,用临时节点cur指向它,将其加入到环形当中
  3. 用while循环遍历,直到cur.next==first结束

哈希表

  • 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
    在这里插入图片描述

模拟一个哈希表

  • 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工的所有信息.
    要求:
    不使用数据库,速度越快越好=>哈希表(散列)
    添加时,保证按照id从低到高插入 [课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?
  1. 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
  2. 思路分析并画出示意图
  3. 代码实现[增删改查(显示所有员工,按id查询)]
import java.util.Scanner;

public class HashTable {
    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("show: 显示成员!");
            System.out.println("search: 查找职员!");
            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 "search":
                    System.out.println("请输入id:");
                    id = scanner.nextInt();
                    hashTab.search(id);
                    break;
                case "show":
                    hashTab.show();
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                    break;
                default:
                    break;
            }
        }
    }
}

//创建HashTbale 管理多条链表
class HashTab{
    private int size;
    private EmpLinkedList[] empLinkedListsArr;

    //构造器
    public HashTab(int size) {
        this.size = size;
        empLinkedListsArr = new EmpLinkedList[size];
        for (int i = 0; i < size; i++) {
            empLinkedListsArr[i] = new EmpLinkedList();
        }
    }
    //添加雇员
    public void add(Emp emp){
        //根据id得到对象的存放地址
        int emLinkedListNo = hash(emp.id);
        //添加雇员到对应链表中
        empLinkedListsArr[emLinkedListNo].add(emp);
    }
    //遍历
    public void show(){
        for (int i = 1; i <= size; i++) {
            empLinkedListsArr[i-1].show(i);
        }
    }
    //散列函数(哈希)
    public int hash(int id){
        return id % size;
    }
    //查找
    public void search(int id){
        int tem = hash(id);
        Emp emp = empLinkedListsArr[tem].search(id);
        if(emp==null){
            System.out.println("未找到该对象!");
        }else {
            System.out.println("在第"+tem+"条链表找到:"+emp.toString());
        }
    }



}


//职员类
class Emp{
    public int id;
    public String name;
    public Emp next;

    public Emp() {
    }

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

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

//链表类
class EmpLinkedList{
    private Emp head;

    //添加雇员 默认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;
        }
        curEmp.next = emp;
    }

    //遍历链表
    public  void show(int no){
        if(head==null){
            System.out.println("第"+no+"链表为空!!");
            return;
        }
        System.out.print("第"+no+"链表信息为:");
        Emp curEmp = head;
        while (true){
            System.out.print(curEmp.toString()+"\t");
            if (curEmp.next==null){
                break;
            }
            curEmp = curEmp.next;
        }
        System.out.println();
    }
    //查找雇员
    public Emp search(int id){
        if(head==null){
            return null;
        }
        Emp curEmp = head;
        while(true){
            if (curEmp.id==id) {
                break;
            }
            if (curEmp.next==null){
                curEmp = null;
            }
            curEmp = curEmp.next;
        }
        return curEmp;
    }
}

二叉树

  • 前序 先输出父节点,再遍历左子树和右子树
  • 中序 先遍历左子树,再输出父节点,再遍历右子树
  • 后续 先遍历左子树,再遍历右子树,最后输出父节点
    小结: 看输出父节点的顺序,就确定是前序,中序还是后序
    根左右 1245367
    左根右 4251637
    左右根 4526731
    在这里插入图片描述
public class BinaryTree {
    public static void main(String[] args) {
        Tree tree = new Tree();
        Node node1 = new Node(1,"1");
        Node node2 = new Node(2,"2");
        Node node3 = new Node(3,"3");
        Node node4 = new Node(4,"4");

        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);

        tree.setRoot(node1);
        System.out.println("前序遍历");
        tree.preOrder();
        System.out.println("中序遍历");
        tree.midOrder();
        System.out.println("后序遍历");
        tree.postOrder();


    }
}

class Tree{
    private Node root;

    public void setRoot(Node root){
        this.root = root;
    }

    //前序遍历
    public void preOrder(){
        if (this.root!=null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
    //前序遍历
    public void midOrder(){
        if (this.root!=null){
            this.root.midOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
    //后序遍历
    public void postOrder(){
        if (this.root!=null){
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
}


//创建建Node节点
class Node{
    private int no;
    private String name;
    private Node left;
    private Node right;

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

    public int getNo() { return no; }
    public void setNo(int no) { this.no = no; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Node getLeft() { return left; }
    public void setLeft(Node left) { this.left = left; }
    public Node getRight() { return right; }
    public void setRight(Node right) { this.right = right; }

    @Override
    public String toString() {
        return "Node{" + "no=" + no + ", 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 midOrder() {
        if (this.left!=null){
            this.left.midOrder();
        }
        System.out.println(this);
        if (this.right!=null){
            this.right.midOrder();
        }
    }
    //后续遍历
    public void postOrder(){
        if (this.left!=null){
            this.left.postOrder();
        }
        if (this.right!=null){
            this.right.postOrder();
        }
        System.out.println(this);
    }
}

查找

在这里插入图片描述

public class BinaryTree {
    public static void main(String[] args) {
        Tree tree = new Tree();
        Node node1 = new Node(1,"占山");
        Node node2 = new Node(2,"里斯");
        Node node3 = new Node(3,"王五");
        Node node4 = new Node(4,"青湖");
        Node node5 = new Node(5,"地藏");

        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);

        tree.setRoot(node1);
        //前序查找
        int no =5;
        Node tem = tree.preOrderSearch(no);
        if (tem!=null){
            System.out.println("前序查找结果:"+"\t"+tem.toString());
        }else {
            System.out.println("没有找到编号为"+no+"的对象!");
        }
        //中序查找
        tem = tree.midOrderSearch(no);
        if (tem!=null){
            System.out.println("中序查找结果:"+"\t"+tem.toString());
        }else {
            System.out.println("没有找到编号为"+no+"的对象!");
        }
        //后序查找
        tem = tree.postOrderSearch(no);
        if (tem!=null){
            System.out.println("后序查找结果:"+"\t"+tem.toString());
        }else {
            System.out.println("没有找到编号为"+no+"的对象!");
        }


    }
}

class Tree{
    private Node root;

    public void setRoot(Node root){
        this.root = root;
    }

    //前序遍历
    public void preOrder(){
        if (this.root!=null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
    //前序遍历
    public void midOrder(){
        if (this.root!=null){
            this.root.midOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
    //后序遍历
    public void postOrder(){
        if (this.root!=null){
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空!遍历失败!");
        }
    }
    //前序查找
    public Node preOrderSearch(int no){
        if(this.root!=null){
            return root.preOrderSearch(no);
        }
        return null;
    }
    //中序查找
    public Node midOrderSearch(int no){
        if(this.root!=null){
            return root.midOrderSearch(no);
        }
        return null;
    }
    //后序查找
    public Node postOrderSearch(int no){
        if(this.root!=null){
            return root.postOrderSearch(no);
        }
        return null;
    }
}


//创建建Node节点
class Node{
    private int no;
    private String name;
    private Node left;
    private Node right;

    public Node(){}

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

    public int getNo() { return no; }
    public void setNo(int no) { this.no = no; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Node getLeft() { return left; }
    public void setLeft(Node left) { this.left = left; }
    public Node getRight() { return right; }
    public void setRight(Node right) { this.right = right; }

    @Override
    public String toString() {
        return "Node{" + "no=" + no + ", 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 midOrder() {
        if (this.left!=null){
            this.left.midOrder();
        }
        System.out.println(this);
        if (this.right!=null){
            this.right.midOrder();
        }
    }
    //后续遍历
    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){
        //判断当前结点是否为空
        System.out.println("前序查找+1");
        if(this.getNo()==no){
            return this;
        }
        //判断左节点是否为空
        //如果不为空左递归前序查找,找到结点,则返回
        Node tem = null;
        if (this.left!=null){
            tem = this.left.preOrderSearch(no);
        }
        if (tem!=null){
            //找到就返回
            return tem;
        }
        //左遍历未找到则继续向右判断
        if (this.right!=null){
            tem = this.right.preOrderSearch(no);
        }
        return tem;

    }
    //中序查找
    public Node midOrderSearch(int no){
        //判断当前的左节点是否为空
        Node tem = null;
        if(this.left!=null){
            tem = this.left.midOrderSearch(no);
        }
        if (tem!=null){
            return tem;
        }
        //没有找到 就和当前节点进行比较 找到则返回
        System.out.println("中序查找+1");
        if (this.getNo()==no){
            return this;
        }
        //当前节点未找到 则向右递归
        if (this.right!=null){
            tem = this.right.midOrderSearch(no);
        }
        return tem;
    }
    //后序查找
    public Node postOrderSearch(int no){
        //判断当前的左节点是否为空
        Node tem = null;
        if (this.left!=null){
            tem = this.left.postOrderSearch(no);
        }
        if (tem!=null){
            return tem;
        }
        //左树未找到 则再右边进行查找
        if (this.right!=null){
            tem = this.right.postOrderSearch(no);
        }
        if (tem!=null){
            return tem;
        }
        //左右子树都未找到 则比较当前节点
        System.out.println("后序查找+1");
        if (this.getNo()==no){
            return this;
        }
        return tem;

    }


}

删除

要求:

  1. 如果删除的节点是叶子节点,则删除该节点。
  2. 如果删除的节点是非叶子节点,则删除该子树.
    思路:
    在这里插入图片描述
    class Teee
//删除
    public void del(int no){
        if(root!=null){
            if (root.getNo()==no){
                root = null;
            }else {
                root.del(no);
            }
        }else {
            System.out.println("空数 不可删除!");
        }
    }

class Node

//删除
    public void del(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.del(no);
        }
        //向右递归
        if (this.right!=null){
            this.right.del(no);
        }
    }

main

Tree tree = new Tree();
        Node node1 = new Node(1,"占山");
        Node node2 = new Node(2,"里斯");
        Node node3 = new Node(3,"王五");
        Node node4 = new Node(4,"青湖");
        Node node5 = new Node(5,"地藏");

        node1.setLeft(node2);
        node1.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);

        tree.setRoot(node1);

        tree.preOrder();
        tree.del(5);
        System.out.println("删除5号");
        tree.preOrder();
        tree.del(3);
        System.out.println("删除3号");
        tree.preOrder();

顺序存储二叉树

在这里插入图片描述
在这里插入图片描述

public class ArrBinaryTree{
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        Tree tree = new Tree(arr);
        tree.preOrder();

    }
}

class Tree{
    private int[] arr;

    public Tree(int[] arr) {
        this.arr = arr;
    }
    //重载
    public void preOrder(){
        this.preOrder(0);
    }
    //前序遍历
    public void preOrder(int index){
        //数组为空
        if (arr==null || arr.length==0){
            System.out.println("数组为空!!");
        }
        //输出这个元素
        System.out.println(arr[index]);
        //向左递归
        if (index*2+1 < arr.length){
            preOrder(2*index + 1);
        }
        if (index*2+2 < arr.length){
            preOrder(2*index + 2);
        }
    }
}

线索化二叉树

  • 我们希望充分的利用各个节点(左右不全的节点)的左右指针, 让各个节点可以指向自己的前后节点-线索化二叉树
  • 一个结点的前一个结点,称为前驱结点
  • 一个结点的后一个结点,称为后继结点
  • 线索化后 遍历方式也随之改变 各个节点通过线性方式遍历
import com.sun.media.jfxmediaimpl.HostUtils;

class ThreadedBinaryTree {
    public static void main(String[] args) {
        Node root = new Node(1,"tom");
        Node node1 = new Node(3,"jack");
        Node node2 = new Node(6,"smith");
        Node node3 = new Node(8,"mary");
        Node node4 = new Node(10,"king");
        Node node5 = new Node(14,"dim");

        root.setLeft(node1);
        root.setRight(node2);
        node1.setLeft(node3);
        node1.setRight(node4);
        node2.setLeft(node5);

        Tree tree = new Tree();
        tree.setRoot(root);
        tree.threadedNodes();

        //测试10节点
        System.out.println(node4.getLeft());
        System.out.println(node4.getRight());

        //线索化遍历
        tree.threadList();
    }
}

class Tree {
    private Node root;
    //为了实现线索化 创建指向当前节点的前驱节点的指针pre
    private Node pre = null;


    public void setRoot(Node root) {
        this.root = root;
    }

    //重载
    public void threadedNodes(){
        this.threadedNodes(root);
    }

    /**
     * 中序线索化的方法
     * @param node 需要线索化的节点
     * leftType==0 表示指向左子树 ==1表示指向前驱节点
     * rightType==0 表示指向右子树 ==1表示指向后继节点
     */
    public void threadedNodes(Node node){
        //节点为空 不能线索化
        if (node == null){
            return;
        }

        //线索化左子树
        threadedNodes(node.getLeft());

        //线索化当前节点
        //先处理当前节点的前驱节点
        if (node.getLeft() == null){
            node.setLeft(pre);
            node.setLeftType(1);
        }
        //处理后继节点
        if (pre!=null && pre.getRight()==null){
            //让前驱节点的右指针指向当前节点
            pre.setRight(node);
            //修改前驱节点的右节点类型
            pre.setRightType(1);
        }
        //每处理一个节点后 让当前节点是下一个节点的前驱节点
        pre = node;

        //线索化右子树
        threadedNodes(node.getRight());
    }

    //遍历中序线索化二叉树
    public void threadList() {
        //定义变量 存储当前遍历的节点 从root开始
        Node node = root;
        while (root != null){
            //循环找到leftType=1的节点
            while (node.getLeftType() == 0){
                node = node.getLeft();
            }
            //打印当前节点
            System.out.println(node);
            //如果当前节点的右指针指向后继节点 就一直输出
            while (node.getRightType() ==1){
                node = node.getRight();
                System.out.println(node);
            }
            node = node.getRight();
        }
    }


}


//创建建Node节点
class Node {
    private int no;
    private String name;
    private Node left;
    private Node right;
    //leftType==0 表示指向左子树 为1表示指向前驱节点
    //rightType==0 表示指向右子树 为1表示指向后继节点
    private int leftType;
    private int rightType;

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

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }

    public int getNo() {
        return no;
    }

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

    public String getName() {
        return name;
    }

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

    public Node getLeft() {
        return left;
    }

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

    public Node getRight() {
        return right;
    }

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

    @Override
    public String toString() {
        return "Node{" + "no=" + no + ", 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 midOrder() {
        if (this.left != null) {
            this.left.midOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.midOrder();
        }
    }

    //后续遍历
    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) {
        //判断当前结点是否为空
        System.out.println("前序查找+1");
        if (this.getNo() == no) {
            return this;
        }
        //判断左节点是否为空
        //如果不为空左递归前序查找,找到结点,则返回
        Node tem = null;
        if (this.left != null) {
            tem = this.left.preOrderSearch(no);
        }
        if (tem != null) {
            //找到就返回
            return tem;
        }
        //左遍历未找到则继续向右判断
        if (this.right != null) {
            tem = this.right.preOrderSearch(no);
        }
        return tem;

    }

    //中序查找
    public Node midOrderSearch(int no) {
        //判断当前的左节点是否为空
        Node tem = null;
        if (this.left != null) {
            tem = this.left.midOrderSearch(no);
        }
        if (tem != null) {
            return tem;
        }
        //没有找到 就和当前节点进行比较 找到则返回
        System.out.println("中序查找+1");
        if (this.getNo() == no) {
            return this;
        }
        //当前节点未找到 则向右递归
        if (this.right != null) {
            tem = this.right.midOrderSearch(no);
        }
        return tem;
    }

    //后序查找
    public Node postOrderSearch(int no) {
        //判断当前的左节点是否为空
        Node tem = null;
        if (this.left != null) {
            tem = this.left.postOrderSearch(no);
        }
        if (tem != null) {
            return tem;
        }
        //左树未找到 则再右边进行查找
        if (this.right != null) {
            tem = this.right.postOrderSearch(no);
        }
        if (tem != null) {
            return tem;
        }
        //左右子树都未找到 则比较当前节点
        System.out.println("后序查找+1");
        if (this.getNo() == no) {
            return this;
        }
        return tem;
    }
}


霍夫曼树

在这里插入图片描述

  • 给出一个数列{13,7,8,3,29,6,1},转成一个霍夫曼树
  1. 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
  2. 取出根节点权值最小的两颗二叉树
  3. 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
  4. 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class HeapSort {
    public static void main(String[] args) {
        int arr[] = {13,7,8,3,29,6,1};
        preOrder(HuffmanTree(arr));
    }

    //前序遍历
    public static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("空树,遍历失败!!");
        }
    }

    //创建霍夫曼树
    public static Node HuffmanTree(int[] arr){
        //遍历arr 将每个元素构建成一个Node 放入ArrayList
        List<Node> list = new ArrayList<>();
        for (int value: arr) {
            list.add(new Node(value));
        }

        while (list.size()>1) {
            //1 排序(从小到大)
            Collections.sort(list);
            //2 用权值最小的两个点 构建新的树
            Node par = new Node(list.get(0).value + list.get(1).value);
            par.left = list.get(0);
            par.right = list.get(1);
            //3 删除处理过的节点
            list.remove(0);
            list.remove(0);
            //4 加入新的树
            list.add(par);
        }
        return list.get(0);
    }
}

//创建建Node节点
//为了持续排序Collection 在Node实现Compare接口
class Node implements Comparable<Node>{
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value;
    }

    @Override
    public int compareTo(Node o) {
        return this.value - o.value;
    }

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

}
霍夫曼编码

在这里插入图片描述
在这里插入图片描述

import java.util.*;

class HeapSort {
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] bytes = content.getBytes();

        List<Node> list = getNodes(bytes);

        Node root = Huffmantree(list);
        root.preOrder();

        System.out.println("霍夫曼编码:");
        getCodes(root,"",string);
        System.out.println(huffmanCodes);

    }

     //将编码表存放为 Map<Byte,String>的形式 32—>01 0->0011
    static Map<Byte,String> huffmanCodes = new HashMap<>();
    //生成的霍夫曼编码 需要不断拼接 定义一个StringBuilder存储节点路径
    static StringBuilder string = new StringBuilder();
    //生成霍夫曼树对应的霍夫曼编码
    /**
     * 得到传入节点为node的所有叶子节点的霍夫曼编码 存放到huffmanCodes中
     * @param node 传入节点
     * @param code 路径 左子节点0 右子节点1
     * @param string 拼接路径
     */
    public static void getCodes(Node node,String code,StringBuilder string){
        StringBuilder string1 = new StringBuilder(string);
        //每个叶子节点 用一个string1保存这一路径的值
        string1.append(code);
        if (node.data == null){
            //不是叶子节点 则不断递归
            getCodes(node.left,"0",string1);
            getCodes(node.right,"1",string1);
        }else {
            //找到叶子节点 保存编码
            huffmanCodes.put(node.data,string1.toString());
        }
    }


    //前序遍历
    public static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("空树,遍历失败!!");
        }
    }
    //接受字节数组
    public static List<Node> getNodes(byte[] bytes){
        //创建list集合 存放数据和权重
        List<Node> list = new ArrayList();

        //利用map来统计
        Map<Byte,Integer> map = new HashMap<>();
        for (byte b : bytes) {
            Integer count = map.get(b);
            if (count == null){
                //map还没有这个字符
                map.put(b,1);
            }else {
                //有这个字符 则权重+1
                map.put(b,count+1);
            }
        }
        //把map的键值对转换成Node对象 存储到list中
        //map的遍历
        for (Map.Entry<Byte,Integer> entry : map.entrySet()) {
            list.add(new Node(entry.getKey(), entry.getValue()));
        }

        return list;
    }

    //通过list创建霍夫曼树
    public static Node Huffmantree(List<Node> list){

        while (list.size() > 1){
            //排序
            Collections.sort(list);
            //取出最小的两个元素
            Node par = new Node(null,list.get(0).weight+list.get(1).weight);
            par.left = list.get(0);
            par.right = list.get(1);
            //删除处理过的节点
            list.remove(0);
            list.remove(0);
            //4 加入新的树
            list.add(par);
        }
        return list.get(0);
    }
}

/**
 * data 数据本身 ‘a’‘b’
 * weight 数据权重 在句子中出现的次数
 */
class Node implements Comparable<Node>{
    Byte data;
    int weight;
    Node left;
    Node right;

    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }

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

}

二叉排序树

  • 二叉排序树:BST: (Binary Sort(Search) Tree), 又名二叉搜索树,对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大
  • 中序遍历的结果,即为从小到大排序
  • 查找 插入 删除 效率都很高

创建、遍历、删除 其中删除比较复杂 在这里插入图片描述

  • 删除叶子节点 (比如:2, 5, 9, 12)在这里插入图片描述

  • 删除只有一颗子树的节点 (比如:1)在这里插入图片描述

  • 删除有两颗子树的节点. (比如:7, 3,10 )在这里插入图片描述

import java.util.*;

class BinarySortTree {
    public static void main(String[] args) {
        int[] arr = {7,3,10,12,5,1,9};
        Tree tree = new Tree();

        //循环添加
        for (int i = 0; i < arr.length; i++) {
            tree.add(new Node(arr[i]));
        }
        tree.midOrder();

        tree.dele(10);
        System.out.println("删除:");
        tree.midOrder();
    }

    //中序遍历
    public static void midOrder(Node root){
        if (root != null){
            root.midOrder();
        }else {
            System.out.println("空树,遍历失败!!");
        }
    }
}

class Tree{
    private Node root;

    public Tree(Node root) {
        this.root = root;
    }

    public Tree() {
    }

    //添加
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    //查找要删除的节点
    public Node search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查找要删除节点的父节点
    public Node searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }

    //查找以该节点为根节点的子树最小值
    public int treeMin(Node node){
        while (node.left != null){
            node = node.left;
        }
        return node.value;
    }

    //删除
    public void dele(int value){
        if (root == null){
            return;
        }else {
            //1.先寻找要删除的节点
            Node targetNode = root.search(value);
            if (targetNode == null){
                return;
            }
            //只有一个根节点 并且是要删除的节点
            if (root.left == null && root.right == null){
                root = null;
            }

            //2.寻找要删除节点的父节点
            Node parent = root.searchParent(value);

            //3.判断要删除的是否是叶子节点
            if (targetNode.left == null && targetNode.right == null){
                //判断targetNode是左节点还是右节点
                if (parent != null && parent.left != null && parent.left.value ==value){
                    parent.left = null;
                }else {
                    parent.right = null;
                }

            //删除节点有两颗子树
            }else if (targetNode.left != null && targetNode.right !=null){
                int minValue = treeMin(targetNode.right);
                dele(minValue);
                targetNode.value = minValue;

            } else{//删除节点只有一个子树
                //只有左子树
                if (targetNode.left != null){
                    if (parent != null) {
                        //targerNode是parent的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { //targerNode是parent的右子节点
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else { //只有右子树
                    if (parent != null) {
                        //targerNode是parent的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { //targerNode是parent的右子节点
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = parent.right;
                    }
                }
            }
        }
    }

    //中序遍历
    public void midOrder(){
        if (root == null){
            System.out.println("根节点为空,遍历失败!");
        }else {
            root.midOrder();
        }
    }
}

class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    //查找要删除的节点
    public Node search(int value){
        if (this.value == value){
            return this;
        }else if (this.value > value){
            if (this.left == null){
                return null;
            }else {
                return this.left.search(value);
            }
        }else {
            if (this.right == null){
                return null;
            }else {
                return this.right.search(value);
            }
        }
    }

    //查找要删除的节点的父节点
    public Node searchParent(int value){
        if ((this.left != null && this.left.value == value) ||
                (this.right != null && this.right.value == value)){
            return this;
        }else {
            if (this.value <= value && this.right != null){
                return this.right.searchParent(value);
            }else if (this.value > value && this.left != null){
                return this.left.searchParent(value);
            }else {
                return null;
            }
        }
    }

    //添加节点
    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 midOrder() {
        if (this.left != null) {
            this.left.midOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.midOrder();
        }
    }

}

平衡二叉树(AVL)

在这里插入图片描述
在这里插入图片描述

class AVLTree {
    public static void main(String[] args) {
        int[] arr = {10,11,7,7,8,9};
        Tree tree = new Tree();

        for (int i = 0; i < arr.length; i++) {
            tree.add(new Node(arr[i]));
        }

        tree.midOrder();
        System.out.println("平衡前:");
        System.out.println("高度为:" + tree.root.height());
        System.out.println("左树高度:"+tree.root.leftHeight());
        System.out.println("右树高度:"+tree.root.rightHeight());
    }

}

class Tree{
    public Node root;

    public Tree(Node root) {
        this.root = root;
    }

    public Tree() {
    }


    //添加
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    //查找要删除的节点
    public Node search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }
    //查找要删除节点的父节点
    public Node searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }

    //查找以该节点为根节点的子树最小值
    public int treeMin(Node node){
        while (node.left != null){
            node = node.left;
        }
        return node.value;
    }

    //删除
    public void dele(int value){
        if (root == null){
            return;
        }else {
            //1.先寻找要删除的节点
            Node targetNode = root.search(value);
            if (targetNode == null){
                return;
            }
            //只有一个根节点 并且是要删除的节点
            if (root.left == null && root.right == null){
                root = null;
            }

            //2.寻找要删除节点的父节点
            Node parent = root.searchParent(value);

            //3.判断要删除的是否是叶子节点
            if (targetNode.left == null && targetNode.right == null){
                //判断targetNode是左节点还是右节点
                if (parent != null && parent.left != null && parent.left.value ==value){
                    parent.left = null;
                }else {
                    parent.right = null;
                }

            //删除节点有两颗子树
            }else if (targetNode.left != null && targetNode.right !=null){
                int minValue = treeMin(targetNode.right);
                dele(minValue);
                targetNode.value = minValue;

            } else{//删除节点只有一个子树
                //只有左子树
                if (targetNode.left != null){
                    if (parent != null) {
                        //targerNode是parent的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { //targerNode是parent的右子节点
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else { //只有右子树
                    if (parent != null) {
                        //targerNode是parent的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { //targerNode是parent的右子节点
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = parent.right;
                    }
                }
            }
        }
    }

    //中序遍历
    public void midOrder(){
        if (root == null){
            System.out.println("根节点为空,遍历失败!");
        }else {
            root.midOrder();
        }
    }
}

class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    //左旋方法
    private void leftRotate(){
        //创建新节点 以当前根节点的值
        Node newNode = new Node(value);
        //把新的节点的左子树 设置为当前节点的左子树
        newNode.left = this.left;
        //把新节点的右子树 设置为当前节点的右子树的左子树
        newNode.right = this.right.left;
        //把当前节点的值替换成右子节点的值
        value = right.value;
        //把当前节点的右子树 设置为当前节点右子树的右子树
        this.right = this.right.right;
        //把当前节点的左子树 设置为新的节点
        this.left = newNode;
    }

    //右旋方法
    private void rightRotate(){
        Node newNode = new Node(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

    //返回当前节点左子树高度
    public int leftHeight(){
        if (this.left == null){
            return 0;
        }else {
            return this.left.height();
        }
    }
    //返回当前节点右子树高度
    public int rightHeight(){
        if (this.right == null){
            return 0;
        }else {
            return this.right.height();
        }
    }
    //返回以当前节点为根节点的树高度
    public int height(){
        return Math.max(this.left == null ? 0 : this.left.height(),
                this.right == null ? 0 : this.right.height()) + 1;
    }


    //查找要删除的节点
    public Node search(int value){
        if (this.value == value){
            return this;
        }else if (this.value > value){
            if (this.left == null){
                return null;
            }else {
                return this.left.search(value);
            }
        }else {
            if (this.right == null){
                return null;
            }else {
                return this.right.search(value);
            }
        }
    }

    //查找要删除的节点的父节点
    public Node searchParent(int value){
        if ((this.left != null && this.left.value == value) ||
                (this.right != null && this.right.value == value)){
            return this;
        }else {
            if (this.value <= value && this.right != null){
                return this.right.searchParent(value);
            }else if (this.value > value && this.left != null){
                return this.left.searchParent(value);
            }else {
                return null;
            }
        }
    }

    //添加节点
    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);
            }
        }
        //左旋
        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.rightHeight() > left.leftHeight()){
                //左子树的 右子树的高度大于其左子树高度 先左旋
                left.leftRotate();
                //再右旋
                rightRotate();
            }else {
                //直接进行右旋
                rightRotate();
            }
        }
    }

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

}

多叉树和B树

在这里插入图片描述
在这里插入图片描述

  • B-tree树即B树,B即Balanced,平衡的意思,也符合二叉排序树的特性。有人把B-tree翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-tree就是指的B树。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建图解

在这里插入图片描述

  • 图的遍历
  • 深度优先搜索(Depth First Search) --DFS
  • 每次都在访问完当前结点后首先访问当前结点的第一个邻接结点
  1. 访问初始结点v,并标记结点v为已访问。
  2. 查找结点v的第一个邻接结点w。
  3. 若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
  4. 若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
  5. 查找结点v的w邻接结点的下一个邻接结点,转到步骤3。
  • 广度优先搜索(Broad First Search)–BFS
  • 类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
  1. 访问初始结点v并标记结点v为已访问。
  2. 结点v入队列
  3. 当队列非空时,继续执行,否则算法结束。
  4. 出队列,取得队头结点u。
  5. 查找结点u的第一个邻接结点w。
  6. 若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:
    6.1 若结点w尚未被访问,则访问结点w并标记为已访问。
    6.2 结点w入队列
    6.3 查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

class Graph{
    private ArrayList<String> vertexList;//顶点集合
    private int[][] edges;//图相应矩阵
    private int numOfEdges;//边的数目
    private boolean[] isVisited;//标记某个节点是否被访问

    public static void main(String[] args) {
        int n = 5;
        String vertexValue[] = {"A","B","C","D","E"};
        //创建图对象
        Graph graph = new Graph(n);
        //添加顶点
        for (String vertex : vertexValue){
            graph.insertVertex(vertex);
        }
        //添加边  A-B A-C B-C B-D B-E
        graph.insertEdge(0,1,1);
        graph.insertEdge(0,2,1);
        graph.insertEdge(1,2,1);
        graph.insertEdge(1,3,1);
        graph.insertEdge(1,4,1);

        graph.show();

        //dfs深度遍历
        System.out.println("深度优先!");
        graph.dfs();
        System.out.println("");

        //bfs广度优先
        System.out.println("广度优先!");
        graph.bfs();
    }



    //构造器
    public Graph(int n) {
        //初始化矩阵和vertexList
        edges = new int[n][n];
        vertexList = new ArrayList<String>(n);
    }

    //深度优先遍历
    public void dfs(int i,boolean[] isVisited){
        //首先输出该节点
        System.out.print(getValueByIndex(i) + "->");
        isVisited[i] = true;
        //查找第一个临接节点
        int w = getFirstNeighbor(i);
        while (w != -1){//找到第一个临接节点
            if (!isVisited[w]){
                dfs(w,isVisited);
            }//若第一个节点已被访问过
            w= getNextNeighbor(i,w);
        }
    }

    //广度优先遍历
    private void bfs(boolean[] isVisited, int i) {
        int u ; // 表示队列的头结点对应下标
        int w ; // 邻接结点 w
        // 队列,记录结点访问的顺序
        LinkedList queue = new LinkedList();
        //访问结点,输出结点信息
        System.out.print(getValueByIndex(i) + "=>");
        //标记为已访问
        isVisited[i] = true;
        //将结点加入队列
        queue.addLast(i);

        while( !queue.isEmpty()) {
            //取出队列的头结点下标
            u = (Integer)queue.removeFirst();
            //得到第一个邻接结点的下标 w
            w = getFirstNeighbor(u);
            while(w != -1) {//找到
                // 是否访问过
                if(!isVisited[w]) {
                    System.out.print(getValueByIndex(w) + "=>");
                    //标记已经访问
                    isVisited[w] = true;
                    // 入队
                    queue.addLast(w);
                    //
                    }
                //以 u 为前驱点,找 w 后面的下一个邻结点
                w = getNextNeighbor(u, w);
                //体现出我们的广度优先
                }
        }

    }

    //遍历所有的结点,都进行广度优先搜索
    public void bfs() {
        isVisited = new boolean[vertexList.size()];
        for(int i = 0; i < getNumOfVertex(); i++) {
            if(!isVisited[i]) {
                bfs(isVisited,i);
            }
        }
    }

    //对dfs重载 遍历所有的节点 并进行
    public void dfs(){
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < getNumOfVertex(); i++) {
            if (!isVisited[i]){
                dfs(i,isVisited);
            }
        }
    }


    //得到index节点第1个临接节点的下标
    public int getFirstNeighbor(int index){
        for (int i = 0; i < vertexList.size(); i++) {
            if (edges[index][i] > 0){
                return i;
            }
        }
        return -1;
    }
    //根据前一个临接节点下标v1 获取下一个临接节点下标
    public int getNextNeighbor(int index,int v1){
        for (int i = v1 + 1; i < vertexList.size(); i++) {
            if (edges[index][v1] > 0){
                return i;
            }
        }
        return -1;
    }

    //图的常用方法
    //返回节点个数
    public int getNumOfVertex(){
        return vertexList.size();
    }
    //返回边的数目
    public int getNumOfEdges(){
        return numOfEdges;
    }
    //返回下标i对应的节点
    public String getValueByIndex(int i){
        return vertexList.get(i);
    }
    //返回v1 v2的权值
    public int getWEeight(int v1,int v2){
        return edges[v1][v2];
    }
    //显示图对应的矩阵
    public void show(){
        for (int[] line : edges){
            System.out.println(Arrays.toString(line));
        }
    }

    //插入节点
    public void insertVertex(String vertex){
        vertexList.add(vertex);
    }
    //添加边

    /**
     *
     * @param v1 点的下标 即第1个顶点
     * @param v2 点的下标 即第2个顶点
     * @param weight 边的权值
     */
    public void insertEdge(int v1,int v2,int weight){
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges++;
    }
}

  1. 英文为stack
  2. 先入后出的原则,是一个有序列表
  3. push入栈 pop出栈

用数组模拟一个栈

public class ArrayStackDemo{
    public static void main(String[] args) {
        ArrayStack arrayStack = new ArrayStack(10);
        arrayStack.push(1);
        arrayStack.push(2);
        arrayStack.push(3);
        arrayStack.list();
    }
}

class ArrayStack{
    private int maxSize;//栈大小
    private int[] stack;//数组模拟栈
    private int top=-1;//栈顶 初始化-1

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

    //判断边界
    public boolean isFull(){
        return top == maxSize-1;
    }
    private boolean isEmpty(){
        return top == -1;
    }

    //push入栈
    public void push(int value){
        if(isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top]=value;
    }
    //pop出栈
    public int pop(){
        if(isEmpty()){
            throw new RuntimeException("栈空,没有数据");
        }
        return stack[top--];
    }
    //遍历
    public void list(){
        if(isEmpty()){
            System.out.println("空,没有数据");
            return;
        }
        //从栈顶开始显示
        for (int i = top; i >= 0; i--) {
            System.out.println("stack["+i+"]="+stack[i]);
        }
    }
}

栈-计算表达式(中缀表达式)

  1. 创建两个栈 :numStack存放数据;operStack存放运算符
  2. 数字直接入栈
  3. 运算符-若栈空 直接入栈;栈非空 则比较当前和栈顶符号优先级:若当前符号优先级小于等于栈顶优先级,则numStack弹出两个数据,peroStack出栈栈顶符号,进行运算,得到的结果在numStack入栈,将当前符号入栈;若当前符号优先级更大,则直接peroStack入栈。、、
  4. 计算式扫描完毕。顺序得从数据和符号中出栈并计算
  5. 数字栈只有一个数据时,就是所求结果
public class ArrayStackDemo{
    public static void main(String[] args) {
        ArrayStack numStack = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        String expression = "20+20/20";
        String str = "";

        //得到表达式的每一个字符
        for (int i = 0; i < expression.length(); i++) {
            char ch = expression.charAt(i);
            //是否为运算符
            if(operStack.isOper(ch)){
                if(!operStack.isEmpty()){
                    //不为空 比较优先级
                    if(operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        int num1 = numStack.pop();
                        int num2 = numStack.pop();
                        int oper = operStack.pop();
                        int res = numStack.cal(num1,num2,oper);
                        numStack.push(res);
                        operStack.push(ch);
                    }else {
                        operStack.push(ch);
                    }
                }else {
                    operStack.push(ch);
                }
            }//数字 则直接入栈 注意 可以使多位数
            else {
                str+=ch;
                if(i==expression.length()-1){
                    numStack.push(Integer.parseInt(str));
                    break;
                }
                if(operStack.isOper(expression.charAt(i+1))){
                    numStack.push(Integer.parseInt(str));
                    str = "";
                }
            }

        }
        //扫描完毕,按顺序执行栈中元素
        while (true){
            if(operStack.isEmpty()){
                break;
            }
            int num1 = numStack.pop();
            int num2 = numStack.pop();
            int oper = operStack.pop();
            int res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        System.out.println("计算结果为:"+numStack.pop()+"!!!!");
    }
}

class ArrayStack{
    private int maxSize;//栈大小
    private int[] stack;//数组模拟栈
    private int top=-1;//栈顶 初始化-1

    //构造器

    public ArrayStack() {
    }

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

    //判断边界
    public boolean isFull(){
        return top == maxSize-1;
    }
    public boolean isEmpty(){
        return top == -1;
    }

    //push入栈
    public void push(int value){
        if(isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top]=value;
    }
    //pop出栈
    public int pop(){
        if(isEmpty()){
            throw new RuntimeException("栈空,没有数据");
        }
        return stack[top--];
    }
    public int peek(){
        return stack[top];
    }
    //遍历
    public void list(){
        if(isEmpty()){
            System.out.println("空,没有数据");
            return;
        }
        //从栈顶开始显示
        for (int i = top; i >= 0; i--) {
            System.out.println("stack["+i+"]="+stack[i]);
        }
    }
    //优先级
    public int priority(int oper){
        if(oper == '*' || oper == '/'){
            return 1;
        }else if(oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;
        }
    }
    //判断运算符
    public boolean isOper(char val){
        return val=='-' || val=='*' || val=='+' ||val=='/';
    }
    //判断数字
    public boolean isNum(char val){
        return 0 < val && val < 10;
    }
    //计算
    public int cal(int num1,int num2,int oper){
        int res = 0;
        switch (oper){
            case '+':
                return num1+num2;
            case '-':
                return  num2-num1;
            case '*':
                return  num1*num2;
            case '/':
                return  num2/num1;
            default:
                return res;
        }
    }
}


递归

简单的说:就是自己调用自己,每次传入不同变量

  1. 程序每执行一个方法,都会开辟一个独立的空间(栈)
  2. 每个空间的数据(局部变量)是独立的
  3. 如果方法中使用的是引用类型变量 就会共享该引用类型的数据
  4. 递归必须向退出递归的条件逼近 否则无限循环
  5. 谁调用 返回谁
//阶乘
public statci int text(int n){
	if(n==1){
		return 1;
	} else{
		return text(n-1)*n;
	}
}

中缀转后缀表达式

  • 前缀表达式 运算符位于操作符之前
    (3+4)5-6 -->-+3456
    从右至左扫描,数字直接入栈,遇到运算符时弹出两个数进行计算,并将结果入栈。重复直到表达式最左边得到结果

  • 中缀表达式
    (3+4)*5-6 判断运算符优先级运算,对于计算机来说不好操作,一般都是转换成其他表达式来操作的

  • 后缀表达式(逆波兰表达式) 运算符位于操作数之后
    (3+4)5-6 -->34+56-
    从左至右扫描,遇到数字将数字入栈,遇到运算符时弹出两个数做运算,并将结果入栈。重复直到扫描完成得到结果

  • 中缀转后缀“1+((2+3)×4)-5”–>“1 2 3 + 4 × + 5 –”

  1. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压s2;
  4. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
  5. 遇到括号时:
    (1) 如果是左括号“(”,则直接压入s1
    (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤2至5,直到表达式的最右边
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class ArrayStackDemo {
    public static void main(String[] args) {
        //定义逆波兰表达式
        String input = "1+((2+3)*4)-5";
        String sufficExpression = transExpression(input);
        System.out.println(sufficExpression);
        int res = getRes(sufficExpression);
        System.out.println("计算结果为"+res+"!!!");
    }

    //中缀转后缀 为了不用逆序输出 存储栈换成List结构
    public static String transExpression(String input) {
        List<String> list = new ArrayList<>();
        Stack<String> operStack = new Stack<>();
        String str = "";

        for (int i = 0; i < input.length(); i++) {
            char tem = input.charAt(i);
            //判断数字 考虑多位
            if (tem <= 57 && tem >= 48) {
                str += tem;
                if (i != input.length() - 1) {
                    if (input.charAt(i + 1) > 57 || input.charAt(i + 1) < 48) {
                        list.add(str);
                        str = "";
                    }
                } else {
                    list.add(str);
                }
            }


            //判断符号位 遇到运算符
            else if (tem == '+' || tem == '-' || tem == '*' || tem == '/') {
                while (true){
                    if(operStack.size()!=0 &&
                       priority(tem)<priority(operStack.peek().charAt(0))){
                        list.add(operStack.pop());
                    }else {
                        operStack.push(tem + "");
                        break;
                    }
                }
            }

            //左括号直接入栈
            else if (tem == '(') {
                operStack.push(tem + "");
            }
            //右括号 弹出元素 直到遇到左括号为止 丢掉这对括号
            else if (tem == ')') {
                while (operStack.peek().charAt(0) != '(') {
                    list.add(operStack.pop());
                }
                operStack.pop();
            }
            else{
            throw new RuntimeException("符号有误!请重新输入!!");
        }
        }
        //剩余运算符加入到列表中
        while (operStack.size()!=0){
            list.add(operStack.pop());
        }

        StringBuffer res = new StringBuffer();
        for (String s:list) {
            res.append(s+" ");
        }
        return res.toString();
    }



    //计算逆波兰表达式
    //从左至右扫描,遇到数字将数字入栈,遇到运算符时弹出两个数做运算,并将结果入栈。重复直到扫描完成得到结果
    public static int getRes(String sufficExpression){
        Stack <String> stack = new Stack<>();
        String [] split = sufficExpression.split(" ");
        for (String val:split) {
            if(val.matches("\\d+")){//匹配多位数
                stack.push(val);
            }else {
                int res = 0;
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                switch (val.charAt(0)){
                    case '+':
                        res = num1 + num2;
                        break;
                    case '-':
                        res = num1 - num2;
                        break;
                    case '*':
                        res = num1 * num2;
                        break;
                    case '/':
                        res = num1 / num2;
                        break;
                }
             stack.push(String.valueOf(res));
            }
        }
        return Integer.parseInt(stack.pop());
    }

    public static int priority(char c){
        int res = 0;
        switch (c){
            case '+':res = 0;break;
            case '-':res = 0;break;
            case '*':res = 1;break;
            case '/':res = 1;break;
        }
        return res;
    }
}

八皇后问题

  • 8X8的格子上摆放8个皇后,使其不能相互攻击(任意两个不能处于同一行、同一列或同一斜线),问有多少种摆法?

  • 第一个皇后先放第一行第一列

  • 第二个皇后放在第二行第一列、然后判断是否OK, 如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适

  • 继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解

  • 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到.

  • 然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4的步骤

  • 说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题. arr = {0 , 4, 7, 5, 2, 6, 1, 3} //对应arr 下标 表示第几行和第几个皇后,arr[i] = val , 表示第i+1个皇后,放在第i+1行的第val+1列


public class TextDemo {
    //8X8的map表示棋盘
    int max = 8;
    int [] map = new int[max];
    int count = 0;

    public static void main(String[] args) {
        TextDemo queue = new TextDemo();
        queue.put(0);
        System.out.println(queue.count);


    }
    private void put(int n){
        if(n==max){
            print();
            count++;
            return;
        }
        //依次从第一列放入 检查冲突
        for (int i = 0; i < max; i++) {
            map[n] = i;
            if(check(n)){
                put(n+1);
            }
        }
    }


    //检查第n个皇后是否冲突
    private boolean check(int n){
        for (int i = 0; i < n; i++) {
            //判断是否在一列 或 是否在一条斜线
            if(map[i]==map[n] || Math.abs(n-i) == Math.abs(map[n]-map[i])){
                return false;
            }
        }
        return true;
    }

    private void print(){
        for (int i = 0; i < max; i++) {
            System.out.print(map[i]);
        }
        System.out.println();
    }

}


迷宫问题


public class ArrayStackDemo {
    public static void main(String[] args) {
        //二维数组 模拟迷宫
        //地图
        int [][] map = new int[8][7];
        //1表示墙 上下全部置为1
        for (int i = 0; i < map.length; i++) {
            map[i][0]= 1;
            map[i][map[0].length-1] = 1;
        }
        for (int i = 0; i < map[0].length; i++) {
            map[0][i] = 1;
            map[map.length-1][i] = 1;
        }
        //路径障碍 设置为1
        map[3][1] = 1;
        map[3][2] = 1;

        findWay(map,1,1);

        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                System.out.print(map[i][j]+" ");
            }
            System.out.println();
        }

    }
    //约定map[i][j]=0未走 1表示墙 2表示可以走 3表示走过但是不通
    //策略:下右上左
    //map[6][5]说明通路找到
    /**
     *
     * @param map 表示迷宫地图
     * @param i 行
     * @param j 列 开始的位置
     * @return 若找到通路 返回true 否则返回false
     */
    public static boolean findWay(int[][]map,int i,int j){


        if(map[6][5] == 2){
            return true;
        }else {
            if (map[i][j] == 0){
                map[i][j] = 2;
                if(findWay(map,i+1,j)){//向下走
                    return true;
                }else if(findWay(map,i,j+1)){//向右走
                    return true;
                }else if(findWay(map,i-1,j)){//上
                    return true;
                }else if (findWay(map,i,j-1)){//左
                    return true;
                }else {//该点走不通
                    map[i][j] = 3;
                    return false;
                }
            }else {
                return false;
            }
        }
    }
}

典型算法

1.二分查找算法(非递归)

  • 二分查找法只适用于从有序的数列中进行查找(比如数字和字母等),将数列排序后再进行查找
  • 二分查找法的运行时间为对数时间O(㏒₂n) ,即查找到需要的目标位置最多只需要㏒₂n步,假设从[0,99]的队列(100个数,即n=100)中寻到目标数30,则需要查找步数为㏒₂100 , 即最多需要查找7次( 2^6 < 100 < 2^7)
class Algorithm{
    public static void main(String[] args) {
        int[] arr = {1,3,8,10,11,67,100};
        System.out.println(binarySearch(arr,1));
    }

    /**
     * 二分查找(非递归)
     * @param arr 待查找的数组
     * @param target 查找的目标值
     * @return -1表示没有找到
     */
    public static int binarySearch(int[] arr,int target){
        int left = 0;
        int right = arr.length - 1;

        while (left <= right){
            int mid = (right + left) / 2;
            if (arr[mid] == target){
                return mid;
            }else if (arr[mid] > target){
                //向左边继续查找
                right = mid - 1;
            }else {
                //向右继续查找
                left = mid + 1;
            }
        }
        return -1;
    }
}

2.分治算法

  • 分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……
  • 可以解决二分搜索、大整数乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、最接近点对问题、循环赛日程表、汉诺塔
  1. 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
  2. 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
  3. 合并:将各个子问题的解合并为原问题的解。
汉诺塔问题
  • 若盘数n=1,直接从A->C
  • 若n>=2,总是可以将其看做两个盘 1最下面的盘 2其他的盘
  1. 先把最上面的盘 A->B
  2. 最下面的盘 A->C
  3. 把C的所有盘 B->C
class Algorithm{
    public static void main(String[] args) {
        int num = 5;
        hannuoTower(num,'A','B','C');

    }

    /**
     *
     * @param num 盘数
     * @param a 柱
     * @param b 柱
     * @param c 柱
     */
    public static void hannuoTower(int num,char a,char b,char c) {
        //只有一个盘
        if (num == 1){
            System.out.println("第1个盘移动:" + a + "->" + c);
        }else {
            //n >= 2的情况 总是可以看作两个盘 1底盘 2其他盘
            //1.将其他盘移动 a-b
            hannuoTower(num-1,a,c,b);
            //2.底盘移动 a-c
            System.out.println("第" + num + "个盘移动:" + a + "->" + c);
            //3.把b柱的盘移动 b-c
            hannuoTower(num-1,b,a,c);
        }
    }
}

3.动态规划

背包问题

背包问题:有一个背包,容量为4磅 , 现有如下物品
在这里插入图片描述

  1. 要求达到的目标为装入的背包的总价值最大,并且重量不超出
  2. 要求装入的物品不能重复
  3. 这里属于01背包,即每个物品最多放一个。而无限背包可以转化为01背包。
  • 主要思想,利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值。则我们有下面的结果:
    (1) v[i][0]=v[0][j]=0; //表示 填入表 第一行和第一列是0
    (2) 当w[i]> j 时:v[i][j]=v[i-1][j] // 当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个单元格的装入策略
    (3) 当j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
    // 当 准备加入的新增的商品的容量小于等于当前背包的容量,
    // 装入的方式:
    v[i-1][j]: 就是上一个单元格的装入的最大值
    v[i] : 表示当前商品的价值
    v[i-1][j-w[i]] : 装入i-1商品,到剩余空间j-w[i]的最大值
    当j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
class Algorithm{
    public static void main(String[] args) {
        int[] w = {1,4,3};//物品重量
        int[] val = {1500,3000,2000};//物品价值
        int m = 4;//背包容量
        int n = w.length;//物品的个数i

        //v[i][j]表示在前i个物品中能够装入剩余容量为j的背包中的最大价值
        int[][] v = new int[n+1][m+1];
        //记录商品
        int[][] path  = new int[n+1][m+1];

        //初始化第一行和第一列
        for (int i = 0; i < v.length; i++) {
            v[i][0] = 0;
        }
        for (int i = 0; i < v[0].length; i++) {
            v[0][i] = 0;
        }
        
        //动态规划处理
        for (int i = 1; i < v.length; i++) {//不处理第一行
            for (int j = 1; j < v[0].length; j++) {//不处理第一列
                if (w[i-1] > j){
                    v[i][j] = v[i-1][j];
                }else {
                    //v[i][j] = Math.max(v[i-1][j],val[i-1] + v[i-1][j-w[i-1]]);
                    if (v[i-1][j] < (val[i-1] + v[i-1][j-w[i-1]])){
                        v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
                        path[i][j] = 1;
                    }else {
                        v[i][j] = v[i-1][j];
                    }
                }
            }
        }

        for (int i = 0; i < v.length; i++) {
            for (int j = 0; j < v[0].length; j++) {
                System.out.print(v[i][j]+" ");
            }
            System.out.println();
        }
        System.out.println("=================");

        int i = path.length - 1;
        int j = path[0].length -1;
        while (i > 0 && j > 0){
            if (path[i][j] == 1){
                System.out.println("放入第"+i+"个物品");
                j -= w[i-1];
            }
            i--;
        }
    }
    
        

}

3.KMP

  • 它是⽤来解决字符串查找的问题,可以在⼀个字符串(S)中查找⼀个⼦串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩⼩到O(m+n) ,⽽空间复杂度也只有O(m)。
    1.利用最长公共前后缀 添加-1后 移位 建前缀表 Prefix Table
    2.未匹配到的字母 P表对应值的为i 移动子串将对应下标为i的字符移位到匹配为不成功处,并从此处继续扫描
public class test {

    public static void main(String[] args) {

        String a = "ababababcabaab";
        String b = "aba";
        int n = a.length();
        int m = b.length();
        char [] s = new char[n];
        char [] w = new char[m];

        for (int i = 0; i < n; i++) {
            s[i] = a.charAt(i);
        }
        for (int i = 0; i < m; i++) {
            w[i] = b.charAt(i);
        }

        int [] prefix = initial(w,m);

        int [] next = move(m,prefix);


        System.out.println("满足匹配!共匹配"+match(m,n,next,s,w)+"次");
    }

    //创建前缀表
    public static int[] initial(char[] s,int m){

        int [] prefix = new int [m];
        prefix[0] = 0;
        int len = 0;
        int i = 1;
		//建立方式 a->ab->aba 若下一个字母与下标len字符匹配 则最长公共前后缀len++
        while(i<m){
            if(s[i] == s[len]){
                len++;
                prefix[i] = len;
                i++;
            }else{
            	//若不匹配 斜着往前一位 判断是否与前一位匹配 循环
                if(len>0){
                    len = prefix[len-1];
                }else{
                    prefix[i] = len;
                    i++;
                }
            }
        }
    return prefix;
    }

    //移位表
    public static int[] move(int m,int [] prefix){
        for (int i = m-1; i > 0; i--) {
            prefix [i] = prefix[i-1];
        }
        prefix [0] = -1;
        return prefix;
    }

    public static int match(int m,int n,int[]next,char[]s,char[]w){
        int j=0,i=0,result=0;
        //这里要注意下标越界
        while(j<m && (n-i)>m){
            if(s[i] == w[j]){
                    i++;
                    j++;
            }else{
                j = next[j];
                if(next[j] == -1){
                    i++;
                    j++;
                }
            }
            if(j==m-1 && s[i]==w[j]){
                System.out.println("匹配成功!下标为"+(i-j));
                result++;
                j = next[j];
            }

        }
        return result;
    }


}

4.贪心算法

  1. 贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法
  2. 贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果
集合覆盖
  • 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号
    在这里插入图片描述
  • 思路分析:
    目前并没有算法可以快速计算得到准备的值, 使用贪婪算法,则可以得到非常接近的解,并且效率高。选择策略上,因为需要覆盖全部地区的最小集合:
  1. 遍历所有的广播电台, 找到一个覆盖最多地区的电台
  2. 将这个电台加入到一个集合中(比如ArrayList), 想办法把该电台覆盖的地区在下次从总地区比较时去掉。
  3. 重复第1步直到覆盖了全部的地区
import java.util.*;

class Algorithm{
    public static void main(String[] args) {
        String[] k1 = {"北京", "上海", "天津"};
        String[] k2 = {"广州", "北京", "深圳"};
        String[] k3 = {"成都", "上海", "杭州"};
        String[] k4 = {"上海", "天津"};
        String[] k5 = {"杭州", "大连"};
        //电台存储为HashMap结构 所有待覆盖地区存储为HashSet结构
        HashSet<String> allAreas = new HashSet();
        HashMap<String,HashSet> broadcasts = add(k1,k2,k3,k4,k5);
        for (String key:broadcasts.keySet()) {
            System.out.println(key + "\t" +broadcasts.get(key));
            allAreas.addAll(broadcasts.get(key));
        }
        for (String s:allAreas) {
            System.out.print(s+"\t");
        }
        System.out.println();
        //选择电台存储为ArrayList
        ArrayList<String> selects = new ArrayList();
        //临时集合 存放电台覆盖地区与总地区的交集
        HashSet<String> tempSet = new HashSet<>();
        //定义maxKey 覆盖最多未覆盖地区的电台啊
        String maxKey = "";

        while (allAreas.size() != 0){
            //每次将maxKey置空
            maxKey = null;
            //遍历电台 取出对应的key
            for (String key : broadcasts.keySet()) {
                //清空上一次的temSet
                tempSet.clear();
                //当前key能覆盖的地区
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
                //找出与未覆盖地区重合的地区
                tempSet.retainAll(allAreas);
                //如果当前key的未覆盖地区数 maxKey比更多 则重新给maxKey赋值
                //这里体现贪心算法 每次都选择最优
                if (tempSet.size() > 0 && (maxKey == null ||
                        tempSet.size() > broadcasts.get(maxKey).size())){
                    maxKey = key;
                }
            }
            if (maxKey != null){
                selects.add(maxKey);
                //将maxKey覆盖的地区 从allAreas清除掉
                allAreas.removeAll(broadcasts.get(maxKey));
            }
        }


        System.out.println("选择结果:"+selects);

    }

    public static HashMap add(String[] k1, String[] k2, String[] k3, String[] k4, String[] k5){
        HashMap<String,HashSet> map = new HashMap();
        HashSet<String> set1 = new HashSet();
        HashSet<String> set2 = new HashSet();
        HashSet<String> set3 = new HashSet();
        HashSet<String> set4 = new HashSet();
        HashSet<String> set5 = new HashSet();

        for (String s:k1 ) {
            set1.add(s);
        }
        map.put("k1",set1);

        for (String s:k2 ) {
            set2.add(s);
        }
        map.put("k2",set2);

        for (String s:k3 ) {
            set3.add(s);
        }
        map.put("k3",set3);

        for (String s:k4 ) {
            set4.add(s);
        }
        map.put("k4",set4);

        for (String s:k5 ) {
            set5.add(s);
        }
        map.put("k5",set5);
        return map;
    }





}

5.普里姆算法(Prim)

  • 普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
  • 普利姆的算法如下:
  1. 设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合
  2. 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
  3. 若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
  4. 重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
修路问题
  • 修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST。
  1. 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树
  2. N个顶点,一定有N-1条边
  3. 包含全部顶点
  4. N-1条边都在图中
    求最小生成树的算法主要是普里姆算法和克鲁斯卡尔算法
    在这里插入图片描述
    在这里插入图片描述
import java.util.Arrays;

class Algorithm{
    public static void main(String[] args) {
        char[] data = {'A','B','C','D','E','F','G'};
        int verxs = data.length;
        //10000表示不连通
        int[][] weight = { {10000,5,7,10000,10000,10000,2},
                           {5,10000,10000,9,10000,10000,3},
                           {7,10000,10000,10000,8,10000,10000},
                           {10000,9,10000,10000,10000,4,10000},
                           {10000,10000,8,10000,10000,5,4},
                           {10000,10000,10000,4,5,10000,6},
                           {2,3,10000,10000,4,6,10000} };
        Graph graph = new Graph(verxs);
        MinTree tree = new MinTree();
        tree.create(graph,verxs,data,weight);
        tree.show(graph);

        tree.prim(graph,1);

    }

}

//创建最小生成树
class MinTree{
    //创建图的邻接矩阵
    /**
     *
     * @param graph 图对象
     * @param verxs  对应顶点个数
     * @param data   顶点值
     * @param weight  图的邻接矩阵
     */
    public void create(Graph graph,int verxs,char[] data,int[][] weight){
        for (int i = 0; i < verxs; i++) {
            graph.data[i] = data[i];
            for (int j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }
    public void show(Graph graph){
        for (int[] i:graph.weight) {
            System.out.println(Arrays.toString(i));
        }
    }
    //prim算法

    /**
     *
     * @param graph 图对象
     * @param index 从哪个节点开始生成
     */
    public void prim(Graph graph,int index){
        //标记节点是否被访问过
        int[] visited = new int[graph.verxs];
        visited[index] = 1;
        //h1 h2记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000;
        for (int k = 1; k < graph.verxs; k++) {//算法结束 会有grapg.verxs-1条边

            //确定每次生成的子图 是权重最小的
            for (int i = 0; i < graph.verxs; i++) {//当前访问
                for (int j = 0; j < graph.verxs; j++) {//未访问
                    if (visited[i]==1 && visited[j]==0 && (graph.weight[i][j] < minWeight)) {
                        minWeight = graph.weight[i][j];
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            //找到了一条权值最小的边
            System.out.println("边:" + graph.data[h1] + "-->" + graph.data[h2] + "\t" +minWeight);
            visited[h2] = 1;
            minWeight = 10000;
        }
    }
}


class  Graph{
    int verxs;//图的节点个数
    char[] data;//存放节点数据
    int[][] weight;//存放边 即邻接矩阵

    public Graph(int verxs) {
        this.verxs = verxs;
        data = new char[verxs];
        weight = new int[verxs][verxs];
    }
}

5.克鲁斯卡尔(Kruskal)算法

  • 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
  • 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
  • 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
  • 将边添加到最小生成树中时,怎么样判断是否形成了回路?
  • 处理方式是:记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"(ASII)。 然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。
公交站问题

在这里插入图片描述

  • 有北京有新增7个站点(A, B, C, D, E, F, G) ,现在需要修路把7个站点连通
    各个站点的距离用边线表示(权) ,比如 A – B 距离 12公里
    问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?
import com.sun.javafx.geom.Edge;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.util.Arrays;

class Kruskal{
    private int edgeNum;//边的个数
    private char[] vertexs;//顶点数组
    private int[][] matrix;//邻接矩阵
    private static final int INF = Integer.MAX_VALUE;//顶点不能连通


    public static void main(String[] args) {
        char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int matrix[][] = { /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
                            /*A*/ { 0, 12, INF, INF, INF, 16, 14},
                            /*B*/ { 12, 0, 10, INF, INF, 7, INF},
                            /*C*/ { INF, 10, 0, 3, 5, 6, INF},
                            /*D*/ { INF, INF, 3, 0, 4, INF, INF},
                            /*E*/ { INF, INF, 5, 4, 0, 2, 8},
                            /*F*/ { 16, 7, 6, INF, 2, 0, 9},
                            /*G*/ { 14, INF, INF, INF, 8, 9, 0}};

        Kruskal kruskal = new Kruskal(vertexs,matrix);
        kruskal.print();
        kruskal.kruskal();
    }

    //初始化顶点数和边数
    public Kruskal(char[] vertexs, int[][] matrix) {
        //初始化顶点数和边的个数
        int vlen = vertexs.length;
        //初始化顶点, 复制拷贝的方式
        this.vertexs = new char[vlen];
        for(int i = 0; i < vertexs.length; i++) {
            this.vertexs[i] = vertexs[i];
        }
        //初始化边, 使用的是复制拷贝的方式
        this.matrix = new int[vlen][vlen];
        for(int i = 0; i < vlen; i++) {
            for(int j= 0; j < vlen; j++) {
                this.matrix[i][j] = matrix[i][j];
            }
        }
        //统计边
        for(int i =0; i < vlen; i++) {
            for(int j = i+1; j < vlen; j++) {
                if(this.matrix[i][j] != INF) {
                    edgeNum++;
                }
            }
        }
    }

    //
    public void kruskal(){
        int index = 0;//表示最后结果数组的索引
        int[] ends = new int[edgeNum];//保存已有最小树 中每个顶点的终点
        //创建结果数组
        Edges[]  rets = new  Edges[edgeNum];

        //获取图中 所有边的集合
        Edges[] edges = getEdges();
        System.out.println("图的边集合" + "\n"  + Arrays.toString(edges));
        //排序
        sort(edges);
        //判断回路
        for (int i = 0; i < edgeNum; i++) {
            //获取第i边的第一个顶点
            int p1 = getPosition(edges[i].start);
            //第二个顶点
            int p2 = getPosition(edges[i].end);

            //获取p1 p2的终点
            int m = getEnd(ends,p1);
            int n = getEnd(ends,p2);
            //构成回路
            if ( m!=n){
                ends[m] = n;//设置m的终点
                rets[index++] = edges[i];
            }
        }
        //统计打印最小生成树
        for (int i = 0; i < index; i++) {
            System.out.println(rets[i]);
        }

    }

    //遍历临接矩阵
    public void print(){
        System.out.println("邻接矩阵");
        for (int[] i:matrix) {
            System.out.println(Arrays.toString(i));
        }
    }

    //冒泡对边排序
    public void  sort(Edges[] edges){
        for (int i = 0; i < edges.length -1; i++) {
            for (int j = 0; j < edges.length -1-i; j++) {
                if (edges[j].weight > edges[j+1].weight){
                    Edges tem =  edges[j];
                    edges[j] = edges[j+1];
                    edges[j+1] = tem;
                }
            }
        }
    }

    //得到顶点对应下标
    public int getPosition(char ch){
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] == ch){
                return i;
            }
        }
        return -1;
    }

    //获取图中的边 放到Edges[] 后面需要遍历该数组
    public Edges[] getEdges(){
        int index = 0;
        Edges[] edges= new Edges[edgeNum];
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = i+1; j < vertexs.length; j++) {
                if (matrix[i][j] != INF){
                    edges[index++] = new Edges(vertexs[i],vertexs[j],matrix[i][j]);
                }
            }
        }
        return edges;
    }

    //获取下标为i的顶点的终点 返回对应终点下标
    public int getEnd(int[] ends,int i){
        while (ends[i] != 0){
            i = ends[i];
        }
        return i;
    }
}

//表示一条边
class Edges{
    char start;//起点
    char end;//另外一个点
    int weight;

    public Edges(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "EData{<" + start +
                "," + end + ">"  +
                "," + weight +
                '}';
    }
}

6.迪杰斯特拉算法

最短路径问题

在这里插入图片描述

  • 战争时期,胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在有六个邮差,从G点出发,需要分别把邮件分别送到 A, B, C , D, E, F 六个村庄,各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里
  • 问:如何计算出G村庄到 其它各个村庄的最短距离?

设置出发顶点为v,顶点集合V{v1,v2,vi…},v到V中各顶点的距离构成距离集合Dis,Dis{d1,d2,di…},Dis集合记录着v到图中各顶点的距离(到自身可以看作0,v到vi距离对应为di)

  1. 从Dis中选择值最小的di并移出Dis集合,同时移出V集合中对应的顶点vi,此时的v到vi即为最短路径
  2. 更新Dis集合,更新规则为:比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为vi,表明是通过vi到达的)
  3. 重复执行两步骤,直到最短路径顶点为目标顶点即可结束
import java.util.Arrays;

class Dijkstra{
    public static void main(String[] args) {
        char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;// 表示不可以连接
        matrix[0]=new int[]{N,5,7,N,N,N,2};
        matrix[1]=new int[]{5,N,N,9,N,N,3};
        matrix[2]=new int[]{7,N,N,N,8,N,N};
        matrix[3]=new int[]{N,9,N,N,N,4,N};
        matrix[4]=new int[]{N,N,8,N,N,5,4};
        matrix[5]=new int[]{N,N,N,4,5,N,6};
        matrix[6]=new int[]{2,3,N,N,4,6,N};
        //创建 Graph 对象
        Graph graph = new Graph(vertex, matrix);
        //测试, 看看图的邻接矩阵是否 ok
        graph.show();
        //测试迪杰斯特拉算法
        graph.dsj(6);
        graph.showDSJ();
    }


}

class Graph{
    private  char[] vertex;//顶点数组
    private int[][] matrix;//临接矩阵
    private VisitedVertex vv;//已经访问的顶点集合

    //构造器
    public Graph(char[] vertex, int[][] matrix) {
        this.vertex = vertex;
        this.matrix = matrix;
    }

    public void show(){
        for (int[] link:matrix) {
            System.out.println(Arrays.toString(link));
        }
    }

    /**
     *
     * @param index 出发节点的下标
     */
    public void dsj(int index){
        vv = new VisitedVertex(vertex.length, index);
        update(index);

        for (int i = 1; i < vertex.length; i++) {
            index = vv.updateArr();
            update(index);
        }
    }

    //更新index下标顶点到周围顶点的距离 和周围顶点的前驱节点
    private void update(int index){
        int len = 0;
        //遍历矩阵的matrix[index]行
        for (int i = 0; i < matrix[index].length; i++) {
            //len = 出发点到index的距离 + index到j顶点的距离
            len = vv.getDis(index) + matrix[index][i];
            if (!vv.in(i) && len < vv.getDis(i)){
                vv.updatePre(i,index);
                vv.updateDis(i,len);
            }
        }
    }

    public void showDSJ(){
        vv.show();
    }
}

class VisitedVertex {
    // 记录各个顶点是否访问过 1 表示访问过,0 未访问,会动态更新
    public int[] already_arr;
    // 每个下标对应的值为前一个顶点下标, 会动态更新
    public int[] pre_visited;
    // 记录出发顶点到其他所有顶点的距离,比如 G 为出发顶点,就会记录 G 到其它顶点的距离,会动态更新,求的最短距离就会存放dis
    public int[] dis;

    //构造器

    /**
     *
     * @param length 顶点个数
     * @param index 出发顶点下标
     */
    public VisitedVertex(int length,int index){
        this.already_arr = new int[length];
        this.pre_visited = new int[length];
        this.dis = new int[length];

        //初始化dis
        Arrays.fill(dis,65535);
        this.already_arr[index] = 1;
        this.dis[index] = 0;
    }

    /**
     * 判断index顶点是否被访问过
     * @param index 定点下标
     * @return true已访问
     */
    public boolean in (int index) {
        return already_arr[index] == 1;
    }

    /**
     * 更新出发顶点到index顶点的距离
     * @param index
     * @param len
     */
    public void updateDis(int index,int len){
        dis[index] = len;
    }

    /**
     * 更新pre顶点的前驱顶点为index
     * @param pre
     * @param index
     */
    public void updatePre(int pre,int index){
        pre_visited[pre] = index;
    }

    //返回出发点到Index的距离
    public int getDis(int index){
        return dis[index];
    }

    //继续访问并选择新的开始访问节点
    public int updateArr(){
        int min = 65535;
        int index = 0;
        for (int i = 0; i < already_arr.length; i++) {
            if (already_arr[i] == 0 && dis[i]<min){
                min = dis[i];
                index = i;
            }
        }
        already_arr[index] = 1;
        return index;
    }

    public void show(){
        System.out.println("==========================");
        for (int i:already_arr) {
            System.out.print(i+"\t");
        }
        System.out.println();
        for (int i:pre_visited) {
            System.out.print(i+"\t");
        }
        System.out.println();
        for (int i:dis) {
            System.out.print(i+"\t");
        }
    }

}

7.弗洛伊德(Floyd)算法

  • 弗洛伊德算法 VS 迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他顶点的最短路径
  • 设置顶点vi到顶点vk的最短路径已知为Lik,顶点vk到vj的最短路径已知为Lkj,顶点vi到vj的路径为Lij,则vi到vj的最短路径为:min((Lik+Lkj),Lij),vk的取值为图中所有顶点,则可获得vi到vj的最短路径
import java.util.Arrays;

class Floyd{
     public static void main(String[] args) {
         // 测试看看图是否创建成功
         char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
         //创建邻接矩阵
         int[][] matrix = new int[vertex.length][vertex.length];
         final int N = 65535;
         matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };
         matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };
         matrix[2] = new int[] { 7, N, 0, N, 8, N, N };
         matrix[3] = new int[] { N, 9, N, 0, N, 4, N };
         matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };
         matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };
         matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };
         //创建 Graph 对象
         Graph graph = new Graph(vertex.length, matrix, vertex);
         graph.show();

         graph.floyd();
         graph.show();
     }

 }

 class Graph{
     private char[] vertex;//存放顶点
     private int[][] dis;//从各个顶点出发到其他节点的距离
     private int[][] pre;//对应距离表dis 记录到达目标节点的前驱节点

     /**
      *
      * @param length 顶点个数
      * @param matrix 临接矩阵
      * @param vertex 顶点数组
      */
     public Graph(int length,int[][] matrix,char[] vertex){
         this.vertex = vertex;
         this.dis = matrix;
         this.pre = new int[length][length];

         //对pre初始化
         for (int i = 0; i < length; i++) {
             Arrays.fill(pre[i],i);
         }
     }

     //显示pre dis 数组
     public void show(){
         System.out.println("距离表dis:");
         for (int[] i:dis) {
             System.out.println(Arrays.toString(i));
         }
         System.out.println("前驱表pre:");
         for (int i = 0; i < vertex.length; i++) {
             for (int j = 0; j < vertex.length; j++) {
                 System.out.print(vertex[pre[i][j]]+"\t");
             }
             System.out.println();
         }
     }

     public void floyd(){
         int len = 0;//保存距离

         for (int k = 0; k < dis.length; k++) {//先遍历中间顶点
             for (int i = 0; i < dis.length; i++) {//出发顶点
                 for (int j = 0; j < dis.length; j++) {//到达顶点
                     len = dis[i][k] + dis[k][j];
                     if (len < dis[i][j]){//更新最短距离
                         dis[i][j] = len;
                         pre[i][j] = pre[k][j];
                         pre[j][i] = pre[k][j];
                     }
                 }
             }
         }
     }

 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值