本文纲要
- 二分查找
思路分析
代码实现 - 冒泡排序
思路分析
分步实现
完整实现 - 递归
概念与示例
求阶乘案例
递归内存图 - 快速排序
思路分析
核心代码实现
完整递归实现 - Arrays工具类
toString
sort
binarySearch
二分查找
1 ) 思路分析
二分查找的前提条件:数组元素必须按照大小顺序排列。
核心思想:每次去掉一半的查找范围,在剩余的范围中继续查找,直到找到目标元素或范围无效。
- 定义两个变量
min和max,表示当前查找范围(最小索引和最大索引)。 - 根据
min和max计算出中间位置mid:mid = (min + max) / 2。 - 判断
arr[mid]与目标值的关系:- 相等:直接返回
mid。 - 目标值较小:说明目标在左半边,更新
max = mid - 1。 - 目标值较大:说明目标在右半边,更新
min = mid + 1。
- 相等:直接返回
- 循环条件:
min <= max。 - 如果
min > max,说明元素不存在,返回-1。
2 ) 代码实现
项目结构:
t
src/
└── com/
└── wb/
└── arrsearch/
└── MyBinarySearchDemo.java
java
package com.wb.arrsearch;
public class MyBinarySearchDemo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int number = 11;
// 1. 我现在要干嘛? --- 二分查找
// 2. 我干这件事情需要什么? --- 数组 元素
// 3. 我干完了,要不要把结果返回调用者 --- 把索引返回给调用者
int index = binarySearchForIndex(arr, number);
System.out.println(index);
}
private static int binarySearchForIndex(int[] arr, int number) {
// 1. 定义查找的范围
int min = 0;
int max = arr.length - 1;
// 2. 循环查找 min <= max
while (min <= max) {
// 3. 计算出中间位置 mid
int mid = (min + max) >> 1; // 右移1位相当于除以2
// mid指向的元素 > number
if (arr[mid] > number) {
// 表示要查找的元素在左边
max = mid - 1;
} else if (arr[mid] < number) {
// mid指向的元素 < number
// 表示要查找的元素在右边
min = mid + 1;
} else {
// mid指向的元素 == number
return mid;
}
}
// 如果min大于了max就表示元素不存在,返回-1
return -1;
}
}
运行结果:查找 3 返回索引 2;查找 11 返回 -1。
冒泡排序
1 ) 思路分析
冒泡排序的核心:相邻元素两两比较,小的放前面,大的放后面,每一轮确定一个最大值放到最右边。
- 如果有
n个数据,需要比较n-1轮。 - 每一轮结束后,下一轮比较时就会少一个元素参与(因为最右边的最大值已经确定)。
2 )分步实现
先按轮次拆开,逐步理解每一轮的操作。
java
package com.wb.mybubble;
public class MyBubbleSortDemo {
public static void main(String[] args) {
int[] arr = {3, 5, 2, 1, 4};
// 期望结果: 1 2 3 4 5
// 第一轮:把最大值5找出,并把它放到数组的最右边
// -1 是为了让索引不超出范围
for (int i = 0; i < arr.length - 1 - 0; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
printArr(arr);
// 第二轮
for (int i = 0; i < arr.length - 1 - 1; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
printArr(arr);
// 第三轮
for (int i = 0; i < arr.length - 1 - 2; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
printArr(arr);
// 第四轮
for (int i = 0; i < arr.length - 1 - 3; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
printArr(arr);
}
private static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
完整实现
将四轮循环合并为外层循环控制轮次,内层循环负责实际比较,内层循环的上限为 arr.length - 1 - i。
java
package com.wb.mybubble;
public class MyBubbleSortDemo2 {
public static void main(String[] args) {
int[] arr = {3, 5, 2, 1, 4};
bubbleSort(arr);
}
private static void bubbleSort(int[] arr) {
// 外层循环控制的是次数,比数组的长度少一次
for (int i = 0; i < arr.length - 1; i++) {
// 内层循环就是实际比较的
// -1 是为了让数组不要越界
// -i 每一轮结束之后,我们就会少比一个数字
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;
}
}
}
printArr(arr);
}
private static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
递归
1 ) 概念与示例
递归:方法定义中调用方法本身。把复杂问题层层转化为与原问题相似的规模更小的问题。
递归的两个核心:
- 递归出口 :必须要有结束条件,否则会导致栈内存溢出(
StackOverflowError)。 - 递归规则:找到将大问题拆分为小问题的规律,且每次递归的参数更靠近出口。
先用循环方式求 1~100 的和,再改为递归方式。
java
package com.wb.myfactorial;
public class MyFactorialDemo1 {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
System.out.println(sum);
}
}
递归实现:
java
package com.wb.myfactorial;
public class MyFactorialDemo2 {
public static void main(String[] args) {
int sum = getSum(100);
System.out.println(sum);
}
private static int getSum(int i) {
// 1~100之间的和
// 100 + (1~99之间的和)
// 99 + (1~98之间的和)
// ...
// 1
// 方法的作用: 求 1~i 之间和
if (i == 1) {
return 1;
} else {
return i + getSum(i - 1);
}
}
}
2 ) 求阶乘案例
需求:用递归求 5 的阶乘,并在控制台输出结果。
阶乘定义:5! = 5 × 4 × 3 × 2 × 1。
java
package com.wb.myfactorial;
public class MyFactorialDemo3 {
public static void main(String[] args) {
int result = getJc(5);
System.out.println(result);
}
private static int getJc(int i) {
// 1. 一定要找到出口
if (i == 1) {
return 1;
} else {
// 2. 递归的规则
return i * getJc(i - 1);
}
}
}
3 ) 递归内存图
以 getJc(5) 为例,方法调用与返回的过程如下:
getJc(1) getJc(2) getJc(3) getJc(4) getJc(5) main getJc(1) getJc(2) getJc(3) getJc(4) getJc(5) main #mermaid-svg-evQw95VY2WrDCXMq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-evQw95VY2WrDCXMq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-evQw95VY2WrDCXMq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-evQw95VY2WrDCXMq .error-icon{fill:#552222;}#mermaid-svg-evQw95VY2WrDCXMq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-evQw95VY2WrDCXMq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-evQw95VY2WrDCXMq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-evQw95VY2WrDCXMq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-evQw95VY2WrDCXMq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-evQw95VY2WrDCXMq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-evQw95VY2WrDCXMq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-evQw95VY2WrDCXMq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-evQw95VY2WrDCXMq .marker.cross{stroke:#333333;}#mermaid-svg-evQw95VY2WrDCXMq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-evQw95VY2WrDCXMq p{margin:0;}#mermaid-svg-evQw95VY2WrDCXMq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-evQw95VY2WrDCXMq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-evQw95VY2WrDCXMq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-evQw95VY2WrDCXMq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-evQw95VY2WrDCXMq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-evQw95VY2WrDCXMq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-evQw95VY2WrDCXMq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-evQw95VY2WrDCXMq .sequenceNumber{fill:white;}#mermaid-svg-evQw95VY2WrDCXMq #sequencenumber{fill:#333;}#mermaid-svg-evQw95VY2WrDCXMq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-evQw95VY2WrDCXMq .messageText{fill:#333;stroke:none;}#mermaid-svg-evQw95VY2WrDCXMq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-evQw95VY2WrDCXMq .labelText,#mermaid-svg-evQw95VY2WrDCXMq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-evQw95VY2WrDCXMq .loopText,#mermaid-svg-evQw95VY2WrDCXMq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-evQw95VY2WrDCXMq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-evQw95VY2WrDCXMq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-evQw95VY2WrDCXMq .noteText,#mermaid-svg-evQw95VY2WrDCXMq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-evQw95VY2WrDCXMq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-evQw95VY2WrDCXMq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-evQw95VY2WrDCXMq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-evQw95VY2WrDCXMq .actorPopupMenu{position:absolute;}#mermaid-svg-evQw95VY2WrDCXMq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-evQw95VY2WrDCXMq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-evQw95VY2WrDCXMq .actor-man circle,#mermaid-svg-evQw95VY2WrDCXMq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-evQw95VY2WrDCXMq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 getJc(5)5 * getJc(4)4 * getJc(3)3 * getJc(2)2 * getJc(1)return 1return 2return 6return 24return 120
- 方法调用时依次进栈,到达出口
i == 1时返回1,然后依次出栈计算结果。 - 如果没有出口,方法会无限进栈,最终导致
StackOverflowError。
快速排序
1 ) 思路分析
快速排序(快排)利用递归,每一次递归以左边第一个数作为 基准数,将比基准数小的放左边,比基准数大的放右边,从而确定基准数在数组中应存入的正确位置。
一次递归的步骤:
- 从右边开始找比基准数小的元素。
- 从左边开始找比基准数大的元素。
- 交换两个元素的位置。
- 重复上述过程,直到左右两个指针相遇。
- 基准数归位(与相遇位置的元素交换)。
一次递归结束后,基准数左边的元素都比它小,右边的都比它大,但左右两边内部的顺序尚未确定。接下来对左右两部分分别递归进行同样的操作。递归停止条件:left > right。
2 ) 核心代码实现(只确定一个基准数的位置)
java
package com.wb.myquitesort;
public class MyQuiteSortDemo {
public static void main(String[] args) {
// 1. 从右开始找比基准数小的
// 2. 从左开始找比基准数大的
// 3. 交换两个值的位置
// 4. 红色继续往左找,蓝色继续往右找,直到两个箭头指向同一个索引为止
// 5. 基准数归位
int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
quiteSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void quiteSort(int[] arr, int left, int right) {
int left0 = left;
int right0 = right;
// 计算出基准数
int baseNumber = arr[left0];
while (left != right) {
// 1. 从右开始找比基准数小的
while (arr[right] >= baseNumber && right > left) {
right--;
}
// 2. 从左开始找比基准数大的
while (arr[left] <= baseNumber && right > left) {
left++;
}
// 3. 交换两个值的位置
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
// 基准数归位
int temp = arr[left];
arr[left] = arr[left0];
arr[left0] = temp;
}
}
3 ) 完整递归实现
加入递归停止条件和左右递归调用。
java
package com.wb.myquitesort;
public class MyQuiteSortDemo2 {
public static void main(String[] args) {
int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
quiteSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void quiteSort(int[] arr, int left, int right) {
// 递归停止条件
if (right < left) {
return;
}
int left0 = left;
int right0 = right;
// 计算出基准数
int baseNumber = arr[left0];
while (left != right) {
// 1. 从右开始找比基准数小的
while (arr[right] >= baseNumber && right > left) {
right--;
}
// 2. 从左开始找比基准数大的
while (arr[left] <= baseNumber && right > left) {
left++;
}
// 3. 交换两个值的位置
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
// 基准数归位
int temp = arr[left];
arr[left] = arr[left0];
arr[left0] = temp;
// 递归处理左边部分
quiteSort(arr, left0, left - 1);
// 递归处理右边部分
quiteSort(arr, left + 1, right0);
}
}
运行结果:1 2 3 4 5 6 7 8 9 10
Arrays工具类
java.util.Arrays 类提供了操作数组的静态方法,常用方法:
toString(array):将数组内容拼接成字符串返回。sort(array):对数组进行升序排序(底层使用快速排序)。binarySearch(array, key):使用二分查找法查找元素索引,数组必须有序。
代码示例
java
package com.wb.myutils;
import java.util.Arrays;
public class MyArraysDemo {
public static void main(String[] args) {
int[] arr = {3, 2, 4, 6, 7};
// 1. toString: 将数组转换为字符串
System.out.println(Arrays.toString(arr)); // [3, 2, 4, 6, 7]
// 2. sort: 排序(升序)
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [2, 3, 4, 6, 7]
// 3. binarySearch: 二分查找(数组必须有序)
int[] sortedArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int index = Arrays.binarySearch(sortedArr, 10);
System.out.println(index); // 9
// 查找不存在的元素: 返回 -插入点 - 1
int index2 = Arrays.binarySearch(sortedArr, 11);
System.out.println(index2); // -11 (-10 - 1)
int index3 = Arrays.binarySearch(sortedArr, 0);
System.out.println(index3); // -1 (-0 - 1)
}
}
注意点:
binarySearch调用前数组必须有序。- 如果元素存在,返回实际索引。
- 如果元素不存在,返回
-插入点 - 1。插入点是指该元素如果存在应该在的索引位置,减1是为了避免与索引0冲突(例如查找0时返回-1而不是0)。
总结
以上内容涵盖了数组高级操作中的二分查找、冒泡排序、递归、快速排序以及 Arrays 工具类的核心知识与代码实现,适合作为 Java 基础阶段的算法入门参考