哈希表
哈希表的基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(key value)而直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散数列
哈希表主要用在一些应用程序和数据库之间以提高数据查询速率的手段,为了提高程序与数据库之间的交互速率,一般在他俩中间会有一个缓存层,缓存层缓存了一些用户经常用到的数据,这样在用户每次查找的时候就不用程序在到数据库中进行海量的查找了,缓存层一般会用Redishe Memcache,也可以使用自己写的哈希表,数组 + 链表,数组 + 二叉树。
其实哈希表存储数据就是把数据模它的哈希表大小,在把得到的模作为此数据存储的链表编号,这样同过取模就可以快速定位到数据应该处在哪条链表上,理解了这一点其他的也就很容易理解了,哈希表的各种操作归根结底还是对链表的操作,只是它哈希表是要操作很多条链表而已
代码实现:
//创建hashTable来管理哈希表
class hashTable {
private int size;//表示hash表中有多少条链表
//创建一个hash表,即链表数组
private nodeLink[] linkList;
//带参构造方法
public hashTable(int size) {
//初始化链表
this.size = size;
linkList = new nodeLink[size];
//初始化每个hash表
for(int i = 0;i < size;i++) {
linkList[i] = new nodeLink();
}
}
//向hash表中添加元素
public void add(node Node) {
//找到元素应该插入的链表下标
int insertSite = hashFun(Node.id);
//将元素插入到相应的位置
linkList[insertSite].add(Node);
}
//遍历hash表的元素
public void list() {
//循环遍历hash表中的每一个链表
for(int i = 0;i < size;i++) {
linkList[i].list(i);
}
}
//查找元素
public void findNode(int id) {
//首先确定查找的元素应该在那一条链表里
int number = hashFun(id);
//在相应的链表里进行查找
node node = linkList[number].findNode(id);
if(node == null) {
System.out.println("没有找到相应的元素");
}
else{
System.out.println(node.value);
}
}
//删除元素
public void delete(int id) {
//确定删除元素应该处在的链表标号
int number = hashFun(id);
linkList[number].delete(id);
}
//用简单的取模方法编写简单的散列函数,用于找到插入的元素应该放在hash表的什么位置(放在哪条链表里)
public int hashFun(int id) {
return id % size;
}
}
//表示链表的每个结点
class node {
int id;
int value;
node next;
public node(int id,int value) {
this.id = id;
this.value = value;
}
}
//表示每条链表
class nodeLink {
private node head;
//向链表中添加元素
public void add(node Node) {
if(head == null) {
head = Node;
return ;
}
//创建一个指针用于查找插入的位置
node temp = head;
//对链表进行循环,找插入位置
while(true) {
//插入的位置是链表的末尾位置
if(temp.next == null)
break;
temp = temp.next;
}
//将目标值插入到指定位置
temp.next = Node;
}
//遍历链表的信息
public void list(int no) {
if(head == null) {
System.out.println("第"+ no +"链表为空");
return ;
}
node temp = head;
while(true) {
if(temp == null)
break;
System.out.println(temp.value);
temp = temp.next;
}
}
//查找元素
public node findNode(int id) {
//判断此链表是否为空
if(head == null) {
System.out.println("链表为空");
return null;
}
node temp = head;
while(true) {
if(temp.id == id) {
break;
}
if(temp.next == null) {
temp = null;
break;
}
temp = temp.next;
}
return temp;
}
//删除元素
public void delete(int id) {
if(head == null) {
System.out.println("找不到删除的数据");
return ;
}
if(head.id == id) {
head = null;
return ;
}
//定义指针,用于查找并删除结点
node temp = head;
node tempNext = head.next;
while(true) {
if(tempNext == null) {
System.out.println("找不到删除的数据");
return ;
}
if(tempNext.id == id)
break ;
}
//进行删除操作
temp.next = tempNext.next;
System.out.println("删除成功");
return ;
}
}
二叉树
为什么会需要树这种数据结构
数组,链表,树三种数据结构的比较
(1)数组存储方式的分析
优点:通过下标方式访问元素,速度快,对于有序数组,还可以使用微分查找提高检索速度。
缺点:如果要检索某个具体的值,或者插入值(按照一定顺序)会整体移动,效率较低
(2)链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值结点,只需要将插入结点,链接到链表中即可,删除效率也很好)
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头结点开始遍历)
(3)树存储方式的分析
能提高数据存储,读取的效率,比如用二叉排序树,既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度
树所需要知道的一些基本概念:
(1)结点:A,B,D,E…这些被称之为结点
(2)根结点:像A这种没有任何结点指向它的叫做根结点
(3)父结点:A是B和C的父节点,B又是D和E的父节点,像这种它直接指向谁,他就是谁的父节点
(4)子节点:B是A的子节点,C是A的子节点,像这种是直接谁指向的它,它就是谁的子节点
(5)叶子结点:像H,E,F,G这种没有子节点的结点
(6)结点的权:就是结点的值
(7)路径(从root结点找到某结点的路线)
(8)层:右边黄色部分标注的
(9)子树:一整棵树的一部分,但并不是一个树的全部
(10)树的高度:最大层数
(11)森林:多棵子树构成森林
二叉树:每个结点最多只能有两个子节点的树
二叉树的结点分为左节点和右节点
如果该二叉树的所有叶子结点都在最后一层,并且结点总数=2^n-1,n为层数,则我们称之为满二叉树
如果二叉树的所有叶子结点都在最后一层或者倒数第二层,而且最后一层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续,我们称之为 王权二叉树
二叉树的前,中,后序遍历
步骤
1.创建一棵二叉树
2.前序遍历
2.1先输出当前结点(初始的时候是root结点)
2.2如果左子节点不为空,则递归继续前序遍历
2.3如果右子节点不为空,则递归继续前序遍历
3.中序遍历
3.1如果当前结点的左子节点不为空,则递归中序遍历
3.2输出当前结点
3.3如果当前结点的右子节点不为空,则递归中序遍历
4.后序遍历
4.1如果当前结点的左子节点不为空,则递归后序遍历
4.2如果当前结点的右子节点不为空,则递归后序遍历
4.3输出当前结点
class BinaryTree {
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 infixOrder() {
if(this.root != null)
this.root.infixOrder();
else
System.out.println("这个二叉树是空的");
}
//后序遍历
public void postOrder() {
if(this.root != null)
this.root.postOrder();
else
System.out.println("这个二叉树是空的");
}
}
class Node {
private int ID;
private char Code;
//左结点
private Node left;
//右节点
private Node right;
public Node(int id , char code) {
ID = id;
Code = code;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public char getCode() {
return Code;
}
public void setCode(char code) {
Code = code;
}
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{" +
"ID=" + ID +
", Code=" + Code +
'}';
}
//前序遍历
public void preOrder() {
//先输出自己本身
System.out.println(this);
//在进行左递归前序遍历
if(this.left != null) {
this.left.preOrder();
}
//在进行右递归前序遍历
if(this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
//先左递归中序遍历
if(this.left != null)
this.left.infixOrder();
//在输出自己
System.out.println(this);
//在右递归中序遍历
if(this.right != null)
this.right.infixOrder();
}
//后序遍历
public void postOrder() {
//先左递归后序遍历
if(this.left != null)
this.left.postOrder();
//在进行右递归后序遍历
if(this.right != null) {
this.right.postOrder();
}
//最后在输出自身
System.out.println(this);
}
}
public static void main(String[] args) {
Node root = new Node(1,'A');
Node node1 = new Node(2,'B');
Node node2 = new Node(3,'C');
Node node3 = new Node(4,'D');
BinaryTree N = new BinaryTree();
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
N.setRoot(root);
}
前序输出顺序
中序输出顺序
后序输出顺序
二叉树的查找
上面知道了怎么对二叉树进行遍历,那么查找相信也很简单,就只需要在每一次递归中输出结点的语句改成判断语句,判断此结点是否为目标值即可。
二叉树删除结点
(1)如果删除的结点是叶子结点,则删除该结点
(2)如果删除的结点是非叶子结点,则删除该子树
代码实现:
//删除结点
class BinaryTree {
public void delete(int id) {
if(root != null) {
if(root.getID() == id) {
root = null;
} else {
root.delete(id);
}
} else {
System.out.println("此二叉树为空,无法进行删除操作");
}
}
}
class Node {
//结点的删除
public void delete(int id) {
//先判断其右子节点是否为空,在判断其是否为删除的目标结点
if(this.left != null &&this.left.ID == id) {
this.left = null;
return ;
}
//在对右子节点进行相同的操作
if(this.right != null && this.right.ID == id) {
this.right = null;
return ;
}
//如果没有找到相应的目标结点,就进行递归查找
//左子节点递归
if(this.left != null) {
this.left.delete(id);
}
//右子节点递归
if(this.right != null) {
this.right.delete(id);
}
return ;
}
}
顺序存储二叉树
概念:
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树叶可以转换成数组,
如上图的二叉树结点,要求以数组的方式来存放arr:[1,2,3,4,5,6,7]
要求在遍历数组arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
顺序存储二叉树的特点:
(1)顺序二叉树通常只考虑完全二叉树
(2)第n个元素的左子节点为2n+1
(3)第n个元素的右子节点为2n+2
(4)第n个元素的父结点为(n-1)/2
(5)n:表示二叉树中的第几个元素(按0开始编号,如图所示)
代码实现:
class ArrBinaryTree {
private int[] arr;
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
//前序遍历顺序存储二叉树
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(index*2+1);
}
//递归此结点的右子节点
if(index*2+2 < arr.length) {
preOrder(index*2+2);
}
}
//中序遍历顺序存储二叉树
public void infixOrder(int index) {
if(arr == null || arr.length == 0) {
System.out.println("顺序二叉树是空的");
}
//递归此结点的左子节点
if(index*2+1 < arr.length) {
preOrder(index*2+1);
}
//直接输出此结点
System.out.println(arr[index]);
//递归此结点的右子节点
if(index*2+2 < arr.length) {
preOrder(index*2+2);
}
}
//后序遍历顺序存储二叉树
public void postOrder(int index) {
if(arr == null || arr.length == 0) {
System.out.println("顺序二叉树是空的");
}
//递归此结点的左子节点
if(index*2+1 < arr.length) {
preOrder(index*2+1);
}
//递归此结点的右子节点
if(index*2+2 < arr.length) {
preOrder(index*2+2);
}
//直接输出此结点
System.out.println(arr[index]);
}
}
线索化二叉树
线索化二叉树有何作用:
就是充分利用每个结点的左右指针,不造成资源的浪费
(1)n个结点的二叉树中含有n+1【公式2n-(n-1) = n+1】个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为“线索”)
(2)这种加上了线索的二叉树链表称之为线索链表,相应的二叉树成为线索二叉树(Threaded Binary Tree).根据线索性质的不同,线索二叉树可分为前序线索二叉树,中序线索二叉树和后序线索二叉树三种
(3)一个结点的前一个结点,称之为前驱结点
(4)一个结点的后一个结点,称之为后继结点
例如一个二叉树前序遍历的顺序为【1,2,4,5,3,6,7】,那么1就没有去前驱结点,2就是1的后继结点,而对于2来说1就是他的前驱结点,4就是他的后继结点
说明:当线索化二叉树后,Node结点的属性left和right有如下情况
(1)left指向的是左子树,也可能是指向的前驱结点,比如1结点left指向的左子树,而10结点的left指向的就是前驱结点
(2)right指向的是右子树,也可能是指向后继结点,比如1结点right指向的是右子树,而10结点的right指向的是后继结点
代码实现:
此代码为中序线索化二叉树的模板,如果想实现前序,后序线索化二叉树只有需要将ThreadNode方法中的,左递归遍历线索化,线索化当前结点,右递归遍历线索化,三者的顺序调换一下即可
class ThreadBinaryTree {
private threadNode root;
//记录前驱结点
private threadNode pre = null;
public void setRoot(threadNode root) {
this.root = root;
}
public void ThreadNode() {
this.ThreadNode(root);
}
//为二叉树线索化,node为线索化的结点
public void ThreadNode (threadNode node) {
if(node == null) {
return ;
}
//左递归遍历线索化
ThreadNode(node.getLeft());
//线索化当前结点
//先线索化左指针
if(node.getLeft() == null) {
node.setLeft(pre);
//改变leftType
node.setLeftType(0);
}
//线索化右指针是应该在下一次循环里面来进行,因为我们在线索化某一个点的时候都要先确定它的前驱结点,
//理所当然,我们确定他的后驱结点就要到他的后驱结点进行更改
if(pre != null && pre.getRight() == null) {
pre.setRight(node);
//改变rightType
pre.setRightType(0);
}
//将现在的结点设置为前驱结点
pre = node;
//右递归遍历线索化
ThreadNode(node.getRight());
}
//遍历线索化二叉树
public void threadList() {
threadNode temp = root;
while(temp != null) {
//先根据leftType找到开始的那个点
while (temp.getLeftType() == 1 ) {
temp = temp.getLeft();
}
//输出当前结点
System.out.println(temp);
//在向右边都是后继结点那么就一直输出
while(temp.getRightType() == 0) {
temp = temp.getRight();
System.out.println(temp);
}
//替换这个遍历的结点
temp = temp.getRight();
}
}
}
class threadNode {
private int ID;
private char Code;
//左结点
private threadNode left;
//右节点
private threadNode right;
//左指针的状态,因为在线索二叉树里,left和right指针会指向前后驱结点,坐着左右子节点,所以要确定他们到底代表什么意思
private int leftType = 1;
//右指针的状态,我们默认设定1时为左右子节点,0时为前后驱结点,所以要将这些type的初始值设为1
private int rightType = 1;
public threadNode(int id , char code) {
ID = id;
Code = code;
}
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 getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public char getCode() {
return Code;
}
public void setCode(char code) {
Code = code;
}
public threadNode getLeft() {
return left;
}
public void setLeft(threadNode left) {
this.left = left;
}
public threadNode getRight() {
return right;
}
public void setRight(threadNode right) {
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"ID=" + ID +
", Code=" + Code +
'}';
}
}