[笔记] 数据结构二刷【第三篇:哈希表 , 树 , 图】

笔记依照[尚硅谷·数据结构]

数据结构: 哈希表(散列)

​ 散列表: 是根据关键码值(keyVal)而进行访问的数据结构 . 也就是说,他通过关键码值的映射到表中的一个位置来访问记录, 以加快查找的速度, 这个映射函数就叫做散列函数, 存放记录的数组就叫做散列表

​ 自己写的常见的哈希表的结构: (数组+链表) Or (数组+二叉树) 其主要的目的就是提升数据的查找速度

上机题:

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

​ 要求: 不适用数据库, 尽量的节省内存, 速度越快越好

思路:

​ 这里我们采用: 数组+链表的方式 来完成这么一个问题 … 感觉好像没有什么好说的…

package hashTable;

public class HashTableDemo {
   
  public static void main(String[] args) {
   
    HashTable hashTable = new HashTable(7);
  }
}

class HashTable {
    // 创建哈希表,
  private EmpLinkedList[] empLinkedListsArray;
  private int size; // 已拥有多少条链表

  // 构造器
  public HashTable(int size) {
   
    this.size = size;
    // 初始化empLinkedListsArray
    empLinkedListsArray = new EmpLinkedList[this.size];
    // 分别初始化每一条链表
    for (int i = 0; i < size; i++) {
   
      empLinkedListsArray[i] = new EmpLinkedList();
    }
  }

  // 添加雇员
  public void add(Emp emp) {
   
    // 根据员工的ID得到该员工应该加入到那条链表
    int empLinkedListNo = hashFun(emp.getId());
    // 将员工添加到对应的链表中
    empLinkedListsArray[empLinkedListNo].add(emp);
  }
  // 根据输入的ID查找雇员
  public void findEmpById(int id) {
   
    int i = hashFun(id);
    Emp empById = empLinkedListsArray[i].findEmpById(id);
    if (empById != null) {
   
      System.out.println("该雇员的名字是:" + empById.getName());
    } else {
   
      System.out.println("没有找到该雇员");
    }
  }
  // 遍历所有的链表
  public void list() {
   
    for (int i = 0; i < size; i++) {
   
      empLinkedListsArray[i].list(i);
    }
  }

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

class EmpLinkedList {
    // 表示一条链表
  private Emp head;

  // 添加雇员到链表
  public void add(Emp emp) {
   
    if (head == null) {
   
      head = emp;
      return;
    }
    Emp curEmp = head;
    while (true) {
   
      if (head.getNext() == null) {
   
        break;
      }
      curEmp = curEmp.getNext();
    }
    // 现在将emp加载链表
    curEmp.setNext(emp);
  }

  // 遍历链表的雇员信息
  public void list(int no) {
   
    if (head == null) {
   
      System.out.println("当前的链表为空");
      return;
    }
    System.out.print("当前的链表的信息是:");
    Emp curEmp = head;
    while (true) {
   
      System.out.println("id = " + curEmp.getId() + "name : " + curEmp.getName());
      if (curEmp.getNext() == null) {
   
        break;
      }
      curEmp = curEmp.getNext();
    }
    System.out.println();
  }

  // 根据ID查找雇员
  public Emp findEmpById(int id) {
   
    if (head == null) {
   
      System.out.println("链表为空");
      return null;
    }
    Emp curEmp = head;
    while (true) {
   
      if (curEmp.getId() == id) {
   
        break;
      }
      if (curEmp.getNext() == null) {
   
        curEmp = null;
        break;
      }
      curEmp.setNext(curEmp.getNext());
    }
    return curEmp;
  }
}

class Emp {
   
  private Integer id;
  private String name;
  private Emp next; // next默认为空

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

  public Integer getId() {
   
    return id;
  }

  public void setId(Integer id) {
   
    this.id = id;
  }

  public String getName() {
   
    return name;
  }

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

  public Emp getNext() {
   
    return next;
  }

