查找算法(Java实现)
1、二分查找算法
package other; public class BinarySearch {
/*
* 循环实现二分查找算法arr 已排好序的数组x 需要查找的数-1 无法查到数据
*/
public static int binarySearch(int[] arr, int x) {
int low = 0;
int high = arr.length-1;
while(low <= high) {
int middle = (low + high)/2;
if(x == arr[middle]) {
return middle;
}else if(x <arr[middle]) {
high = middle - 1;
}else {
low = middle + 1;
}
}
return -1;
}
//递归实现二分查找
public static int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){
int midIndex = (beginIndex+endIndex)/2;
if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){
return -1;
}
if(data <dataset[midIndex]){
return binarySearch(dataset,data,beginIndex,midIndex-1);
}else if(data>dataset[midIndex]){
return binarySearch(dataset,data,midIndex+1,endIndex);
}else {
return midIndex;
}
} public static void main(String[] args) {
int[] arr = { 6, 12, 33, 87, 90, 97, 108, 561 };
System.out.println("循环查找:" + (binarySearch(arr, 87) + 1));
System.out.println("递归查找"+binarySearch(arr,3,87,arr.length-1));
}
}
时间复杂度
比如:总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
由于n/2^k取整后>=1,即令n/2^k=1,
可得k=log2n,(是以2为底,n的对数),所以时间复杂度可以表示O()=O(logn)
2、归并排序
归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。
工作原理:
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4、重复步骤3直到某一指针达到序列尾
5、将另一序列剩下的所有元素直接复制到合并序列尾
public class MergeSortTest { public static void main(String[] args) {
int[] data = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };
print(data);
mergeSort(data);
System.out.println("排序后的数组:");
print(data);
} public static void mergeSort(int[] data) {
sort(data, 0, data.length - 1);
} public static void sort(int[] data, int left, int right) {
if (left >= right)
return;
// 找出中间索引
int center = (left + right) / 2;
// 对左边数组进行递归
sort(data, left, center);
// 对右边数组进行递归
sort(data, center + 1, right);
// 合并
merge(data, left, center, right);
print(data);
} /**
* 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
*
* @param data
* 数组对象
* @param left
* 左数组的第一个元素的索引
* @param center
* 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
* @param right
* 右数组最后一个元素的索引
*/
public static void merge(int[] data, int left, int center, int right) {
// 临时数组
int[] tmpArr = new int[data.length];
// 右数组第一个元素索引
int mid = center + 1;
// third 记录临时数组的索引
int third = left;
// 缓存左数组第一个元素的索引
int tmp = left;
while (left <= center && mid <= right) {
// 从两个数组中取出最小的放入临时数组
if (data[left] <= data[mid]) {
tmpArr[third++] = data[left++];
} else {
tmpArr[third++] = data[mid++];
}
}
// 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
while (mid <= right) {
tmpArr[third++] = data[mid++];
}
while (left <= center) {
tmpArr[third++] = data[left++];
}
// 将临时数组中的内容拷贝回原数组中
// (原left-right范围的内容被复制回原数组)
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
} public static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + "\t");
}
System.out.println();
} }
static void merge_sort_recursive(int[] arr, int[] result, int start, int end) {
if (start >= end)
return;
int len = end - start, mid = (len >> 1) + start;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
merge_sort_recursive(arr, result, start1, end1);
merge_sort_recursive(arr, result, start2, end2);
int k = start;
while (start1 <= end1 && start2 <= end2)
result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
result[k++] = arr[start1++];
while (start2 <= end2)
result[k++] = arr[start2++];
for (k = start; k <= end; k++)
arr[k] = result[k];
}
public static void merge_sort(int[] arr) {
int len = arr.length;
int[] result = new int[len];
merge_sort_recursive(arr, result, 0, len - 1);
}
3、快速排序
算法概述/思路
快速排序一般基于递归实现。其思路是这样的:
1.选定一个合适的值(理想情况中值最好,但实现中一般使用数组第一个值),称为“枢轴”(pivot)。
2.基于这个值,将数组分为两部分,较小的分在左边,较大的分在右边。
3.可以肯定,如此一轮下来,这个枢轴的位置一定在最终位置上。
4.对两个子数组分别重复上述过程,直到每个数组只有一个元素。
5.排序完成。
public static void quickSort(int[] arr){
qsort(arr, 0, arr.length-1);
}
private static void qsort(int[] arr, int low, int high){
if (low < high){
int pivot=partition(arr, low, high); //将数组分为两部分
qsort(arr, low, pivot-1); //递归排序左子数组
qsort(arr, pivot+1, high); //递归排序右子数组
}
}
private static int partition(int[] arr, int low, int high){
int pivot = arr[low]; //枢轴记录
while (low<high){
while (low<high && arr[high]>=pivot) --high;
arr[low]=arr[high]; //交换比枢轴小的记录到左端
while (low<high && arr[low]<=pivot) ++low;
arr[high] = arr[low]; //交换比枢轴小的记录到右端
}
//扫描完成,枢轴到位
arr[low] = pivot;
//返回的是枢轴的位置
return low;
}
算法性能/复杂度
可以看出,每一次调用partition()方法都需要扫描一遍数组长度(注意,在递归的时候这个长度并不是原数组的长度n,而是被分隔出来的小数组,即n*(2^(-i))),其中i为调用深度。而在这一层同样长度的数组有2^i个。那么,每层排序大约需要O(n)复杂度。而一个长度为n的数组,调用深度最多为log(n)层。二者相乘,得到快速排序的平均复杂度为O(n ㏒n)。
通常,快速排序被认为是在所有同数量级的排序方法中,平均性能最好。
从代码中可以很容易地看出,快速排序单个栈的空间复杂度不高,每次调用partition方法时,其额外开销只有O(1)。所以,最好情形下快速排序空间复杂度大约为O(㏒n)。
算法优化
上面这个快速排序算法可以说是最基本的快速排序,因为它并没有考虑任何输入数据。但是,我们很容易发现这个算法的缺陷:这就是在我们输入数据基本有序甚至完全有序的时候,这算法退化为冒泡排序,不再是O(n㏒n),而是O(n^2)了。
究其根源,在于我们的代码实现中,每次只从数组第一个开始取。如果我们采用“三者取中”,即arr[low],arr[high],arr[(low+high)/2]三者的中值作为枢轴记录,则可以大大提高快速排序在最坏情况下的性能。但是,我们仍然无法将它在数组有序情形下的性能提高到O(n)。还有一些方法可以不同程度地提高快速排序在最坏情况下的时间性能。
此外,快速排序需要一个递归栈,通常情况下这个栈不会很深,为log(n)级别。但是,如果每次划分的两个数组长度严重失衡,则为最坏情况,栈的深度将增加到O(n)。此时,由栈空间带来的空间复杂度不可忽略。如果加上额外变量的开销,这里甚至可能达到恐怖的O(n^2)空间复杂度。所以,快速排序的最差空间复杂度不是一个定值,甚至可能不在一个级别。
为了解决这个问题,我们可以在每次划分后比较两端的长度,并先对短的序列进行排序(目的是先结束这些栈以释放空间),可以将最大深度降回到O(㏒n)级别。
算法稳定性
快速排序并不是稳定的。这是因为我们无法保证相等的数据按顺序被扫描到和按顺序存放。
算法适用场景
快速排序在大多数情况下都是适用的,尤其在数据量大的时候性能优越性更加明显。但是在必要的时候,需要考虑下优化以提高其在最坏情况下的性能。
快排的非递归实现
按照通常的理论,我们知道递归算法一般比较直观自然,容易理解和书写;而非递归算法一般更为晦涩,但是性能比递归算法更优良,因为其省去了大量的函数调用开销。快速排序肯定有非递归实现的版本,例如这篇博客。有趣的是,这位作者认为快速排序的非递归实现比递归还要慢,并做出了分析。而下面的这位博主则写了另一篇博文,证明“非递归算法总要比响应(应为"相应"--本博作者注)的递归算法速度快”,并认为前面的现象是由于Windows 下的STL效率比较低。
快速排序的Java非递归实现当然有,通常都是用自己实现的栈来模拟递归操作(实际上,前面两位使用C++的同学也是如此做的)。但是我并不认为它们比递归的方式有极大的性能提升,反而丢失了可读性,晦涩难懂。因此,我个人不提倡使用非递归方式。
4、动态规划
5、堆排序
堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。
如下图,是一个堆和数组的相互关系
堆和数组的相互关系
对于给定的某个结点的下标 i,可以很容易的计算出这个结点的父结点、孩子结点的下标:
- Parent(i) = floor(i/2),i 的父节点下标
- Left(i) = 2i,i 的左子节点下标
- Right(i) = 2i + 1,i 的右子节点下标
package com.genesis.arithmetic; import java.util.Arrays; public class HeapSort { private int[] arr; public HeapSort(int[] arr){
this.arr = arr;
} /**
* 堆排序的主要入口方法,共两步。
*/
public void sort(){
/*
* 第一步:将数组堆化
* beginIndex = 第一个非叶子节点。
* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
* 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
*/
int len = arr.length - 1;
int beginIndex = (len - 1) >> 1;
for(int i = beginIndex; i >= 0; i--){
maxHeapify(i, len);
} /*
* 第二步:对堆化数据排序
* 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
* 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
* 直至未排序的堆长度为 0。
*/
for(int i = len; i > 0; i--){
swap(0, i);
maxHeapify(0, i - 1);
}
} private void swap(int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
} /**
* 调整索引为 index 处的数据,使其符合堆的特性。
*
* @param index 需要堆化处理的数据的索引
* @param len 未排序的堆(数组)的长度
*/
private void maxHeapify(int index,int len){
int li = (index << 1) + 1; // 左子节点索引
int ri = li + 1; // 右子节点索引
int cMax = li; // 子节点值最大索引,默认左子节点。 if(li > len) return; // 左子节点索引超出计算范围,直接返回。
if(ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。
cMax = ri;
if(arr[cMax] > arr[index]){
swap(cMax, index); // 如果父节点被子节点调换,
maxHeapify(cMax, len); // 则需要继续判断换下后的父节点是否符合堆的特性。
}
} /**
* 测试用例
*
* 输出:
* [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
*/
public static void main(String[] args) {
// int[] arr = new int[]{3,5,3,0,8,6,1,5,8,6,2,4,9,4,7,0,1,8,9,7,3,1,2,5,9,7,4,0,2,6};
int[] arr = new int[]{16, 7, 3, 20, 17, 8};
new HeapSort(arr).sort();
System.out.println(Arrays.toString(arr));
} }
查找算法(Java实现)的更多相关文章
- 二分查找算法java实现
今天看了一下JDK里面的二分法是实现,觉得有点小问题.二分法的实现有多种今天就给大家分享两种.一种是递归方式的,一种是非递归方式的.先来看看一些基础的东西. 1.算法概念. 二分查找算法也称为折半搜索 ...
- 二分查找算法java
二分查找又称折半查找,它是一种效率较高的查找方法. 折半查找的算法思想是将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小于该中点元 ...
- Java实现的二分查找算法
二分查找又称折半查找,它是一种效率较高的查找方法. 折半查找的算法思想是将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小 于该中点 ...
- Java中常用的查找算法——顺序查找和二分查找
Java中常用的查找算法——顺序查找和二分查找 神话丿小王子的博客 一.顺序查找: a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数据最后一位 ...
- Java中的查找算法之顺序查找(Sequential Search)
Java中的查找算法之顺序查找(Sequential Search) 神话丿小王子的博客主页 a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数 ...
- 相似文档查找算法之 simHash 简介及其 java 实现 - leejun_2005的个人页面 - 开源中国社区
相似文档查找算法之 simHash 简介及其 java 实现 - leejun_2005的个人页面 - 开源中国社区 相似文档查找算法之 simHash 简介及其 java 实现
- Java学习之二分查找算法
好久没写算法了.只记得递归方法..结果测试下爆栈了. 思路就是取范围的中间点,判断是不是要找的值,是就输出,不是就与范围的两个临界值比较大小,不断更新临界值直到找到为止,给定的集合一定是有序的. 自己 ...
- "二分法"-"折半法"-查找算法-之通俗易懂,图文+代码详解-java编程
转自http://blog.csdn.net/nzfxx/article/details/51615439 1.特点及概念介绍 下面给大家讲解一下"二分法查找"这个java基础查找 ...
- 算法:时间复杂度+二分查找法(Java/Go/Python)实现
导读 曾几何时学好数据结构与算法是我们从事计算机相关工作的基本前提,然而现在很多程序员从事的工作都是在用高级程序设计语言(如Java)开发业务代码,久而久之,对于数据结构和算法就变得有些陌生了,由于长 ...
随机推荐
- 关于 SVN 项目检出
前几天呢,同事遇到这么一个问题:他新建了一个工作空间,当他通过 svn 检出公司项目的时候,准备过来测试运行,但是呢出现了下面的报错 [ERROR] Failed to execute goal or ...
- Time模块和datetime模块
Time模块和datetime模块 一. 调用 import time #调用time模块 二.使用方法 1.time.time 拿到时间戳.以Linux诞生年份1970年开始计算到程序执 ...
- 保存单文件为mhtml
1.F6 ,在地址栏输入 输入:chrome://flags 然后ctrl+f: 输入:mhtml 启动,重启即可保存为单文件.
- linux系统莫名被黑的诡异经历
2017年10月28日,ssh登录本地虚拟机之后发现主机名被改成了HUAIWEI_P10. 我一个同事用的此款手机,起初怀疑是他,没放心上. 今天(10月30)发现自己ssh登录不上了.恼火了办公室开 ...
- Flex 基础语法(二)
1.flex -direction 属性 含义 row(默认值) 主轴为水平方向,起点在左端. row-reverse 主轴为水平方向,起点在右边. column 主轴为垂直方向,起点在上沿. col ...
- grid 布局
display:grid 是一种新的布局方式,旧的布局方式通常有副作用,例如float(需要额外修复浮动)或者inline-block(两个元素之间的空格问题) 把父元素定义为grid,就像表格一 ...
- android 横竖屏切换不重走生命周期
android在系统配置发生改变时,Activity会被重新创建,但是某些情况下我们希望系统配置改变时不会重新创建Activity,这个时候我们可以给Activity指定相对应的configChang ...
- Task及Mvc的异步控制器 使用探索
微软的Task已经出来很久了,一直没有去研究,以为就是和Thread差不多的东西.直到最近看到了Task的使用介绍,发现比Thread的语法要精炼多了,于是便在项目中用上了. 结果就出问题了,数据库连 ...
- WebGL学习(1) - 三角形
原文地址:WebGL学习(1) - 三角形 还记得第一次看到canvas的粒子特效的时候,真的把我给惊艳到了,原来在浏览器也能做出这么棒的效果.结合<HTML5 Canvas核心技术>和网 ...
- linux-echo
echo 更新时间: 2017-10-11-11:55:24 echo:打印输出内容 参数选择 -e 激活转义字符 命令:echo 123 ,此命令 就会输出123 命令: echo -e &q ...