文章目录
查找算法
- 顺序(线性)查找
- 二分查找/折半查找
- 插值查找
- 斐波那契查找
顺序(线性)查找
有一个数列: {1,8, 10, 89, 1000, 1234}
,判断数列中是否包含此名称【顺序查找】 要求: 如果找到了,就提示找到,并给出下标值。
代码实现:
package com.jxust.search;
public class SeqSearch {
public static void main(String[] args) {
int arr[] = {1,9,11,-1,34,89};
int index = seqSearch(arr, 11);
if(index == -1) {
System.out.println("没有找到");
}else {
System.out.println("找到了,下标为="+index);
}
}
//这里我们实现的线性查找是找到一个满足条件的值,就返回
public static int seqSearch(int[] arr,int value) {
//线性查找是逐一比对,发现有相同值,就返回下标
for(int i=0;i<arr.length;i++) {
if(arr[i] == value) {
return i;
}
}
return -1;
}
}
二分查找
请对一个有序数组进行二分查找 {1,8, 10, 89, 1000, 1234}
,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
思路分析:
- 首先确定该数组的中间的下标
mid = (left + right) / 2
- 然后让需要查找的数 findVal 和 arr[mid] 比较
①findVal > arr[mid]
, 说明你要查找的数在mid 的右边, 因此需要递归的向右查找
②findVal < arr[mid]
, 说明你要查找的数在mid 的左边, 因此需要递归的向左查找
③findVal == arr[mid]
说明找到,就返回
什么时候我们需要结束递归?
①找到就结束递归
②递归完整个数组,仍然没有找到findVal ,也需要结束递归 。当left > right
就需要退出
代码实现:
package com.jxust.search;
//注意:使用二分查找的前提是该数组是有序的
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1,8,10,89,1000,1234};
int resIndex = binarySearch(arr, 0, arr.length -1, 89);
if(resIndex == -1) {
System.out.println("没有这个数");
}else {
System.out.println("resIndex = "+resIndex);
}
}
//二分查找算法
/**
*
* @param arr 数组
* @param left 左边的索引
* @param right 右边的索引
* @param findVal 要查找的值
* @return 如果找到就返回下标,如果没有找到,就返回-1
*/
public static int binarySearch(int[] arr,int left,int right,int findVal) {
//当left>right 时,说明递归整个数组但没有找到
if(left>right) {
return -1;
}
int mid = (left+right)/2;
int midVal = arr[mid];
if(findVal > midVal) {//向右递归
return binarySearch(arr, mid+1, right, findVal);
}else if(findVal < midVal) { //向左递归
return binarySearch(arr, left, mid-1, findVal);
}else {
return mid;
}
}
}
优化、完善:
{1,8, 10, 89, 1000, 1000,1234}
当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000。
思路分析:
- 在找到mid索引值后,不要马上返回
- 向mid索引值的左边扫描,将所有满足1000的元素的下标加入到集合ArrayList中
- 向mid索引值的右边扫描,将所有满足1000的元素的下标加入到集合ArrayList中
- 将ArrayList返回
代码实现:
package com.jxust.search;
import java.util.ArrayList;
import java.util.List;
//注意:使用二分查找的前提是该数组是有序的
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1,8,10,89,1000,1000,1000,1000,1234};
List<Integer> resIndexList = binarySearch2(arr, 0, arr.length -1, 1000);
System.out.println("resIndexList = "+resIndexList);
}
//有多个相同数值时,如何将所有的数值都找到
public static List<Integer> binarySearch2(int[] arr,int left,int right,int findVal) {
//当left>right 时,说明递归整个数组但没有找到
if(left>right) {
return new ArrayList<Integer>();
}
int mid = (left+right)/2;
int midVal = arr[mid];
if(findVal > midVal) {//向右递归
return binarySearch2(arr, mid+1, right, findVal);
}else if(findVal < midVal) { //向左递归
return binarySearch2(arr, left, mid-1, findVal);
}else {
// 1、在找到mid索引值后,不要马上返回
List<Integer> resIndexlist = new ArrayList<Integer>();
//2、向mid索引值的左边扫描,将所有满足1000的元素的下标加入到集合ArrayList中
int temp = mid-1;
while(true) {
if(temp<0 || arr[temp]!= findVal) { //退出
break;
}
//否则,就把temp放入到resIndexlist中
resIndexlist.add(temp);
temp -= 1; //temp左移
}
resIndexlist.add(mid);
//3、向mid索引值的右边扫描,将所有满足1000的元素的下标加入到集合ArrayList中
temp = mid+1;
while(true) {
if(temp>arr.length-1 || arr[temp]!= findVal) { //退出
break;
}
//否则,就把temp放入到resIndexlist中
resIndexlist.add(temp);
temp += 1; //temp左移
}
return resIndexlist;
}
}
}
插值查找
原理介绍: 插值查找算法也要求数组是有序的
插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
将折半查找中的求mid 索引的公式 :int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
注意:
- 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
- 关键字分布不均匀的情况下,该方法不一定比折半查找要好
代码实现:
package com.jxust.search;
import java.util.Arrays;
public class InsertValueSearch {
public static void main(String[] args) {
int[] arr = new int[100];
for(int i=0;i<100;i++) {
arr[i] = i+1;
}
int index = insertValueSearch(arr, 0, arr.length-1, 100);
System.out.println("index = "+index);
// System.out.println(Arrays.toString(arr));
}
//插值查找算法
//插值查找算法也要求数组是有序的
//如果找到了就返回下标,如果没有找到就返回-1
public static int insertValueSearch(int[] arr,int left,int right,int findVal)
{
//注意: findVal < arr[0] || findVal > arr[arr.length - 1]是必要的,否则我们得到的mid可能越界
if(left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
//求出mid
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if(findVal > midVal) { //说明要向右递归
return insertValueSearch(arr, mid+1, right, findVal);
}else if(findVal < midVal) { //说明要向左递归
return insertValueSearch(arr, left, mid-1, findVal);
}else {
return mid;
}
}
}
斐波那契(黄金分割法)查找算法
原理:
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1
(F代表斐波那契数列)
对F(k-1)-1
的理解:
①由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F(k-1)-1
②类似的,每一子段也可以用相同的方式分割
③但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1。这里的k值只要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1到F[k]-1位置),都赋为n位置的值即可。
while(n>fib(k)-1)
k++;
代码实现:
package com.jxust.search;
import java.util.Arrays;
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int [] arr = {1,8, 10, 89, 1000, 1234};
System.out.println("index=" + fibSearch(arr, 1));// 0
}
//因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
//非递归方法得到一个斐波那契数列
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
//编写斐波那契查找算法
//使用非递归的方式编写算法
/**
*
* @param a 数组
* @param key 我们需要查找的关键码(值)
* @return 返回对应的下标,如果没有-1
*/
public static int fibSearch(int[] a, int key) {
int low = 0;
int high = a.length - 1;
int k = 0; //表示斐波那契分割数值的下标
int mid = 0;
int f[] = fib(); //获取到斐波那契数列
//获取到斐波那契分割数值的下标
while(high > f[k] - 1) {
k++;
}
//因为 f[k] 值 可能大于 a 的 长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp[]
//不足的部分会使用0填充
int[] temp = Arrays.copyOf(a, f[k]);
//实际上需求使用a数组最后的数填充 temp
//temp = {1,8, 10, 89, 1000, 1234, 0, 0} => {1,8, 10, 89, 1000, 1234, 1234, 1234}
for(int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
// 使用while来循环处理,找到我们的数 key
while (low <= high) {
mid = low + f[k - 1] - 1;
if(key < temp[mid]) { //我们应该继续向左查找
high = mid - 1;
//为什么是 k--
/*1. 全部元素 = 前面的元素 + 后边元素
2. f[k] = f[k-1] + f[k-2]
因为 前面有 f[k-1]个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3]
即 在 f[k-1] 的前面继续查找 k--
即下次循环 mid = f[k-1-1]-1
*/
k--;
} else if ( key > temp[mid]) { // 我们应该继续向右查找
low = mid + 1;
//为什么是k -=2
//说明
//1. 全部元素 = 前面的元素 + 后边元素
//2. f[k] = f[k-1] + f[k-2]
//3. 因为后面我们有f[k-2] 所以可以继续拆分 f[k-2] = f[k-3] + f[k-4]
//4. 即在f[k-2] 的前面进行查找 k -=2
//5. 即下次循环 mid = f[k - 1 - 2] - 1
k -= 2;
} else {
//需要确定,返回的是哪个下标
if(mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}
哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
示例:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时,要求查找到该员工的 所有信息。
思路分析:
代码实现:
package com.jxust.hashTab;
import java.util.Scanner;
public class HashTabDemo {
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("list:显示雇员");
System.out.println("find:查找雇员");
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 "list":
hashTab.list();
break;
case "exit":
scanner.close();
System.exit(0);
case "find":
System.out.println("请输入要查找的id");
id = scanner.nextInt();
hashTab.findEmpById(id);
break;
default:
break;
}
}
}
}
//表示一个雇员
class Emp{
public int id;
public String name;
public Emp next;
public Emp(int id,String name) {
super();
this.id = id;
this.name = name;
}
}
//创建HashTab管理多条链表
class HashTab{
private EmpLinkedList[] empLinkedListArray;
private int size; //表示共有多少条链表
//构造器
public HashTab(int size) {
this.size = size;
//初始化empLinkedListArray
empLinkedListArray = new EmpLinkedList[size];
//?,这是分别初始化每个链表
for(int i=0;i<size;i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp) {
//根据员工的id,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
//遍历所有链表,遍历hashTab
public void list() {
for(int i=0;i<size;i++) {
empLinkedListArray[i].list(i);
}
}
//根据输入的id查找雇员
public void findEmpById(int id) {
//使用散列函数确定到哪条链表查找
int empLinkedListNO = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
if(emp != null) { //找到
System.out.printf("在第%d条链表中找到雇员id = %d\n",(empLinkedListNO+1),id);
}else {
System.out.println("在哈希表中,没有找到该雇员");
}
}
//编写散列函数,使用取模法
public int hashFun(int id) {
return id%size;
}
}
//创建EmpLinkedList,表示链表
class EmpLinkedList{
//头指针,指向第一个Emp
private Emp head; //默认为null
//添加雇员到链表
//假定,当添加雇员时,id是自增长,即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;//后移
}
//退出时直接将emp加入链表
curEmp.next = emp;
}
//遍历链表的雇员信息
public void list(int no) {
if(head == null) { //说明链表为空
System.out.println("第 "+(no+1)+" 链表为空");
return;
}
System.out.print("第 "+(no+1)+" 链表信息为:");
Emp curEmp = head; //辅助指针
while(true) {
System.out.printf("=> id=%d name=%s\t",curEmp.id,curEmp.name);
if(curEmp.next == null) {//说明curEmp已经是最后结点
break;
}
curEmp = curEmp.next; //后移,遍历
}
System.out.println();
}
//根据id查找雇员
//如果找到,就返回Emp,如果没有找到,就返回null
public Emp findEmpById(int id) {
//判断链表是否为空
if(head == null) {
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while(true) {
if(curEmp.id == id) { //找到
break; //这时curEmp就只想要查找的雇员
}
//退出
if(curEmp.next == null) { //说明遍历当前链表并没有找到该雇员
curEmp = null;
break;
}
curEmp = curEmp.next;
}
return curEmp;
}
}
二叉树
为什么需要树这种数据结构
-
数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 -
链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) -
树存储方式的分析
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
树的示意图
二叉树的概念
- 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
- 二叉树的子节点分为左节点和右节点。
- 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
前序、后序、中序遍历
前序遍历: 先输出父节点,再遍历左子树和右子树
中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
小结: 看输出父节点的顺序,就确定是前序,中序还是后序
使用前序,中序和后序对下面的二叉树进行遍历:
**思路分析:**二叉树前序、中序、后序遍历步骤
- 创建一棵二叉树
- 前序遍历
①先输出当前节点(初始的时候是root)
②如果左子节点不为空,则递归前序遍历
③如果右子节点不为空,则递归前序遍历 - 中序遍历
①如果当前节点的左子节点不为空,则递归中序遍历
②输出当前节点
③如果当前节点的右子节点不为空,则递归中序遍历 - 后序遍历
①如果当前节点的左子节点不为空,则递归后序遍历
②如果当前节点的右子节点不为空,则递归后序遍历
③输出当前节点
代码实现:
package com.jxust.tree;
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);
System.out.println("前序遍历");
binaryTree.preOrder();
System.out.println("中序遍历");
binaryTree.infixOrder();
System.out.println("后序遍历");
binaryTree.postOrder();
}
}
//定义一个二叉树
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 postOrder() {
if (this.root != null) {
this.root.postOrder();
} else {
System.out.println("二叉树为空,无法遍历!");
}
}
}
//先创建HeroNode结点
class HeroNode{
private int no;
private String name;
private HeroNode left; //默认为null
private HeroNode right; //默认为null
public HeroNode(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 HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode [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 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);
}
}
二叉树-查找指定结点
思路分析:
- 前序查找:
①先判断当前结点的no是否等于要查找的no
②如果相等,则返回当前结点
③如果不等,则判断当前结点的左子节点是否为空,不过不为空则递归前序查找
④如果左递归前序查找,找到结点,则返回,否则继续判断,当前结点的右子节点是否为空,如果不为空,则继续向右递归前序查找 - 中序查找
①判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
②如果找到则返回,如果没有找到,就和当前结点比较,如果相等就返回当前结点,否则继续向右递归中序查找
③如果有递归中序查找,找到就返回,否则返回null - 后序查找
①判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
②如果找到则返回,如果没有找到,判断当前结点的右子节点是否为空,如果不为空,则有递归进行后序查找,如果找到就返回
③和当前结点比较,如果相等返回,否则返回null
代码实现:
package com.jxust.tree;
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);
//前序遍历查找
System.out.println("前序遍历查找");
HeroNode resNode = binaryTree.preOrderSearch(5);
if(resNode != null) {
System.out.printf("找到了,信息为no=%d name=%s",resNode.getNo(),resNode.getName());
}else {
System.out.printf("没有找到no=%d的英雄",5);
}
}
}
// 定义一个二叉树
class BinaryTree {
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
// 前序遍历查找
public HeroNode preOrderSearch(int no) {
if (root != null) {
return root.preOrderSearch(no);
} else {
return null;
}
}
// 中序遍历查找
public HeroNode infixOrderSearch(int no) {
if (root != null) {
return root.infixOrderSearch(no);
} else {
return null;
}
}
// 后序遍历查找
public HeroNode postOrderSearch(int no) {
if (root != null) {
return root.postOrderSearch(no);
} else {
return null;
}
}
}
// 先创建HeroNode结点
class HeroNode {
private int no;
private String name;
private HeroNode left; // 默认为null
private HeroNode right; // 默认为null
public HeroNode(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 HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
// 前序遍历查找
public HeroNode preOrderSearch(int no) {
// 比较当前结点是不是
if (this.no == no) {
return this;
}
// 判断左子节点是否为空,如果不为空则递归前序遍历
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.preOrderSearch(no);
}
if (resNode != null) { // 说明左子树找到了
return resNode;
}
// 判断右子节点是否为空,如果不为空则递归前序遍历
if (this.right != null) {
resNode = this.right.preOrderSearch(no);
}
return resNode;
}
// 中序遍历查找
public HeroNode infixOrderSearch(int no) {
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.infixOrderSearch(no);
}
if (resNode != null) {
return resNode;
}
if (this.no == no) {
return this;
}
// 右递归
if (this.right != null) {
resNode = this.right.infixOrderSearch(no);
}
return resNode;
}
// 后序遍历查找
public HeroNode postOrderSearch(int no) {
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.postOrderSearch(no);
}
if (resNode != null) {
return resNode;
}
if (this.right != null) {
resNode = this.right.postOrderSearch(no);
}
if (resNode != null) {
return resNode;
}
// 如果左右子树都没找到,就比较当前结点
if (this.no == no) {
return this;
}
return resNode;
}
}
二叉树-删除节点
要求:
①如果删除的节点是叶子节点,则删除该节点
②如果删除的节点是非叶子节点,则删除该子树
思路分析:
如果树是空树root,如果只有一个root结点,则等价将二叉树置空
- 二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除
- 如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将
this.left = null
,并且返回(结束递归删除) - 如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将
this.right = null
,并且返回(结束递归删除) - 如果第2步和第3步都没有删除结点,那我们就需要对左子树进行递归删除
- 如果第4步也没有删除结点,则应当向右子树进行递归删除
代码实现:
package com.jxust.tree;
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);
System.out.println("删除前,前序遍历");
binaryTree.preOrder();
binaryTree.delNode(5);
System.out.println("删除后,前序遍历");
binaryTree.preOrder();
}
}
// 定义一个二叉树
class BinaryTree {
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//删除结点
public void delNode(int no) {
if(root != null) {
//如果只有一个root结点,立即判断root是否为要删除的节点
if(root.getNo() == no) {
root = null;
}else {
//递归删除
root.delNode(no);
}
}else {
System.out.println("空树,不能删除");
}
}
// 前序遍历
public void preOrder() {
if (this.root != null) {
this.root.preOrder();
} else {
System.out.println("二叉树为空,无法遍历!");
}
}
}
// 先创建HeroNode结点
class HeroNode {
private int no;
private String name;
private HeroNode left; // 默认为null
private HeroNode right; // 默认为null
public HeroNode(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 HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
// 递归删除结点
// 如果删除的节点是叶子节点,则删除该节点
// 如果删除的节点是非叶子节点,则删除该子树.
public void delNode(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.delNode(no);
}
// 向右子树进行递归删除
if (this.right != null) {
this.right.delNode(no);
}
}
// 前序遍历的方法
public void preOrder() {
System.out.println(this);// 先输出父结点
// 递归向左子树前序遍历
if (this.left != null) {
this.left.preOrder();
}
// 递归向右子树前序遍历
if (this.right != null) {
this.right.preOrder();
}
}
}
顺序存储二叉树
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
要求:
①要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 6]
②要求在遍历数组 arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
顺序存储二叉树的特点:
①顺序二叉树通常只考虑完全二叉树
②第n个元素的左子节点为 2 * n + 1
③第n个元素的右子节点为 2 * n + 2
④第n个元素的父节点为 (n-1) / 2
⑤n : 表示二叉树中的第几个元素(按0开始编号)
示例:需求: 给你一个数组 {1,2,3,4,5,6,7}
,要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7
代码实现:
package com.jxust.tree;
public class ArrBinaryTree {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
//创建一个ArrayBinaryTree
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.preOrder();
}
}
//编写一个ArrayBinaryTree,实现顺序存储二叉树遍历
class ArrayBinaryTree{
private int[] arr; //存储数据结点的数组
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
//重载preOrder
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(2*index+1);
}
//向右递归遍历
if((index*2+2)<arr.length) {
preOrder(index*2+2);
}
}
}
线索化二叉树
线索二叉树基本介绍
① n个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
② 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
③一个结点的前一个结点,称为前驱结点
④一个结点的后一个结点,称为后继结点
示例:
思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}
说明: 当线索化二叉树后,Node节点的属性 left 和 right ,有如下情况:
- left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
- right指向的是右子树,也可能是指向后继节点,比如 ① 节点right 指向的是右子树,而⑩ 节点的right 指向的是后继节点.
代码实现:
package threadedbinarytree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
HeroNode root = new HeroNode(1, "Tom");
HeroNode node2 = new HeroNode(3, "jack");
HeroNode node3 = new HeroNode(6, "smith");
HeroNode node4 = new HeroNode(8, "mary");
HeroNode node5 = new HeroNode(10, "king");
HeroNode node6 = new HeroNode(14, "dim");
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//中序线索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadedNodes();
//测试,以10号结点为例
HeroNode leftNode = node5.getLeft();
HeroNode rightNode = node5.getRight();
System.out.println("10号结点的前驱结点是"+leftNode);
System.out.println("10号结点的后继结点是"+rightNode);
System.out.println("使用线索化的方式遍历线索化二叉树");
threadedBinaryTree.threadedList();
}
}
//定义一个二叉树
class ThreadedBinaryTree {
private HeroNode root;
//为了实现线索化,需要创建当前结点的前驱结点的指针
//在递归进行线索化时,pre总是保留前一个结点
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
//重载threadedNodes方法
public void threadedNodes() {
this.threadedNodes(root);
}
//遍历中序线索二叉树
public void threadedList() {
//定义一个变量,存储当前遍历的结点,从root开始
HeroNode node = root;
while(node != null) {
//循环找到leftType == 1 的结点,第一个找到的就是8这个结点
//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();
}
}
//编写对二叉树进行中序线索化方法
/**
*
* @param node 当前需要线索化的结点
*/
public void threadedNodes(HeroNode node) {
//如果node == null,不能线索化
if(node == null) {
return;
}
//1.先线索化左子树
threadedNodes(node.getLeft());
//2.线索化当前结点
//处理当前结点的前驱结点
if(node.getLeft() == null) {
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针
node.setLeftType(1);
}
//处理后继结点
if(pre != null && pre.getRight() == null) {
//让前驱结点的右指针指向当前结点
pre.setRight(node);
//修改前驱结点的右指针类型
pre.setRightType(1);
}
//每处理一个结点后,让当前结点是下一个结点的前驱结点
pre = node;
//3.线索化右子树
threadedNodes(node.getRight());
}
}
//创建HeroNode结点
class HeroNode {
private int no;
private String name;
private HeroNode left; // 默认为null
private HeroNode right; // 默认为null
/*说明:
* 1.如果leftType == 0 表示指向的是左子树,如果是1则表示指向的是前驱结点
* 2.如果rightType == 0 表示指向的是右子树,如果是1则表示指向的是后继结点
*/
private int leftType;
private int rightType;
public HeroNode(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 HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
}