笔记依照[尚硅谷·数据结构]
数据结构: 哈希表(散列)
散列表: 是根据关键码值(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()); // 输出父节点
}
}
二叉树-指定节点的查找
要求:
- 编写前中后序查找的方法
- 分别使用三种查找方法, 查找heroNo = 5的节点
- 分别使用各种查找方法, 分别比较查找了多少次
查找思路
前序思路:
- 先判断当前节点的 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);
}
}
}
二叉树-删除节点
要求:
- 如果删除的节点是叶子结点, 则直接删除该节点
- 如果删除的节点是非叶子结点, 则删除该树
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("这是一个空树,不能删除");
}
}
顺序存储二叉树
基本的说明:
从数据存储的方式来看, 数组存储方法和树的存储方法是可以相互转换的 ,
要求:
- 根据二叉树的特点, 以数组的方式存放arr[1,2,3,4,5,6,7]
- 要求在遍历数组的时候,仍然可以以前序遍历的方式,中序遍历的方式,后序遍历的方式完成结点
特点:
-
顺序存储二叉树通常只考虑完全二叉树
-
第N个元素的左子节点为2N+1
-
第N个元素的右子节点为2N+2
-
第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[]