二分法与二叉树的 Java 实现
算法与数据结构始终是计算机基础的重要一环,今天我们来讨论下 Java 中二叉树的实现以及一些简单的小算法,如二分查找,归并排序等。
-
二分查找
二分查找是一种在有序数组中查找某一特定元素的搜索算法,它在开发中应用的也是非常广泛,需要注意的是二分法是建立在有序数组基础上的快速查找,所以一般需要先对数组进行排序。
-
算法思想
-
搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;
-
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较;
-
如果在某一步骤数组为空,则代表找不到,如果找到则返回
这种搜索算法的特点是每一次比较都使搜索范围缩小一半。
-
实现思路
-
找出位于数组中间的值,并存放在一个变量中(为了下面的说明,变量暂时命名为 base 基准值);
-
需要找到的 key 和 base 进行比较;
-
如果 key 值大于 base,则把数组中间位置作为下一次计算的起点;重复 1 2;
-
如果 key 值小于 base,则把数组中间位置作为下一次计算的终点;重复 1 2;
-
如果 key 值等于 base,则返回数组下标,完成查找。
-
代码如下:
/**
* 二分法查找 : 在有序数组中查找特定元素的算法。(有序数组)
*/
public class TwoSplitSearch {
public static void main(String[] args) {
int[] data = {-10, -3, 1, 4, 6, 8, 10, 22, 33, 44, 100, 203};
//存在
System.out.println(towSplitSearch(data, 0, data.length - 1,4));
System.out.println(towSplitSearch(data, 0, data.length - 1,9));
}
/**
* 二分查找
*
* @param data 有序数组
* @param from 开始下标
* @param to 终止下标
* @param key 目标值
* @return int 目标值的下标,如果没有返回 -1
*/
private static int towSplitSearch(int[] data, int from, int to, int key) {
if (from < 0 || to < 0) {
return -1;
}
// 判断 from 不能大于 to
if(from <= to ){
//获取数组中间下标
int centerIndex = (from + to) / 2;
//将数组中间值作为基准值比较
int base = data[centerIndex];
//目标值比基准值大,则需要往中间下标后查找,起始点为 centerIndex + 1
if (key > base) {
from = centerIndex + 1;
//目标值比基准值小,则需要往中间下标1前查找,终止点为 centerIndex - 1
} else if (key < base) {
to = centerIndex - 1;
} else {
return centerIndex;
}
}else{
return -1;
}
return towSplitSearch(data, from, to, key);
}
}
在了解二分法之后,还需要知道一些简单的排序算法,下面我们介绍下快速排序法和归并排序法。
-
快速排序
快速排序是通过在数组中选定基准值,通过其他元素与基准值的比较,使得基准值前是比基准值小的数组,基准值后是比基准值大的数组,再通过递归调用完成排序的方法。
-
算法思想
-
通过一趟排序将要排序的数据分割成独立的两部分;
-
其中一部分的所有数据都比另外一部分的所有数据都要小,基准值在中间;
-
再按此方法对这两部分数据分别进行快速排序;
-
整个排序过程可以递归进行,以此达到整个数据变成有序序列。
-
实现思路
-
选取基准值,一般选择数组第一个元素
-
从后往前与基准值比较,如果比基准值小,与其调换位置
-
再从前往后比较,如果比基准值大,与其调换位置
-
经过比较后,达到基准值前元素都比它小,基准值后元素都比它大
-
递归比较,将基准值前所有元素看做一个新数组比较,后所有元素看做一个新数组比较
-
代码如下:
public class QuickSort {
public static void main(String[] args){
int[] a = {1,5,45,-2,-44,3,20,8,11,-7};
System.out.println(Arrays.toString(a));
quickSort(a);
System.out.println(Arrays.toString(a));
}
private static void quickSort(int[] data) {
if(data.length > 0){
quickSubSort(data,0,data.length-1);
}
}
/**
*
* @param data
* @param low 最小下标
* @param high 最高小标
*/
private static void quickSubSort(int[] data, int low, int high) {
// 定义基准值
int base = data[low];
//定义开始下标
int start = low;
//定义结束下标
int end = high;
while(end > start){
//从后往前找,找到比基准值大的 放过,让下标减1 ,直到找到比base小的下标 end
while(end > start && data[end] >= base){
end --;
}
// 找到比base小的下标,与base交换位置
if(end > start && data[end] < base){
swap(data,start,end);
}
//开始从前往后找,找到比基准值小的放过,让start下标加1,直到找到比base大的下标 end
while(end > start && data[start] <= base){
start ++;
}
// 找到比base大的下标,与base交换位置
if(end > start && data[start] > base){
swap(data,start,end);
}
}
//第一次循环后 开始递归调用
//基准值前的数
if(start > low){
quickSubSort(data,low,start-1);
}
//基准值后的数
if(end < high){
quickSubSort(data,end+1,high);
}
}
/**
* 交换位置
* @param data
* @param start
* @param end
*/
private static void swap(int[] data, int start, int end) {
int temp = data[start];
data[start] = data[end];
data[end] = temp;
}
}
-
归并排序
归并排序 是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
-
算法思想
-
将一个序列拆分成两个序列;
-
先使每个子序列有序,再使子序列段间有序;
-
将已有序的子序列合并,得到完全有序的序列。
-
实现思路
-
使用二路归并法,将两个有序表合并成一个有序表;
-
新建临时数组,用于存储比较后的数值;
-
将左右数组同时比较,小的放入临时数组中;
-
一旦有某一数组比较完成,则将剩下的数组全放到临时数组中;
-
最后将临时数组复制回原数组。
-
代码如下:
/**
* 归并排序,二分归并
* 新建临时数组,将左右数组同时比较,小的放入临时数组中
* 一旦有某一数组比较完成,则将剩下的数组全放到临时数组中
* 两个循环只会走一个,因为只有一个数组没有遍历完
* <p>
* 最后将临时数组复制回原数组
*/
public class MergeSort {
public static void main(String[] args) {
int[] a = {1, 5, 45, -2, -44, 3, 20, 8, 11, -7};
System.out.println(Arrays.toString(a));
mergeSort(a);
System.out.println(Arrays.toString(a));
}
private static void mergeSort(int[] a) {
if (a.length > 0) {
mergeSubSort(a, 0, a.length - 1);
}
}
/**
* 二分归并
*
* @param data
* @param left
* @param right
*/
private static void mergeSubSort(int[] data, int left, int right) {
if (left >= right) {
return;
}
//获取中间值
int center = (left + right) / 2;
//划分成左右2个数组
mergeSubSort(data, left, center);
mergeSubSort(data, center + 1, right);
//开始归并排序
merge(data, left, center, center + 1, right);
}
/**
* @param data
* @param leftStart
* @param leftEnd
* @param rightStart
* @param rightEnd
*/
private static void merge(int[] data, int leftStart, int leftEnd, int rightStart, int rightEnd) {
//定义循环开始左下标
int leftIndex = leftStart;
//定义循环开始右下标
int rightIndex = rightStart;
//定义临时数组开始下标
int tempIndex = 0;
//定义临时数组
int[] temp = new int[rightEnd - leftStart + 1];
//开始循环 ,当左右有任意一方下标达到临界值 停止循环
while (leftIndex <= leftEnd && rightIndex <= rightEnd) {
//比较最小值,将最小值放到临时数组中
if (data[leftIndex] > data[rightIndex]) {
temp[tempIndex++] = data[rightIndex++];
} else {
temp[tempIndex++] = data[leftIndex++];
}
}
//有一方数组循环完成
// 一下循环只有其实只有一个执行
while (leftIndex <= leftEnd) {
temp[tempIndex++] = data[leftIndex++];
}
while (rightIndex <= rightEnd) {
temp[tempIndex++] = data[rightIndex++];
}
//将临时数组复制回原数组
tempIndex = leftStart;
for (int element : temp) {
data[tempIndex++] = element;
}
}
}
-
二叉树
二叉树是一种非常重要的数据结构,它同时具有数组和链表各自的特点:它可以像数组一样快速查找,也可以像链表一样快速添加。但是它也有自己的缺点:删除操作复杂。查找,插入,删除的复杂度都为 O(logN)。
二叉查找树:是每个结点最多有两个子树的有序树,在使用二叉树的时候,数据并不是随便插入到节点中的,一个节点的左子节点的关键值必须小于此节点,右子节点的关键值必须大于或者是等于此节点,所以又称二叉排序树、二叉搜索树。
下面我们就以 int 类型作为树的根节点,来看看 二叉树的 Java 实现。
-
定义节点
/**
* 定义节点类型
* 主要属性: value 值 left 左节点 right 右节点
*
*/
public class TreeNode {
//关键值
private int value;
//左子树
private TreeNode left;
//右子树
private TreeNode right;
//删除状态
private Boolean deleteStatus;
public TreeNode() {
}
public TreeNode(int value) {
this(value,null,null,false);
}
public TreeNode(int value, TreeNode left, TreeNode right, Boolean deleteStatus) {
this.value = value;
this.left = left;
this.left = right;
this.deleteStatus = deleteStatus;
}
…… get set 方法
}
-
定义二叉树
利用节点来创建二叉树,由于二叉树删除操作比较复杂,这里使用删除标识 deleteStatus 来记录删除状态。代码中主要写的是树的插入,查找,遍历。
/**
* 创建 树
* 主要属性 root 只有获取根节点方法
*
* 主要方法
* 插入
* 查找
* 遍历
*
*/
public class BinaryTree {
private TreeNode root;
public TreeNode getRoot() {
return root;
}
/**
* 向树中插入数据
* @param value
*/
public void insert(int value){
TreeNode newNode = new TreeNode(value);
//插入数据时判断是否是根节点插入
if(root == null){
root = newNode;
root.setLeft(null);
root.setRight(null);
}else{
// 不是根节点插入,获取根节点作为当前节点
TreeNode currentNode = root;
TreeNode parentNode;
//循环插入,直到找到叶子节点,将新值插入
while(true){
//将根节点赋值给父节点
parentNode = currentNode;
if(newNode.getValue() > currentNode.getValue()) {
currentNode = currentNode.getRight();
if (currentNode == null) {
parentNode.setRight(newNode);
return;
}
}else{
currentNode = currentNode.getLeft();
if(currentNode == null){
parentNode.setLeft(newNode);
return;
}
}
}
}
}
/**
* 查找
*
* @param value
* @return
*/
public TreeNode find(int value){
//获取根节点作为当前节点
TreeNode currentNode = root;
//根节点不为null
if(root != null){
while (currentNode.getValue() != value){
if(value > currentNode.getValue()){
currentNode = currentNode.getRight();
}else{
currentNode = currentNode.getLeft();
}
if(currentNode == null){
return null;
}
}
if(currentNode.getDeleteStatus()){
return null;
}else{
return currentNode;
}
}else{
return null;
}
}
/**
* 中序遍历
* @return
*/
public void inOrder(TreeNode root){
if(root != null){
inOrder(root.getLeft());
System.out.println(root.getValue());
inOrder(root.getRight());
}
}
/**
* 前序遍历
* @return
*/
public void preOrder(TreeNode root){
if(root != null){
System.out.println(root.getValue());
preOrder(root.getLeft());
preOrder(root.getRight());
}
}
/**
* 后序遍历
* @return
*/
public void postOrder(TreeNode root){
if(root != null){
postOrder(root.getLeft());
postOrder(root.getRight());
System.out.println(root.getValue());
}
}
}
-
测试代码
public static void main(String[] args){
BinaryTree binaryTree = new BinaryTree();
binaryTree.insert(10);
binaryTree.insert(3);
binaryTree.insert(5);
binaryTree.insert(20);
binaryTree.insert(30);
binaryTree.insert(15);
binaryTree.insert(45);
binaryTree.insert(123);
TreeNode root = binaryTree.getRoot();
System.out.println("跟节点是"+root.getValue());
TreeNode treeNode = binaryTree.find(5);
if(treeNode != null){
System.out.println("找到了");
}else{
System.out.println("没找到");
}
System.out.println("===前序====");
binaryTree.preOrder(root);
System.out.println("===中序====");
binaryTree.inOrder(root);
System.out.println("===后序====");
binaryTree.postOrder(root);
}
以上便是二叉树的 Java 实现,相关代码参照参考资料。
参考资料:
https://github.com/fanpengyi/jianzhi-offer ----文中代码地址
关注一下,我写的就更来劲儿啦