java 查找排序_查找与排序算法(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

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[endIndex]||beginIndex>endIndex){

return -1;

}

if(data

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

while (low=pivot) --high;

arr[low]=arr[high]; //交换比枢轴小的记录到左端

while (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、堆排序

堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

如下图,是一个堆和数组的相互关系

heap-and-array.png堆和数组的相互关系

对于给定的某个结点的下标 i,可以很容易的计算出这个结点的父结点、孩子结点的下标:

Parent(i) = floor(i/2),i 的父节点下标

Left(i) = 2i,i 的左子节点下标

Right(i) = 2i + 1,i 的右子节点下标

heap-and-array-parent-children.png

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));

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/554538.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统盘怎么重装系统

现在人们越来越离不开电脑了&#xff0c;无论是工作需要还是生活娱乐都离不开对电脑的使用&#xff0c;不过电脑毕竟是电子产品&#xff0c;使用久了难免会出现一些毛病&#xff0c;如果找不到具体原因的话&#xff0c;那最好的办法就是重装系统。下面就来分享一下系统盘怎么重…

yii mysql 缓存_yii2优化 - 开启 Schema 缓存

开启 Schema 缓存Schema 缓存是一个特殊的缓存功能&#xff0c;每当你使用活动记录时应该要开启这个缓存功能。如你所知&#xff0c; 活动记录能智能检测数据库对象的集合(例如列名、列类型、约束)而不需要手动地描述它们。活动记录是通过执行额外的 SQL 查询来获得该信息。 通…

电脑重装系统按哪个键

电脑重装系统按哪个键&#xff0c;下面就来与大家分享装系统按什么键的教程。 键盘上没有按键可以按一下就重装系统&#xff0c;电脑重装需要使用一个U盘&#xff0c;并且会清空电脑与U盘数据&#xff0c;需要提前备份好数据。 1 第一步 打开装机软件&#xff0c;选择启动U盘…

java 检视_Java高并发系列——检视阅读(五)

JUC中工具类CompletableFutureCompletableFuture是java8中新增的一个类&#xff0c;算是对Future的一种增强&#xff0c;用起来很方便&#xff0c;也是会经常用到的一个工具类&#xff0c;熟悉一下。CompletionStage接口CompletionStage代表异步计算过程中的某一个阶段&#xf…

java中wmi的username_有没有一种方法可以在Java中使用WMI类

小编典典JavaScript和Java不是一回事。JavaScriptWindows脚本宿主(WSH)下提供了JavaScript。有了它&#xff0c;访问WMI相当容易&#xff1a;var loc new ActiveXObject("WbemScripting.SWbemLocator");var svc loc.ConnectServer(".", "root\\cimv…

PP视频怎么查看云钻的兑换记录呢

本文小编给大家分享的是PP视频怎么查看云钻的兑换记录呢。如果大家想在家体验电影院的音质&#xff0c;那么戴上耳机&#xff0c;PP视频上的杜比音效会让你无比震撼&#xff0c;当然前提是这个视频资源在制作的时候制作了这个音效。PP视频独播剧质量还是可以的&#xff0c;比较…

java dos 菜单栏_学习java之电脑的常用快捷键和DOS窗口下的常用命令

学习java之电脑的常用快捷键和DOS窗口下的常用命令电脑一些常用的快捷键win快捷键&#xff1a;单独按Windows&#xff1a;显示或隐藏 “开始”功能表WindowsBREAK&#xff1a;显示“系统属性” 对话框WindowsD&#xff1a;显示桌面或恢复桌面WindowsM&#xff1a;最小化所有窗口…

360浏览器一打开就是瑞星安全网址怎么办

核心提示&#xff1a;360浏览器一打开就是瑞星安全网址怎么办 360浏览器一打开就是瑞星安全网址怎么办 解决方法如下&#xff1a; 1、打开360杀毒软件后&#xff0c;在页面底部右下角点击打开【软件净化】后能找到瑞星导航&#xff0c;点击卸载。卸载完成后&#xff0c;重新…

js将中文转换成编码 java解析_JS实现的汉字与Unicode码相互转化功能分析

本文实例讲述了JS实现的汉字与Unicode码相互转化功能。分享给大家供大家参考&#xff0c;具体如下&#xff1a;有时候&#xff0c;我们在给后端传递变量的的值中有汉字&#xff0c;可能由于编码的原因&#xff0c;传递到后端后变为乱码了。所以有时候为了省事或者其它特殊要求的…