  public void setNext(Emp next) {
   
    this.next = next;
  }
}

数据结构: 树

为什么需要树?

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

树结构的基础

二叉树的创建和-增删改查-

package tree.traverse;

import lombok.Data;
import lombok.NoArgsConstructor;

public class BinaryTreeDemo {
   
  public static void main(String[] args) {
   
    BinaryTree binaryTree = new BinaryTree();
    HeroNode root = new HeroNode(1, "宋江");
    HeroNode node2 = new HeroNode(2, "吴用");
    HeroNode node3 = new HeroNode(3, "卢俊义");
    HeroNode node4 = new HeroNode(4, "林冲");
    HeroNode node5 = new HeroNode(5, "关胜");
    // 先手动创建该二叉树   后面 递归的方式创建该二叉树
    root.setLeft(node2);
    root.setRight(node3);
    node3.setRight(node4);
    node3.setLeft(node5);
    binaryTree.setRoot(root);
    // 前序遍历
    binaryTree.infixOrder();
  }
}

class BinaryTree {
   
  private HeroNode root;

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

  /*public void preOrder() {
    if (this.root != null) {
      this.root.preOrder();
    } else {
      System.out.println("二叉树是空的, 无法遍历");
    }
  }*/

  public void infixOrder() {
   
    if (this.root != null) {
   
      this.root.infixOrder();
    } else {
   
      System.out.println("二叉树是空的, 无法遍历");
    }
  }

  public void rearOrder() {
   
    if (this.root != null) {
   
      this.root.rearOrder();
    } else {
   
      System.out.println("二叉树是空的, 无法遍历");
    }
  }

  /*
    1.自己一开始的问题是: 不想把遍历的方法写在一个结点里面 , 就想让结点就是纯粹的一个结点
    2.然后写在外面的话:就不能一步步的向下遍历 , 因为这个this.root不是当前类的对象不能调用这个类进行递归,所以就不能一步步的向下遍历
    3.如果强行遍历,则需要通过 this.root.getLeft().xxx这后面的"xx"因为下面的遍历已经被删掉了 ,所以这里接不下去了
    问题分析:
    目标: 能够一步步的向下遍历, 这个root因为上面的绑定,不能改变了
      解决的方法: 使用另外一个辅助指针
  */
  public void preOrder(HeroNode now) {
   
    if (now != null) {
   
      // this.root.preOrder();
      System.out.println(now);
      if (now.getLeft() != null) {
   
        preOrder(now.getLeft());
      }
      if (now.getRight() != null) {
   
        preOrder(now.getRight());
      }
    }
  }
}

@Data
@NoArgsConstructor
class HeroNode {
   
  private Integer no;
  private String name;
  private HeroNode left;
  private HeroNode right;

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

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

  // 编写 前序遍历 , 中序遍历 , 后序遍历
  /*public void preOrder() {
    System.out.println(this.toString()); // 先输出父节点
    // 递归向左子树
    if (this.left != null) {
      this.left.preOrder();
    }
    if (this.right != null) {
      this.right.preOrder();
    }
  }*/

  public void infixOrder() {
   
    // 递归向左子树
    if (this.left != null) {
   
      this.left.infixOrder();
    }
    System.out.println(this.toString()); // 输出父节点
    if (this.right != null) {
   
      this.right.infixOrder();
    }
  }

  public void rearOrder() {
   
    // 递归向左子树
    if (this.left != null) {
   
      this.left.rearOrder();
    }
    if (this.right != null) {
   
      this.right.rearOrder();
    }
    System.out.println(this.toString()); // 输出父节点
  }
}

二叉树-指定节点的查找

要求:

  1. 编写前中后序查找的方法
  2. 分别使用三种查找方法, 查找heroNo = 5的节点
  3. 分别使用各种查找方法, 分别比较查找了多少次

查找思路

前序思路:

  • 先判断当前节点的 No == findVal ? 当前节点 : 判断当前节点的左子节点 == null ? : 前序递归查找
  • 如果做递前序查找,找到节点,则返回,否则继续判断,当前的节点的右子节点是否为空, 如果不为空, 则继续向右前序查找
package tree.select;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

public class BinaryTreeDemoSelect {
   
