这篇文章主要介绍“如何使用Java二分查找”,在日常操作中,相信很多人在如何使用Java二分查找问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用Java二分查找”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
基本思想:又叫折半查找,要求待查找的序列有序,是一种快速查找算法,时间复杂度为 O(logn),要求数据集为一个有序数据集。
应用场景:一般用于查找数组元素,并且数组在查找之前必须已经排好序(一般是升序)。
步骤:
1、取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,
2、如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。
3、直到查找到了为止,否则序列中没有待查的关键字。
代码示例:
public static int biSearch(int []array,int a){ int lo=0; int hi=array.length-1; int mid; while(lo<=hi){ mid=(lo+hi)/2;//中间位置 if(array[mid]==a){ return mid+1; }else if(array[mid]<a){ //向右查找 lo=mid+1; }else{ //向左查找 hi=mid-1; } } return -1; }
基本思想:比较前后相邻的两个数据,如果前面数据大于后面的数据,就将这二个数据交换。这样对数组的第 0 个数据到 N-1 个数据进行一次遍历后,最大的一个数据就“沉”到数组第 N-1 个位置。N=N-1,如果 N 不为 0 就重复前面二步,否则排序完成。
应用场景:数据量不大,对稳定性有要求,且数据基本有序的情况。
步骤:
1、将序列中所有元素两两比较,将最大的放在最后面。
2、将剩余序列中所有元素两两比较,将最大的放在最后面。
3、重复第二步,直到只剩下一个数。
代码示例:
public static void bubbleSort1(int [] a, int n){ int i, j; for(i=0; i<n; i++){//表示 n 次排序过程。 for(j=1; j<n-i; j++){ if(a[j-1] > a[j]){//前面的数字大于后面的数字就交换 //交换 a[j-1]和 a[j] int temp; temp = a[j-1]; a[j-1] = a[j]; a[j]=temp; } } } }
基本思想:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。
应用场景:数据量不大,对算法的稳定性有要求,且数据局部或者整体有序的情况。
步骤:
1、将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2、从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
代码示例:
public class InsertSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的 for (int i = 1; i < arr.length; i++) { // 记录要插入的数据 int tmp = arr[i]; // 从已经排序的序列最右边的开始比较,找到比其小的数 int j = i; while (j > 0 && tmp < arr[j - 1]) { arr[j] = arr[j - 1]; j--; } // 存在比其小的数,插入 if (j != i) { arr[j] = tmp; } } return arr; } } 快速排序算法
基本思想:选择一个关键值作为基准值。比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。一般选择序列的第一个元素。
应用场景:数值范围较大,相同值的概率较小,数据量大且不考虑稳定性的情况,数值远大于数据量时威力更大。
步骤:
1、一次循环,从后往前比较,用基准值和最后一个值比较,如果比基准值小的交换位置,如果没有继续比较下一个,直到找到第一个比基准值小的值才交换。
2、找到这个值之后,又从前往后开始比较,如果有比基准值大的,交换位置,如果没有继续比较下一个,直到找到第一个比基准值大的值才交换。
3、直到从前往后的比较索引 > 从后往前比较的索引,结束第一次循环,此时,对于基准值来说,左右两边就是有序的了。
代码示例:
public void sort(int[] a,int low,int high){ int start = low; int end = high; int key = a[low]; while(end>start){ //从后往前比较 while(end>start&&a[end]>=key) //如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较 end--; if(a[end]<=key){ int temp = a[end]; a[end] = a[start]; a[start] = temp; } //从前往后比较 while(end>start&&a[start]<=key) //如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置 start++; if(a[start]>=key){ int temp = a[start]; a[start] = a[end]; a[end] = temp; } //此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用 } //递归 if(start>low) sort(a,low,start-1);//左边序列。第一个索引位置到关键值索引-1 if(end<high) sort(a,end+1,high);//右边序列。从关键值索引+1 到最后一个 } }
基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
应用场景:数据量较大,不要求稳定性的情况。
步骤:
1、选择一个增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
2、按增量序列个数 k,对序列进行 k 趟排序;
3、每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码示例:
private void shellSort(int[] a) { int dk = a.length/2; while( dk >= 1 ){ ShellInsertSort(a, dk); dk = dk/2; } } private void ShellInsertSort(int[] a, int dk) { //类似插入排序,只是插入排序增量是 1,这里增量是 dk,把 1 换成 dk 就可以了 for(int i=dk;i<a.length;i++){ if(a[i]<a[i-dk]){ int j; int x=a[i];//x 为待插入元素 a[i]=a[i-dk]; for(j=i-dk; j>=0 && x<a[j];j=j-dk){ //通过循环,逐个后移一位找到要插入的位置。 a[j+dk]=a[j]; } a[j+dk]=x;//插入 } } } 归并排序算法
基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
应用场景:内存少的时候使用,可以进行并行计算的时候使用。
步骤:
1、选择相邻两个数组成一个有序序列。
2、选择相邻的两个有序序列组成一个有序序列。
3、重复第二步,直到全部组成一个有序序列。
代码示例:
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(); } }
基本思想: 把数组 arr 划分为 n 个大小相同子区间(桶),每个子区间各自排序,最后合并 。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。
应用场景:在数据量非常大,而空间相对充裕的时候是很实用的,可以大大降低算法的运算数量级。
步骤:
1、找出待排序数组中的最大值 max、最小值 min
2、我们使用动态数组 ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(maxmin)/arr.length+1
3、遍历数组 arr,计算每个元素 arr[i] 放的桶
4、每个桶各自排序
代码示例:
public static void bucketSort(int[] arr){ int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for(int i = 0; i < arr.length; i++){ max = Math.max(max, arr[i]); min = Math.min(min, arr[i]); } //创建桶 int bucketNum = (max - min) / arr.length + 1; ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum); for(int i = 0; i < bucketNum; i++){ bucketArr.add(new ArrayList<Integer>()); } //将每个元素放入桶 for(int i = 0; i < arr.length; i++){ int num = (arr[i] - min) / (arr.length); bucketArr.get(num).add(arr[i]); } //对每个桶进行排序 for(int i = 0; i < bucketArr.size(); i++){ Collections.sort(bucketArr.get(i)); } }
基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
应用场景:用于大量数,很长的数进行排序时的情况。
步骤:
1、将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
2、将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。
代码示例:
public class radixSort { inta[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,101,56,17,18,23,34,15,35,25,53,51}; public radixSort(){ sort(a); for(inti=0;i<a.length;i++){ System.out.println(a[i]); } } public void sort(int[] array){ //首先确定排序的趟数; int max=array[0]; for(inti=1;i<array.length;i++){ if(array[i]>max){ max=array[i]; } } int time=0; //判断位数; while(max>0){ max/=10; time++; } //建立 10 个队列; List<ArrayList> queue=newArrayList<ArrayList>(); for(int i=0;i<10;i++){ ArrayList<Integer>queue1=new ArrayList<Integer>(); queue.add(queue1); } //进行 time 次分配和收集; for(int i=0;i<time;i++){ //分配数组元素; for(intj=0;j<array.length;j++){ //得到数字的第 time+1 位数; int x=array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10, i); ArrayList<Integer>queue2=queue.get(x); queue2.add(array[j]); queue.set(x, queue2); } int count=0;//元素计数器; //收集队列元素; for(int k=0;k<10;k++){ while(queue.get(k).size()>0){ ArrayList<Integer>queue3=queue.get(k); array[count]=queue3.get(0); queue3.remove(0); count++; } } } } }
基本思想:在搜索算法中优化中,剪枝,就是通过某种判断,避免一些不必要的遍历过程,形象的说,就是剪去了搜索树中的某些“枝条”,故称剪枝。应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
应用场景:通常应用在 DFS 和 BFS 搜索算法中,寻找过滤条件,提前减少不必要的搜索路径。
步骤:
1、基于训练数据集生成决策树,生成的决策树要尽量大;
2、用验证数据集最已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准
代码示例:
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { Arrays.sort(candidates); LinkedList<Integer> track = new LinkedList<>(); combinationSum(candidates, 0, target, track); return result; } private List<List<Integer>> result = new ArrayList<>(); private void combinationSum(int[] candidates, int start, int target, LinkedList<Integer> track) { if (target < 0) { return; } else if (target == 0) { result.add(new LinkedList<>(track)); return; } for (int i = start; i < candidates.length; i++) { if (target < candidates[i]) { break; } track.add(candidates[i]); combinationSum(candidates, i, target - candidates[i], track); track.removeLast(); } } }
基本思想:回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
应用场景:设置一个递归函数,函数的参数会携带一些当前的可能解的信息,根据这些参数得出可能解或者不可能而回溯,平时经常见的有 N 皇后、数独、集合等情况。
步骤:
1、定义一个解空间,它包含问题的解;
2、利用适于搜索的方法组织解空间;
3、利用深度优先法搜索解空间;
4、利用限界函数避免移动到不可能产生解的子空间。
代码示例:
function backtrack(n, used) { // 判断输入或者状态是否非法 if (input/state is invalid) { return; } // 判读递归是否应当结束,满足结束条件就返回结果 if (match condition) { return some value; } // 遍历当前所有可能出现的情况,并尝试每一种情况 for (all possible cases) { // 如果上一步尝试会影响下一步尝试,需要写入状态 used.push(case) // 递归进行下一步尝试,搜索该子树 result = backtrack(n + 1, used) // 在这种情况下已经尝试完毕,重置状态,以便于下面的回溯尝试 used.pop(case) } }
基本思想:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路径。解决最短路的问题有以下算法,Dijkstra 算法,Bellman-Ford 算法,Floyd 算法和 SPFA 算法等。
应用场景:应用有计算机网络路由算法,机器人探路,交通路线导航,人工智能,游戏设计等。
步骤:(Dijkstra 算法示例)
1、 访问路网中里起始点最近且没有被检查过的点,把这个点放入 OPEN 组中等待检查。
2、 从OPEN表中找出距起始点最近的点,找出这个点的所有子节点,把这个点放到 CLOSE 表中。
3、 遍历考察这个点的子节点。求出这些子节点距起始点的距离值,放子节点到 OPEN 表中。
4、重复2,3,步。直到 OPEN 表为空,或找到目标点。
代码示例:
//Dijkstra 算法 static int[] pathSrc = new int[9]; static int[] shortPath = new int[9]; static void shortestPath_DijkStra(MGraph m, int v0) { // finalPath[w] = 1 表示已经获取到顶点V0到Vw的最短路径 int[] finalPath = new int[9]; for (int i = 0; i < m.numVertexes; i++) { finalPath[i] = 0; shortPath[i] = m.arc[v0][i]; pathSrc[i] = 0; } // v0到v0的路径为0 shortPath[v0] = 0; // vo到v0不需要求路径 finalPath[v0] = 1; for (int i = 1; i < m.numVertexes; i++) { // 当前所知的离V0最近的距离 int min = INFINITY; int k = 0; for (int w = 0; w < m.numVertexes; w++) { if(shortPath[w] < min && finalPath[w] == 0) { min = shortPath [w]; k = w; } } finalPath[k] = 1; // 修改finalPath的值,标记为已经找到最短路径 for (int w = 0; w < m.numVertexes; w++) { // 如果经过V顶点的路径比原来的路径(不经过V)短的话 if(finalPath[w] == 0 && (min + m.arc[k][w]) < shortPath[w]) { // 说明找到了更短的路径,修改 shortPath[w] = min + m.arc[k][w]; // 修改路径的长度 pathSrc[w] = k; // 修改顶点下标W的前驱顶点 } } } }
基本思想:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
应用场景:生活中可以用来查看股票一周之内的增长状态,需要得到最合适的买入和卖出时间。
步骤:
1、将子串和为负数的子串丢掉,只留和为正的子串。
2、如果 nums 中有正数,从左到右遍历 nums,用变量 cur 记录每一步的累加和,遍历到正数 cur 增加,遍历到负数 cur 减少。
3、当 cur>=0 时,每一次累加都可能是最大的累加和,所以,用另外一个变量 max 全程跟踪记录 cur 出现的最大值即可。
代码示例:
class Solution { public: /* * @param nums: A list of integers * @return: A integer indicate the sum of max subarray */ int maxSubArray(vector<int> nums) { if(nums.size()<=0){ return 0; } int max=INT_MIN,cur=0;//c++最小值 for(int i=0; i<nums.size(); i++) { if(cur < 0) cur = nums[i];//如果前面加起来的和小于0,抛弃前面的 else cur+=nums[i]; if(cur > max) max = cur; } return max; } };
基本思想:最长公共子序列是一个在一个序列集合中用来查找所有序列中最长子序列的问题。这与查找最长公共子串的问题不同的地方是:子序列不需要在原序列中占用连续的位置。
应用场景:最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如 Diff 工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如 Git 用来调和文件之间的改变。
步骤:
1、可以使用递归去解决,需要遍历出所有的可能,很慢;
2、对于一般的 LCS 问题,都属于 NP 问题;
3、当数列的量为一定的时,都可以采用动态规划去解决。
代码示例:
class Solution { public int longestCommonSubsequence(String text1, String text2) { int length2 = text1.length(); int length3 = text2.length(); int[][] a = new int[length2 + 1][length3 + 1];//0行0列保留 for(int i = 1; i <= length2; i++){ for(int j = 1; j <= length3; j++){ if (text1.charAt(i - 1) == text2.charAt(j - 1)) { a[i][j] = a[i - 1][j - 1] + 1; } else { if (a[i][j - 1] > a[i-1][j]) { a[i][j] = a[i][j - 1]; } else { a[i][j] = a[i - 1][j]; } } } } return a[length2][length3]; } }
基本思想:在含有n个顶点的带权无向连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树(不一定唯一)。
一般情况,要解决最小生成树问题,通常采用两种算法:Prim算法和Kruskal算法。
应用场景:一般用来计算成本最小化的情况。
步骤:(Prim 算法示例)
1、以某一个点开始,寻找当前该点可以访问的所有的边;
2、在已经寻找的边中发现最小边,这个边必须有一个点还没有访问过,将还没有访问的点加入我们的集合,记录添加的边;
3、寻找当前集合可以访问的所有边,重复 2 的过程,直到没有新的点可以加入;
4、此时由所有边构成的树即为最小生成树。
代码示例:
/** prim算法 * @param first 构成最小生成树的起点的标识 * @return 返回最小生成树构成的边 */ public List<Edge> generateMinTreePrim(T first){ //存储最小生成树构成的边 List<Edge> result=new LinkedList<>(); //首先建立map,key为vertex,value为edge HashMap<Vertex<T>, Edge> map=new HashMap<>(); Iterator<Vertex<T>> vertexIterator=getVertexIterator(); Vertex<T> vertex; Edge edge; while(vertexIterator.hasNext()){ //一开始,value为edge的两端的都为自己,weight=maxDouble vertex=vertexIterator.next(); edge=new Edge(vertex, vertex, Double.MAX_VALUE); map.put(vertex, edge); } //first是构成最小生成树的起点的标识 vertex=vertexMap.get(first); if(vertex==null){ System.out.println("没有节点:"+first); return result; } //所有不在生成树中的节点,都是map的key,如果map为空,代表所有节点都在树中 while(!map.isEmpty()){ //这次循环要加入生成树的节点为vertex,边为vertex对应的edge(也就是最小的边) edge=map.get(vertex); //每将一个结点j加入了树A,首先从map中去除这个节点 map.remove(vertex); result.add(edge); System.out.println("生成树加入边,顶点:"+vertex.getLabel()+ " ,边的终点是:"+edge.getEndVertex().getLabel()+" ,边的权值为: "+edge.getWeight());; //如果是第一个节点,对应的边是到自己的,删除 if(vertex.getLabel().equals(first)){ result.remove(edge); } //然后看map中剩余的节点到节点j的距离,如果这个边的距离小于之前边的距离,就将边替换成这个到节点j的边 //在遍历替换中,同时发现距离最短的边minEdge Edge minEdge=new Edge(vertex, vertex, Double.MAX_VALUE); for(Vertex<T> now:map.keySet()){ edge=map.get(now); //newEdge为now到节点j的边 Edge newEdge=now.hasNeighbourVertex(vertex); if(newEdge!=null&&newEdge.getWeight()<edge.getWeight()){ //如果这个边的距离小于之前边的距离,就将边替换成这个到节点j的边 edge=newEdge; map.put(now, edge); } if(edge.getWeight()<minEdge.getWeight()){ //更新minEdge minEdge=edge; } } //这里设定边的方向是不在树上的v(为起始点)到树上的u //这条边的起始点是不在树上的,是下一个加入生成树的节点 vertex=minEdge.getBeginVertex(); } return result; }
到此,关于“如何使用Java二分查找”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。