Java 基础算法入门:从查找、排序到递归,一篇搞懂核心逻辑

为什么要学基础算法?
  • 基础算法是编程的 “内功”,无论是面试还是实际开发(如数据处理、集合操作)都离不开。
  • 本文聚焦查找算法排序算法递归思想,通过实例代码解析核心逻辑,帮你快速掌握这些 “必学知识点”。

一、查找算法:如何高效定位目标元素?

查找算法的核心是 “在数据集中快速找到目标元素”,常见的基础查找算法有 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;
          }
      }

  • 特点
    • 时间复杂度:O(n)(最坏情况遍历所有元素);
    • 优点:简单、无需数据有序;缺点:数据量大时效率低。
2. 二分查找(折半查找)
  • 核心思想:利用数据的 “有序性”,每次通过中间元素缩小一半查找范围(类似字典查字)。
  • 前提条件:数据必须是有序的(升序或降序)。
  • 基本步骤
    1. 定义min(起始索引)和max(结束索引);
    2. 计算中间索引mid = (min + max) / 2,比对arr[mid]与目标值;
    3. arr[mid] > 目标值,则目标在左半部分(max = mid - 1);反之在右半部分(min = mid + 1);
    4. 重复步骤 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 块)。
  • 基本步骤
    1. 创建 “索引表”:存储每个块的最大值、起始索引和结束索引;
    2. 查找索引表,确定目标元素所在的块;
    3. 在对应块内顺序查找目标元素。
      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. 冒泡排序
  • 核心思想:相邻元素两两比较,大的元素 “冒泡” 到数组末尾(每轮确定一个最大值)。
  • 基本步骤
    1. 从 0 索引开始,相邻元素比较,若前 > 后则交换;
    2. 第一轮结束后,最大值已在数组末尾;
    3. 忽略已排序的末尾元素,重复上述步骤,直到所有元素有序。
      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. 选择排序
  • 核心思想:每轮从剩余元素中找到最小值(或最大值),放到已排序部分的末尾。
  • 基本步骤
    1. 从 0 索引开始,遍历剩余元素,记录最小值的索引;
    2. 将最小值与当前索引元素交换;
    3. 索引 + 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. 插入排序
  • 核心思想:将数组分为 “有序部分” 和 “无序部分”,从无序部分取元素,插入到有序部分的合适位置(类似整理扑克牌)。
  • 基本步骤
    1. 确定无序部分的起始索引(默认 0~i 为有序,i+1 开始为无序);
    2. 遍历无序元素,依次与有序部分元素比较,找到插入位置(小于前一个元素则交换,直到找到合适位置)。
      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. 快速排序(分治思想)
  • 核心思想:通过 “分治” 将大数组拆分为小数组,以 “基准数” 为界,将数据分为 “小于基准” 和 “大于基准” 两部分,递归排序子数组。
  • 基本步骤
    1. 选基准数(通常为数组第一个元素);
    2. 从两端遍历,将小于基准的元素放左边,大于基准的放右边(分区);
    3. 基准数归位后,递归排序左半部分和右半部分。
      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. 递归出口:当问题规模小到可直接解决时,停止递归(避免无限循环导致栈溢出);
    2. 递归规则:如何将大问题拆解为小问题(调用自身时参数需 “缩小规模”)。
  • 实例解析
    • 求 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);
          }
      }

  • 适用场景:树遍历、回溯算法、分治问题(如快速排序)等。

总结:算法选择指南

场景推荐算法核心考量
无序小数据查找基本查找实现简单
有序大数据查找二分查找效率优先(O(log n)
数据动态变化频繁分块查找平衡查找效率与维护成本
小规模数据排序冒泡 / 插入排序简单易实现
大规模数据排序快速排序效率优先(平均O(n log n)
问题可拆解为子问题递归简化复杂逻辑

结语

基础算法是编程的基石,掌握它们不仅能应对面试,更能培养 “解决问题的逻辑思维”。建议结合代码动手实践,尝试修改参数(如数据规模、有序性),观察算法表现,加深理解。算法的魅力在于 “一通百通”,学会这些基础后,再学习高级算法(如归并排序、哈希查找)会更轻松!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值