为什么要学基础算法?
- 基础算法是编程的 “内功”,无论是面试还是实际开发(如数据处理、集合操作)都离不开。
- 本文聚焦查找算法、排序算法和递归思想,通过实例代码解析核心逻辑,帮你快速掌握这些 “必学知识点”。
一、查找算法:如何高效定位目标元素?
查找算法的核心是 “在数据集中快速找到目标元素”,常见的基础查找算法有 3 种:
1. 基本查找(顺序查找)
- 核心思想:从数据的第一个元素开始,逐个遍历比对,直到找到目标或遍历结束。
- 适用场景:无序数据、数据量小的场景(简单直接,无需预处理)。
- 代码实现:
- 需求 1:判断元素是否存在(返回
boolean); - 需求 2:返回所有匹配元素的索引(解决重复元素问题,返回
List<Integer>)。package com.hm.CommonAlgorithms; import java.util.ArrayList; import java.util.List; public class BasicSearch { public static void main(String[] args) { int[] arr={131,12,57,85,96,14,2,35,2}; System.out.println(basicSearch(arr, 96)); // 查找96是否存在 System.out.println(basicSearchIndex(arr, 2)); // 查找2的所有索引 } // 判断元素是否存在 public static boolean basicSearch(int[] arr,int value){ for (int i = 0; i < arr.length; i++) { if (arr[i] == value){ return true; } } return false; } // 返回所有匹配元素的索引(处理重复值) public static List<Integer> basicSearchIndex(int[] arr,int value){ List<Integer> list = new ArrayList<>(); for (int i = 0; i < arr.length; i++) { if (arr[i] == value){ list.add(i); } } return list; } }
- 需求 1:判断元素是否存在(返回
- 特点:
- 时间复杂度:
O(n)(最坏情况遍历所有元素); - 优点:简单、无需数据有序;缺点:数据量大时效率低。
- 时间复杂度:
2. 二分查找(折半查找)
- 核心思想:利用数据的 “有序性”,每次通过中间元素缩小一半查找范围(类似字典查字)。
- 前提条件:数据必须是有序的(升序或降序)。
- 基本步骤:
- 定义
min(起始索引)和max(结束索引); - 计算中间索引
mid = (min + max) / 2,比对arr[mid]与目标值; - 若
arr[mid] > 目标值,则目标在左半部分(max = mid - 1);反之在右半部分(min = mid + 1); - 重复步骤 2-3,直到找到目标(返回索引)或
min > max(返回-1,表示不存在)。package com.hm.CommonAlgorithms; public class Binarysearch { public static void main(String[] args) { int[] arr={7,8,12,27,63,89,90,95}; // 有序数组 System.out.println(binarySearch(arr, 95)); // 查找95的索引 } public static int binarySearch(int[] arr,int value){ int min=0; int max=arr.length-1; while (min<=max){ int mid=(min+max)/2; // 中间索引 if (arr[mid]==value){ return mid; // 找到目标,返回索引 }else if (arr[mid]> value) { max=mid-1; // 目标在左半部分,缩小max }else { min=mid+1; // 目标在右半部分,缩小min } } return -1; // 未找到 } }
- 定义
- 特点:
- 时间复杂度:
O(log n)(效率远高于顺序查找); - 优点:适合大数据量的有序集合;缺点:依赖数据有序,插入删除成本高。
- 时间复杂度:
3. 分块查找(索引查找)
- 核心思想:结合 “顺序查找” 和 “二分查找” 的优点,将数据分为多个 “块”(块内无序,块间有序),先定位块,再在块内查找。
- 分块原则:
- 块间有序:前一块的最大值 < 后一块的最小值;
- 块数建议:约为数据总量的开根号(如 16 个元素分 4 块)。
- 基本步骤:
- 创建 “索引表”:存储每个块的最大值、起始索引和结束索引;
- 查找索引表,确定目标元素所在的块;
- 在对应块内顺序查找目标元素。
package com.hm.CommonAlgorithms; public class BlockSearch { public static void main(String[] args) { int[] arr={16,5,9,12,21,18, 32,23,37,26,45,34, 50,48,61,52,73,66}; // 分3块,块间有序 // 定义3个块(存储块的最大值、起始/结束索引) Block b1=new Block(21,0,5); Block b2=new Block(45,6,11); Block b3=new Block(73,12,17); Block[] blockArr={b1,b2,b3}; // 索引表 int num=21; System.out.println(getIndex(blockArr, arr, num)); // 查找21的索引 } // 查找目标所在块,并在块内遍历 public static int getIndex(Block[] blockArr,int[] arr,int num){ int indexBlock=findIndexBlock(blockArr,num); // 确定块索引 if (indexBlock==-1) return -1; int startIndex=blockArr[indexBlock].getStartIndex(); int endIndex=blockArr[indexBlock].getEndIndex(); for (int i = startIndex; i <= endIndex; i++) { // 注意是<= if (arr[i]==num) return i; } return -1; } // 确定目标所在的块 public static int findIndexBlock(Block[] blockArr,int num){ for (int i = 0; i < blockArr.length; i++) { if (num<=blockArr[i].getMax()) return i; // 块间有序,小于等于当前块最大值即在此块 } return -1; } } // 块对象:存储块的最大值、起始索引、结束索引 class Block{ private int max; private int startIndex; private int endIndex; public Block() {} public Block(int max, int startIndex, int endIndex) { this.max = max; this.startIndex = startIndex; this.endIndex = endIndex; } // getter/setter public int getMax() { return max; } public int getStartIndex() { return startIndex; } public int getEndIndex() { return endIndex; } }
- 特点:
- 时间复杂度:介于
O(n)和O(log n)之间; - 优点:兼顾有序和无序场景,适合数据频繁动态变化的场景。
- 时间复杂度:介于
二、排序算法:如何让数据 “井然有序”?
排序算法的核心是 “将无序数据按规则(升序 / 降序)重新排列”,以下是 4 种基础排序算法的对比:
1. 冒泡排序
- 核心思想:相邻元素两两比较,大的元素 “冒泡” 到数组末尾(每轮确定一个最大值)。
- 基本步骤:
- 从 0 索引开始,相邻元素比较,若前 > 后则交换;
- 第一轮结束后,最大值已在数组末尾;
- 忽略已排序的末尾元素,重复上述步骤,直到所有元素有序。
package com.hm.CommonAlgorithms; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { int[] arr={20,0,2,7,13,52}; for (int i = 0; i < arr.length-1; i++) { // 控制轮数(n-1轮) for (int j = 0; j < arr.length-1-i; j++) { // 每轮比较次数递减 if (arr[j]>arr[j+1]){ // 前>后则交换 int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } System.out.println(Arrays.toString(arr)); // [0, 2, 7, 13, 20, 52] } }
- 代码关键:外层循环控制轮数(
arr.length - 1轮),内层循环控制每轮比较次数(arr.length - 1 - i次,i为轮次)。 - 特点:
- 时间复杂度:
O(n²); - 优点:简单易实现,稳定排序(相等元素不交换位置);缺点:效率低,适合小数据量。
- 时间复杂度:
2. 选择排序
- 核心思想:每轮从剩余元素中找到最小值(或最大值),放到已排序部分的末尾。
- 基本步骤:
- 从 0 索引开始,遍历剩余元素,记录最小值的索引;
- 将最小值与当前索引元素交换;
- 索引 + 1,重复步骤 1-2,直到所有元素有序。
package com.hm.CommonAlgorithms; import java.util.Arrays; public class SelectSort { public static void main(String[] args) { int[] arr={20,0,2,7,13,52}; for (int i = 0; i < arr.length-1; i++) { // 控制轮数 for (int j = i+1; j < arr.length; j++) { // 遍历剩余元素 if (arr[j]<arr[i]){ // 找到更小的元素则交换 int temp=arr[j]; arr[j]=arr[i]; arr[i]=temp; } } } System.out.println(Arrays.toString(arr)); // [0, 2, 7, 13, 20, 52] } }
- 特点:
- 时间复杂度:
O(n²); - 优点:交换次数少(每轮最多 1 次);缺点:不稳定(可能改变相等元素的相对位置)。
- 时间复杂度:
3. 插入排序
- 核心思想:将数组分为 “有序部分” 和 “无序部分”,从无序部分取元素,插入到有序部分的合适位置(类似整理扑克牌)。
- 基本步骤:
- 确定无序部分的起始索引(默认 0~i 为有序,i+1 开始为无序);
- 遍历无序元素,依次与有序部分元素比较,找到插入位置(小于前一个元素则交换,直到找到合适位置)。
package com.hm.CommonAlgorithms; import java.util.Arrays; public class InsertSort { public static void main(String[] args) { int[] arr={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48}; // 确定无序部分的起始索引(0~i有序,i+1开始无序) int startIndex=-1; for (int i = 0; i < arr.length-1; i++) { if (arr[i]>arr[i+1]){ startIndex=i+1; break; } } // 遍历无序元素,插入有序部分 for (int i=startIndex;i<arr.length;i++){ int j=i; while (j>0&&arr[j]<arr[j-1]){ // 向前比较,找到插入位置 int temp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=temp; j--; } } System.out.println(Arrays.toString(arr)); } }
- 特点:
- 时间复杂度:
O(n²)(但实际效率优于冒泡和选择排序,尤其对接近有序的数据); - 优点:稳定排序,适合小规模或部分有序的数据。
- 时间复杂度:
4. 快速排序(分治思想)
- 核心思想:通过 “分治” 将大数组拆分为小数组,以 “基准数” 为界,将数据分为 “小于基准” 和 “大于基准” 两部分,递归排序子数组。
- 基本步骤:
- 选基准数(通常为数组第一个元素);
- 从两端遍历,将小于基准的元素放左边,大于基准的放右边(分区);
- 基准数归位后,递归排序左半部分和右半部分。
package com.hm.CommonAlgorithms; import java.util.Arrays; public class quickSort { public static void main(String[] args) { int[] arr={6,1,2,7,9,3,4,5,10,8}; System.out.println(Arrays.toString(arr)); // 排序前 quickSort(arr,0,arr.length-1); System.out.println(Arrays.toString(arr)); // 排序后:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } // 参数:数组、起始索引、结束索引 public static void quickSort(int[] arr,int left,int right){ if (left>right) return; // 递归出口:范围无效时停止 int start=left; int end=right; int baseNumber=arr[left]; // 基准数(取第一个元素) // 分区:小于基准的放左,大于基准的放右 while(start!=end){ // 从后往前找小于基准的元素 while (true){ if (end<=start||arr[end]<baseNumber) break; end--; } // 从前往后找大于基准的元素 while (true){ if (end<=start||arr[start]>baseNumber) break; start++; } // 交换找到的两个元素 int temp=arr[start]; arr[start]=arr[end]; arr[end]=temp; } // 基准数归位(与start/end指向的元素交换) int temp=arr[left]; arr[left]=arr[start]; arr[start]=temp; // 递归排序左半部分和右半部分 quickSort(arr, left, start-1); quickSort(arr, start+1, right); } }
- 代码关键:递归出口(
left >= right时停止),分区逻辑(双指针交换元素)。 - 特点:
- 时间复杂度:平均
O(n log n),最坏O(n²)(可通过合理选择基准数优化); - 优点:效率高,适合大数据量;缺点:不稳定,递归可能占用额外栈空间。
- 时间复杂度:平均
三、递归:自己调用自己的 “魔法”
- 核心思想:将复杂问题分解为 “与原问题相似但规模更小的子问题”,通过递归调用解决子问题,最终汇总结果。
- 两大要素:
- 递归出口:当问题规模小到可直接解决时,停止递归(避免无限循环导致栈溢出);
- 递归规则:如何将大问题拆解为小问题(调用自身时参数需 “缩小规模”)。
- 实例解析:
- 求 1~n 的和:
sum(n) = n + sum(n-1),出口为sum(1) = 1; - 求 n 的阶乘:
factorial(n) = n * factorial(n-1),出口为factorial(1) = 1。public class Recursion { public static void main(String[] args) { //需求:递归求1-100之间的和 System.out.println(getSum(100)); //需求:用递归求5的阶乘 System.out.println(getFactorial(5)); } public static int getSum(int number){ //如果number==1,则返回1 if (number==1){ return 1; } //如果number>1,则返回number+getSum(number-1) return number+getSum(number-1); } public static int getFactorial(int number){ if (number==1){ return 1; } return number*getFactorial(number-1); } }
- 求 1~n 的和:
- 适用场景:树遍历、回溯算法、分治问题(如快速排序)等。
总结:算法选择指南
| 场景 | 推荐算法 | 核心考量 |
|---|---|---|
| 无序小数据查找 | 基本查找 | 实现简单 |
| 有序大数据查找 | 二分查找 | 效率优先(O(log n)) |
| 数据动态变化频繁 | 分块查找 | 平衡查找效率与维护成本 |
| 小规模数据排序 | 冒泡 / 插入排序 | 简单易实现 |
| 大规模数据排序 | 快速排序 | 效率优先(平均O(n log n)) |
| 问题可拆解为子问题 | 递归 | 简化复杂逻辑 |
结语
基础算法是编程的基石,掌握它们不仅能应对面试,更能培养 “解决问题的逻辑思维”。建议结合代码动手实践,尝试修改参数(如数据规模、有序性),观察算法表现,加深理解。算法的魅力在于 “一通百通”,学会这些基础后,再学习高级算法(如归并排序、哈希查找)会更轻松!
330

被折叠的 条评论
为什么被折叠?