    public static void main(String[] args) {
   
        HeroNode root = new HeroNode("1", "宋江");
        HeroNode node2 = new HeroNode("2", "吴用");
        HeroNode node3 = new HeroNode("3", "卢俊义");
        HeroNode node4 = new HeroNode("4", "林冲");
        HeroNode node5 = new HeroNode("5", "关胜");
        // 先手动创建该二叉树   后面 递归的方式创建该二叉树
        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);

        BinaryTree binaryTree = new BinaryTree();
        binaryTree.setRoot(root);
        binaryTree.PreSelectBinaryTree(root, "5");
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class HeroNode {
   
    private String no;
    private String name;
    private HeroNode left;
    private HeroNode right;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
class BinaryTree {
   
    private HeroNode root;

    private int times = 0;

    public void PreSelectBinaryTree(HeroNode curNode, String no) {
   
        times += 1;
        if (curNode.getNo() == no) {
   
            System.out.println("该英雄的名字是: " + curNode.getName() + " 他的编号是: " + curNode.getNo() + " 总共使用了" + times + "次的查找");
        }
        if (curNode.getNo() != no && curNode.getLeft() != null) {
   
            PreSelectBinaryTree(curNode.getLeft(), no);
        }
        if (curNode.getNo() != no && curNode.getRight() != null) {
   
            PreSelectBinaryTree(curNode.getRight(), no);
        }
    }
}

二叉树-删除节点

要求:

  1. 如果删除的节点是叶子结点, 则直接删除该节点
  2. 如果删除的节点是非叶子结点, 则删除该树
public void delNode(String no){
   
        if (this.left != null && this.left.no == no){
   
            this.setLeft(null);
            return;
        }
        if (this.right != null && this.right.no == no){
   
            this.setRight(null);
            return;
        }
        if (this.left != null){
   
            this.left.delNode(no);
        }
        if (this.right !=null){
   
            this.right.delNode(no);
        }
    }

public void deleteNode(String no){
   
        if (root != null){
   
            //如果只有root只有一个节点,那么就要立刻判断这个root是不是要删除的节点,否则后面就没有机会了
            if (root.getNo() == no){
   
                root = null;
            }else {
   
                root.delNode(no);
            }
        }else {
   
            System.out.println("这是一个空树,不能删除");
        }
    }

顺序存储二叉树

基本的说明:

从数据存储的方式来看, 数组存储方法和树的存储方法是可以相互转换的 ,

要求:

  1. 根据二叉树的特点, 以数组的方式存放arr[1,2,3,4,5,6,7]
  2. 要求在遍历数组的时候,仍然可以以前序遍历的方式,中序遍历的方式,后序遍历的方式完成结点

特点:

  1. 顺序存储二叉树通常只考虑完全二叉树

  2. 第N个元素的左子节点为2N+1

  3. 第N个元素的右子节点为2N+2

  4. 第N个元素的父节点为(N-1)/2

package tree;

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


class ArrBinaryTree {
   
    private int[] arr; //存储二叉树节点的数组

    public ArrBinaryTree(int[] arr) {
   
        this.arr = arr;
    }

    /**
     * 重载下面的方法
     */
    public void preOrder() {
   
        this.preOrder(0);
    }

    /**
     * 编写一个方法完成顺序存储二叉树的前序遍历
     *
     * @param index 表示数组的下标
     */
    public void preOrder(int index) {
   
        //如果数组为空 或者这个arr.length = 0;
        if (arr == null || arr.length == 0) {
   
            System.out.println("数组为空,不能按照二叉树的前序遍历");
        }
        System.out.println(arr[index]); // 输出当前的元素
        //向左递归遍历
        if (index * 2 + 1 < arr.length) {
   
            preOrder(index * 2 + 1);
        }
        //向右递归遍历
        if (index * 2 + 2 < arr.length) {
   
            preOrder(index * 2 + 2);
        }
    }

}

线索化二叉树

​ 在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化.

package tree.thread;

public class ThreadBinaryTreeDemo {
   
    public static void main(String[]
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值