在生活当中,我们可能每天都要进行查找工作,字典中查找,搜索引擎中查找,数据库中进行查找。在这个信息的时代下,我们每天都要从互联网上接触到很多信息。这些信息从哪里来,当然是保存在数据库中。提到数据库,大家首先想到的肯定是索引,是的数据库的优劣很大是与索引相关。而索引是为了什么,就是为了方便查找,快速从数据库中提取我们想要的数据,对于索引的优化也是一个难点。下面笔者就来介绍一下折半查找和建立二叉查找树进行查找。
折半查找:
大家知道,顺序查找(线性查找)是从线性表中的一端到另一端逐个进行比较,当数据非常大时,查找效率非常低,时间复杂度为O(n),但是它对记录的存储没有任何要求(记录不必有序)。
折半查找:必须要求记录有序,采用顺序存储,利用这个特点,所以折半查找的效率也比顺序查找高,对于数量非常大时,非常快,时间复杂度为O(logN)。
基本思想:(1)在有序数据中,去中间的记录作为比较对象,若给定的值与中间记录相等,则查找成功。
(2) 若给定的值小于中间记录,则在中间记录的左半区进行查找。
(3) 若给定的值大于中间记录,则在中间记录的右半区进行查找。
(4) 重复以上步骤,直到查找成功,若查找的区域无记录,则查找失败。
下面笔者定义了一个折半查找的类: 使用数字进行存储数据。
private Object[] array;
private int size; // 数组的大小
private BinarySearch(int max) { //通过构造函数初始化数组的大小
array = new Object[max];
size = 0;
}
插入数据: 由于折半查找要求数据必须有序,并且插入操作往往比查找操作频率更加小。所以在插入时就对数据进行排序。
/** 插入元素
*/
@SuppressWarnings("unchecked")
public void insert(T value) {
int pos = 0;
while(pos < size){
if (((Comparable<? super T>) array[pos]).compareTo(value) > 0)
break;
pos++;
}
for (int k = size; k > pos; k--) { //将比插入值大的元素后移
array[k] = array[k - 1];
}
array[pos] = value;
size++;
}
折半查找: 这里使用的是 递归进行查找,时间复杂度为O(logN)。
public int find(T searchKey) {
return reFind(searchKey, 0, size - 1);
}
/**
* 通过将关键字与查找部分的中间元素比较,并调用自身实现递归调用,直到找到关键字
*
* @param searchKey 需要寻找的关键字
* @param lower 查找部分的开始
* @param upper 查找部分的结尾
* @return 成功找到返回关键字位置,失败则返回 -1
*/
@SuppressWarnings("unchecked")
public int reFind(T searchKey, int lower, int upper) {
int current;
current = (lower + upper) / 2;
if (array[current] == searchKey) { // 找到关键字直接返回关键字位置
return current;
} else if (lower > upper) { // lower、upper交错,不能找到,直接返回数组大小
return -1;
} else {
if (((Comparable<? super T>) array[current]).compareTo(searchKey) < 0) { // 关键字在小于 array[current] 的一半查找
return reFind(searchKey, current + 1, upper);
} else {
return reFind(searchKey, lower, current - 1); // 关键字在大于array[current]的一半查找
}
}
}
测试:
package org.TT.Recursion;
import java.util.Scanner;
/** 使用递归进行二分查找。
* 递归的二分查找代码简洁,时间复杂度为O(logN)
*/
public class BinarySearch<T extends Comparable<? super T>> {
// 这里的数组需要已经排好序,在插入时就直接排序
private Object[] array;
private int size; // 数组的大小
private BinarySearch(int max) { //通过构造函数初始化数组的大小
array = new Object[max];
size = 0;
}
/** 取得查找数组的大小
*/
public int size() {
return size;
}
public int find(T searchKey) {
return reFind(searchKey, 0, size - 1);
}
/**
* 通过将关键字与查找部分的中间元素比较,并调用自身实现递归调用,直到找到关键字
*
* @param searchKey 需要寻找的关键字
* @param lower 查找部分的开始
* @param upper 查找部分的结尾
* @return 成功找到返回关键字位置,失败则返回 -1
*/
@SuppressWarnings("unchecked")
public int reFind(T searchKey, int lower, int upper) {
int current;
current = (lower + upper) / 2;
if (array[current] == searchKey) { // 找到关键字直接返回关键字位置
return current;
} else if (lower > upper) { // lower、upper交错,不能找到,直接返回数组大小
return -1;
} else {
if (((Comparable<? super T>) array[current]).compareTo(searchKey) < 0) { // 关键字在小于 array[current] 的一半查找
return reFind(searchKey, current + 1, upper);
} else {
return reFind(searchKey, lower, current - 1); // 关键字在大于array[current]的一半查找
}
}
}
/** 插入元素
*/
@SuppressWarnings("unchecked")
public void insert(T value) {
int pos = 0;
while(pos < size){
if (((Comparable<? super T>) array[pos]).compareTo(value) > 0)
break;
pos++;
}
for (int k = size; k > pos; k--) { //将比插入值大的元素后移
array[k] = array[k - 1];
}
array[pos] = value;
size++;
}
/**
* 展示需要查找的所有数组值
*/
public void dispaly() {
for (int i = 0; i < size; i++) {
System.out.print(array[i] + " ");
}
}
@SuppressWarnings("resource")
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
BinarySearch<Integer> search = new BinarySearch<>(10);
search.insert(10);
search.insert(90);
search.insert(80);
search.insert(50);
search.insert(30);
search.insert(20);
search.insert(40);
search.insert(700);
System.out.print("数组元素为: ");
search.dispaly();
System.out.println();
System.out.print("请输入需要查找的元素:");
int searchNum = in.nextInt();
if (search.find(searchNum) != -1) {
System.out.println("Found: " + searchNum);
} else {
System.out.println("Can't find " + searchNum);
}
}
}
建立二叉查找树进行查找
二叉查找树:
性质: 若它的左子树不为空,则左子树上所有节点的值均小于根节点; 若它的右子树不为空,则右子树上所有节点的值均小于根节点; 它的左右子树都是二叉查找树。
首先构造一个结点类,存储数据,并且有左孩子,右孩子的引用。在这里属性不设置成私有主要是为了便于访问,若想设为私有属性,可以设置get 、set方法访问。
package org.TT.BinarySeachTree;
/**结点数据结构*/
public class BinaryNode<T>{
T data;
BinaryNode<T> left;
BinaryNode<T> right;
public BinaryNode(T data) {
this(data,null,null);
}
public BinaryNode( T data, BinaryNode<T> left, BinaryNode<T> right) {
this.data =data;
this.left = left;
this.right =right;
}
}
查找树: 只已知一个根节点,构造函数初始化一棵空树。
判断是否为空:直接判断根节点是否为空。
清除树,直接将根节点的应用置为空即可,虚拟机可进行垃圾回收是发现引用为空,将进行垃圾回收。
<span style="white-space:pre"> </span>private BinaryNode<T> rootTree;
<span style="white-space:pre"> </span>/** 构造一颗空的二叉查找树 */
<span style="white-space:pre"> </span>public BinarySearchTree() {
<span style="white-space:pre"> </span>rootTree = null;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>/** 清空二叉查找树 */
<span style="white-space:pre"> </span>public void clear() {
<span style="white-space:pre"> </span>rootTree = null;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>/** 判断是否为空 */
<span style="white-space:pre"> </span>public boolean isEmpty() {
<span style="white-space:pre"> </span>return rootTree == null;
<span style="white-space:pre"> </span>}
插入: 每次插入寻找正确的位置并插入,保证插入后还是一棵二叉查找树。
/** 插入元素 */
public void insert(T t) {
rootTree = insert(t, rootTree); // 第一次插入时,直接创建新节点并赋值给根节点
}
/** 在某个位置开始判断插入元素,采用递归实现不断寻找插入位置,代码简洁*/
public BinaryNode<T> insert(T t, BinaryNode<T> parent) {
if (parent == null) {
return new BinaryNode<T>(t, null, null); // 创建一个新节点
}
int result = t.compareTo(parent.data); //若 t 小于 parent 返回负数, 大于返回正数,等于返回0
if (result < 0)
parent.left = insert(t, parent.left);
else if (result > 0)
parent.right = insert(t, parent.right);
else
;// 若两个元素相等,什么也做,如果有需要可以在节点记录里附加域来指示重复元素插入的信息
return parent;
}
插入过程:
查找: 每次查找与根结点比较,(1)相等则返回 (2)小于,在左子树中查找 (3)大于,在右子树中查找
/** 查找指定的元素,默认从根结点出开始查询 */
public boolean find(T t) {
return find(t, rootTree);
}
/** 从某个结点出开始查找元素,
* 用递归实现并不断将关键字与父节点比较,大于往右子树走,小于往左子树走等于返回 */
public boolean find(T t, BinaryNode<T> node) {
if (node == null)
return false;
int result = t.compareTo(node.data);
if (result > 0)
return find(t, node.right);
else if (result < 0)
return find(t, node.left);
else
return true;
}
查找最大值、最小值: 若查找最小值,直接在左子树上查找,一直往左查找。 若查找最大值,直接在右子树上查找,一直往右查找。
/** 查找二叉查找树中的最小值 */
public T findMin() {
if (isEmpty()) {
System.out.println("二叉树为空");
return null;
} else
return findMin(rootTree).data;
}
/** 查找出最小元素所在的结点 */
public BinaryNode<T> findMin(BinaryNode<T> node) {
if (node == null)
return null;
else if (node.left == null)
return node;
return findMin(node.left);// 尾递归查找
}
/** 查找二叉查找树中的最大值 */
public T findMax() {
if (isEmpty()) {
System.out.println("二叉树为空");
return null;
} else
return findMax(rootTree).data;
}
/** 查找出最大元素所在的结点 */
public BinaryNode<T> findMax(BinaryNode<T> node) {
if (node != null) {
while (node.right != null)
node = node.right;
}
return node;
}
删除: 删除结点才用递归实现。
(1) 删除结点是叶子节点,直接删除 。
(2)只有左子树、右子树,只需重新连接。
(3)删除有两个孩子的节点:用右子树最小节点代替删除节点,并删除右子树最小节点
/** 删除元素 */
public void remove(T t) {
rootTree = remove(t, rootTree);
}
/** 在某个位置开始判断删除某个结点,同样用递归实现 */
public BinaryNode<T> remove(T t, BinaryNode<T> node) {
if (node == null)
return node;
int result = t.compareTo(node.data);
if (result > 0)
node.right = remove(t, node.right);
else if (result < 0)
node.left = remove(t, node.left);
else if (node.left != null && node.right != null) {
// 删除有两个孩子的节点:用右子树最小节点代替删除节点,并删除右子树最小节点
node.data = findMin(node.right).data; // 找到删除节点右子树的最小节点
node.right = remove(node.data, node.right); // 删除右子树最小节点
} else
node = (node.left != null) ? node.left : node.right;
//删除没有子节点的节点(叶子)和只有一个节点的节点
return node;
}
遍历及测试:
package org.TT.BinarySeachTree;
/**
* 二叉查找树的性质:
* 1.对于树中的每个节点X,它的左子树中所有项的值小于X,而它的右子树中所有项的值大于X,
* Java实现二叉查找树(递归的编写二叉查找树的各种操作)
* 2.树的所有节点的平均深度为 O(logN)
* 3.所有操作的平均运行时间也是 O(logN)
* 4.此处的操作:插入结点,构造二叉查找树、清空二叉树、判断树是否为空、查找指定结点、删除结点、查找最大值、查找最小值
*/
public class BinarySearchTree<T extends Comparable<? super T>> {
private BinaryNode<T> rootTree;
/** 构造一颗空的二叉查找树 */
public BinarySearchTree() {
rootTree = null;
}
/** 清空二叉查找树 */
public void clear() {
rootTree = null;
}
/** 判断是否为空 */
public boolean isEmpty() {
return rootTree == null;
}
/** 插入元素 */
public void insert(T t) {
rootTree = insert(t, rootTree); // 第一次插入时,直接创建新节点并赋值给根节点
}
/** 在某个位置开始判断插入元素,采用递归实现不断寻找插入位置,代码简洁*/
public BinaryNode<T> insert(T t, BinaryNode<T> parent) {
if (parent == null) {
return new BinaryNode<T>(t, null, null); // 创建一个新节点
}
int result = t.compareTo(parent.data); //若 t 小于 parent 返回负数, 大于返回正数,等于返回0
if (result < 0)
parent.left = insert(t, parent.left);
else if (result > 0)
parent.right = insert(t, parent.right);
else
;// 若两个元素相等,什么也做,如果有需要可以在节点记录里附加域来指示重复元素插入的信息
return parent;
}
/** 查找指定的元素,默认从根结点出开始查询 */
public boolean find(T t) {
return find(t, rootTree);
}
/** 从某个结点出开始查找元素,
* 用递归实现并不断将关键字与父节点比较,大于往右子树走,小于往左子树走等于返回 */
public boolean find(T t, BinaryNode<T> node) {
if (node == null)
return false;
int result = t.compareTo(node.data);
if (result > 0)
return find(t, node.right);
else if (result < 0)
return find(t, node.left);
else
return true;
}
/** 查找二叉查找树中的最小值 */
public T findMin() {
if (isEmpty()) {
System.out.println("二叉树为空");
return null;
} else
return findMin(rootTree).data;
}
/** 查找出最小元素所在的结点 */
public BinaryNode<T> findMin(BinaryNode<T> node) {
if (node == null)
return null;
else if (node.left == null)
return node;
return findMin(node.left);// 尾递归查找
}
/** 查找二叉查找树中的最大值 */
public T findMax() {
if (isEmpty()) {
System.out.println("二叉树为空");
return null;
} else
return findMax(rootTree).data;
}
/** 查找出最大元素所在的结点 */
public BinaryNode<T> findMax(BinaryNode<T> node) {
if (node != null) {
while (node.right != null)
node = node.right;
}
return node;
}
/** 删除元素 */
public void remove(T t) {
rootTree = remove(t, rootTree);
}
/** 在某个位置开始判断删除某个结点,同样用递归实现 */
public BinaryNode<T> remove(T t, BinaryNode<T> node) {
if (node == null)
return node;
int result = t.compareTo(node.data);
if (result > 0)
node.right = remove(t, node.right);
else if (result < 0)
node.left = remove(t, node.left);
else if (node.left != null && node.right != null) {
// 删除有两个孩子的节点:用右子树最小节点代替删除节点,并删除右子树最小节点
node.data = findMin(node.right).data; // 找到删除节点右子树的最小节点
node.right = remove(node.data, node.right); // 删除右子树最小节点
} else
node = (node.left != null) ? node.left : node.right;
//删除没有子节点的节点(叶子)和只有一个节点的节点
return node;
}
/** 前序遍历 */
public void preOrder(BinaryNode<T> node) {
if (node != null) {
System.out.print(node.data + ",");
preOrder(node.left);
preOrder(node.right);
}
}
/** 后序遍历 */
public void postOrder(BinaryNode<T> node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.print(node.data + ",");
}
}
/** 中序遍历 */
public void inOrder(BinaryNode<T> node) {
if (node != null) {
inOrder(node.left);
System.out.print(node.data + ",");
inOrder(node.right);
}
}
public static void main(String[] args) {
int[] value = { 6,8,3,7,10,1,9 };
BinarySearchTree<Integer> tree = new BinarySearchTree<>();
for (int v : value) {
tree.insert(v);
}
System.out.println("前序遍历");
tree.preOrder(tree.rootTree);
System.out.println();
System.out.println("后序遍历");
tree.postOrder(tree.rootTree);
System.out.println();
System.out.println("中序遍历");
tree.inOrder(tree.rootTree);
System.out.println();
System.out.println("最小值==" + tree.findMin());
System.out.println("最大值==" + tree.findMax());
System.out.println("查找8: " + tree.find(8));
System.out.println("是否为空: " + tree.isEmpty());
System.out.println();
tree.remove(8);
System.out.println("删除节点值为8的节点,再中序遍历");
System.out.println("查找8: " + tree.find(8));
tree.inOrder(tree.rootTree);
}
}