优酷视频怎么关闭弹幕提醒功能

优酷视频是目前用户量最大的&#xff0c;在同行业当中体验非常好&#xff0c;本文分享“优酷视频怎么关闭弹幕提醒功能”。优酷视频手机版是一款装机必备的手机视频播放软件&#xff0c;用户可在线观看优酷视频全部免费高清正版视频&#xff0c;海量内容&#xff0c;高清画质&a…

要Java显示script文字_Javasrcipt---HTML中使用Javascript

在HTML中使用Javascript&#xff1a;————————————————————————————————————————————————————————主要内容;一、二、嵌入脚本和外部引入脚本。三、文档模式对Javasrcipt的影响。四、什么情况下考虑禁用Javascript。——…

win10一键重装系统​

今天小编要给大家介绍的是云骑士装机大师win10一键重装系统&#xff0c;一键系统重装,无需电脑技术,小白在家也可自己完成安装,三步到位,安全简单!一键系统重装,0费用,0丢包,极加速!一起来看看吧。 1 第一步 首先到云骑士官网下载软件&#xff0c;然后打开云骑士装机大师&…

泊松分酒 java课件_泊松分酒原理 - 我类个擦的个人空间 - OSCHINA - 中文开源技术交流社区...

有一个12品脱(pint)的酒瓶&#xff0c;里面装满葡萄酒&#xff0c;另有8品脱和5品脱的瓶子各一个。问如何从中分出6品脱的酒出来&#xff1f;传说泊松年轻时成功解决了该问题&#xff0c;勾起了他对数学的兴趣而投身数学研究&#xff0c;因此该问题被称为泊松分酒问题。另外这个…

win7怎么还原系统

对于win7怎么还原系统的这个问题&#xff0c;其实很好操作&#xff0c;来看视频是怎么做的吧。 1 第一步 按winr组合键&#xff0c;打开运行&#xff0c;输入sysprep&#xff0c;回车 win7怎么还原系统 2 第二步 进入页面&#xff0c;双击sysprep.exe win7怎么还原系统(1) 3…

java file size 单位_file.getsize 单位

{ //String myFileNamemyFile.getFileName(); //取得上载的文件的文件名 ext myFile.getFileExt(); //取得后缀名 int file_sizemyFile.getSize(); ......{ //String myFileNamemyFile.getFileName(); //取得上载的文件的文件名 ext myFile.getFileExt(); //取得后缀名 int fil…

win7系统还原在哪​

Win7系统用久了之后&#xff0c;可能会出现一些故障或者因为文件过多而变得非常卡。这时很多人会选择重新安装重装系统&#xff0c;其实比起重装系统&#xff0c;小编更推荐win7系统还原&#xff0c;不仅耗费时间短&#xff0c;而且不伤电脑&#xff0c;那么win7系统还原在哪?…

java queue 最大值_[剑指offer题解]队列的最大值/滑动窗口的最大值

前言众所周知&#xff0c;《剑指offer》是一本“好书”。为什么这么说&#xff1f;因为在技术面试中&#xff0c;它里面罗列的算法题在面试中出现的频率是非常非常高的。有多高&#xff0c;以我目前不多的面试来看&#xff0c;在所有遇到的面试算法题中&#xff0c;出现原题的概…

PP视频怎么设置文件的缓存路径

软件是否好用&#xff0c;还得大家下载PP视频安装才知道。本文分享PP视频怎么设置文件的缓存路径。作为国内首家&#xff0c;全力打造正版视频平台的PP视频&#xff0c;将从流畅的观映体验、高清的视觉效果、贴心的分享感受等多个方面将“品质”做到极致&#xff0c;满足用户不…

java jvm 加载类的顺序_由经典面试题看java中类的加载机制

原标题&#xff1a;由经典面试题看java中类的加载机制1.概述类加载是Java程序运行的第一步&#xff0c;研究类的加载有助于了解JVM执行过程&#xff0c;并指导开发者采取更有效的措施配合程序执行&#xff0c;对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。由于J…

联想一键恢复系统教程

联想一键恢复系统教程?下面我们来看一看吧。 联想不同类型的电脑进入一键恢复界面的方法不一&#xff0c;台式机一体机就在电脑开机出现联想logo界面时按下FnF2组合键。 联想一键恢复系统教程 笔记本则寻找一键恢复按键&#xff0c;在电脑电源关闭的情况下按住几秒钟即可进…