1.基本查找/顺序查找
指的是通过数组、集合之类的容器把数据装起来之后通过遍历查找
一般情况使用循环即可
例如数组:
int[] arr = {1, 2, 3, 90, 4, 7, 18};
int a = 90, j = 0;
int[] flag = new int[10];
for (int i = 0; i < arr.length; i++) {
if (a == arr[i]) {
flag[j++] = i;
}
}
返回了所有的符合元素值的索引
当然返回数组的长度是不确定的,为了更加的符合要求,我们可以使用集合来存储符合条件的索引
2.二分查找
也叫做折半查找
元素必须是有序的,从小到大,或者从大到小都是可以的。
如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。
核心思想:
- 使用一个
min
变量存储第一个元素的索引 - 使用一个
max
变量存储最后一个元素的索引 - 每次都使用要查找的元素和
mid = (min + max)/2
指向的元素进行比较- 如果目标元素大与mid指向的元素:说明在右边的那一侧,使
min = mid +1
,之后重复上面的步骤 - 如果小于:说明在左边的一侧,使用
max = mid - 1
,重复之前的操作
- 如果目标元素大与mid指向的元素:说明在右边的那一侧,使
- 结束:
- 目标元素等于 mid指向的元素
min
>max
:结束,说明元素不在数组或者集合中,并且大于其中的所有元素min
<max
:结束,说明元素不再数组或者集合中,并且小于其中的所有元素
代码示例:
public static void main(String[] args) {
//二分查找/折半查找
//核心:
//每次排除一半的查找范围
//需求:定义一个方法利用二分查找,查询某个元素在数组中的索引
//数据如下:{7, 23, 79, 81, 103, 127, 131, 147}
int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
System.out.println(binarySearch(arr, 150));
}
public static int binarySearch(int[] arr, int number){
//1.定义两个变量记录要查找的范围
int min = 0;
int max = arr.length - 1;
//2.利用循环不断的去找要查找的数据
while(true){
if(min > max){
return -1;
}
//3.找到min和max的中间位置
int mid = (min + max) / 2;
//4.拿着mid指向的元素跟要查找的元素进行比较
if(arr[mid] > number){
//4.1 number在mid的左边
//min不变,max = mid - 1;
max = mid - 1;
}else if(arr[mid] < number){
//4.2 number在mid的右边
//max不变,min = mid + 1;
min = mid + 1;
}else{
//4.3 number跟mid指向的元素一样
//找到了
return mid;
}
}
}
3. 插值查找
插值查找是在二分查找的基础上改进的算法,使算法的时间效率更高
我们 在使用二分查找的时候mid
的值总是取区间的一半
使用插值查找可以估测目标元素的位置
核心思想:
mid=low + (key-a[low])/(a[high]-a[low])*(high-low)
- 这个公式是根据比例关系来估算
key
的位置。如果key
更接近lowValue
,那么mid
就会更靠近low
;如果key
更接近highValue
,mid
就会更靠近high
。
lowValue = array[low]; highValue = array[high]
- 将
array[mid]
和key
进行比较,分为大于、等于、小于 - 结束条件和二分查找类似
4.斐波那契查找
在数学中有一个非常有名的数学规律:斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….(从第三个数开始,后边每一个数都是前两个数的和)。
然后我们会发现,随着斐波那契数列的递增,前后两个数的比值会越来越接近0.618,利用这个特性,我们就可以将黄金比例运用到查找技术中。
我们要使用斐波那契数列进行查找:
斐波那契数列是0、1、1、2、3、5、8、13等这样的数列,从第三项开始,每一项都等于前两项之和,即F(n)=F(n - 1)+F(n - 2)
。
基本思想:
- 首先要构建一个长度为斐波那契数的空数组。这个数组的长度是大于等于原有序数组长度的最小斐波那契数。
例如,原数组长度为8,斐波那契数列中大于等于8的最小数是13,那么就构建一个长度为13的空数组。
- 把原有序数组中的元素复制到空数组的前面部分,数组剩下的部分可以用原数组的最后一个元素来填充。
- 查找时,通过斐波那契数来划分查找区间。假设构建的数组长度为
F(k)
,将其分为两部分,一部分长度是F(k - 1)
,另一部分长度是F(k - 2)
。
假如我们要查找元素7,此时k = 7,F(k - 1)=F(6) = 8,F(k - 2)=F(5) = 5。
- 比较要查找的元素和虚拟数组中位置为
F(k - 1)
的元素。如果要查找的元素小于这个元素,就在长度为F(k - 1)
的区间继续查找;如果大于这个元素,就在长度为F(k - 2)
的区间继续查找。 - 随着查找的进行,不断根据斐波那契数列来划分区间,直到找到要查找的元素或者确定元素不存在。
斐波那契查找的平均性能比二分查找好,特别是在数据量较大且分布比较均匀的情况下,因为它可以根据数据的分布特点动态地调整查找区间。
代码示例较长,这里不再给出
5.分块查找
关于查找的解释很多,且总结不易,这里不再给出说明。读者可自行搜索学习
只给出代码:
public class BlockSearch {
public static void main(String[] args) {
int[] arr = {
1,3,7,9,
12,19,21,20,16,
27,22,32,34,
46,35,45,40
};
Block b1 = new Block(9, 0,3);
Block b2 = new Block(21,4,8);
Block b3 = new Block(34, 9,12);
Block b4 = new Block(46, 13, 16);
Block[] blocks = {b1,b2,b3,b4};
int number = 21;
int Index = GetIndex(blocks,arr,number);
System.out.println(Index);
}
private static int GetIndex(Block[] blocks, int[] arr, int number) {
int i = Getblockindex(blocks,number);
if (i == -1) {
return -1;
}
int star = blocks[i].getStarindex(), end = blocks[i].getEndindex();
for (int j = star; j <= end; j++) {
if (number == arr[j]) {
return j;
}
}
return -1;
}
private static int Getblockindex(Block[] blocks, int number) {
for (int i = 0; i < blocks.length; i++) {
if (number <= blocks[i].getMax()) {
return i;
}//从第一个数组的索引开始查找,从最小的开始。找到数据所在的分块
}
return -1;
}
}
class Block{
private int max;
private int starindex;
private int endindex;
public Block(int max, int starindex, int endindex) {
this.max = max;
this.starindex = starindex;
this.endindex = endindex;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getStarindex() {
return starindex;
}
public void setStarindex(int starindex) {
this.starindex = starindex;
}
public int getEndindex() {
return endindex;
}
public void setEndindex(int endindex) {
this.endindex = endindex;
}
}
6. 哈希查找
哈希查找可以满足在查找的同时添加数据到表中,这里也不再赘述。有兴趣的小伙伴可以搜索其他博客学习
7.树表查找
数据结构内容,这里不再赘述
8. 排序内容
1. 冒泡排序
核心思想:
- 相邻的元素两两比较, 大(小)的放右边, 小(大)的放左边。
- 第一轮比较完毕之后, 最大值(最小值)就已经确定, 第二轮可以少循环一次,后面以此类推。
- 如果数组中有n个数据, 总共我们只要执行n-1轮的代码就可以。
2.选择排序
核心思想:
- 从0索引开始,跟后面的元素一一比较。
- 小的放前面,大的放后面。(大的放前面,小的放后面)
- 第一次循环结束后,最小(大)的数据已经确定。
- 第二次循环从1索引开始以此类推。
3.插入排序
- 将0索引的元素到N索引的元素看做是有序的, 把N+1索引的元素到最后一个当成是无序的。
- 遍历无序的数据, 将遍历到的元素插入有序序列中适当的位置, 如遇到相同数据,插在后面(不用进行交换)。
N的范围:0~最大索引
这里给出代码示例:
public static void main(String[] args) {
int[] arr = { 1,44,5,67,4,17,19,29,40,23};
//找到无序索引
int starIndex = -1;//随意赋值
for (int i = 0; i < arr.length; i++) {
if(arr[i] > arr[i+ 1]) {
starIndex = i + 1;
break;
}
}
/*获取索引的第二种方法
starIndex = 1;
for (int i = 0; i < arr.length; i++) {
if(arr[i] <= arr[i+ 1]) {
starIndex += 1;
}
else
break;
}
System.out.println(starIndex);*/
//从无序索引开始遍历,把元素插入到有序中
for (int i = starIndex; i < arr.length; i++) {
for (int j = i; j > 0;j--) {
if(arr[j] <arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j-1];
arr[j - 1] = temp;
}
else
break;;
}
//内层循环也可以使用while循环实现
//内层循环的作用是和前面的有序数列进行比较,找出合适的位置插入
/*int j = starIndex;
while (j > 0 && arr[j] < arr[j- 1]) {
int temp = arr[j];
arr[j] = arr[j-1];
arr[j - 1] = temp;
j--;
}*/
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
4.快速排序
需要使用到递归思想
一个递归方法包含两个部分:基线条件和递归条件。
- 基线条件:用于终止递归,避免无限循环。它是一个简单的非递归的情况,当满足这个条件时,方法不再调用自身,直接返回结果。
例如:计算阶乘时,当输入为0或1,阶乘是1,这就是基线条件。
- 递归条件:是方法调用自身的部分,它会不断地将问题分解为更小的子问题,直到满足基线条件。
比如计算n的阶乘,递归条件就是n乘以(n - 1)的阶乘,即 n * factorial(n - 1) 。
快速排序的核心思想:
- 将排序范围中的第一个数字作为基准数,再定义两个变量
start,end
一般情况下使用第一个数字作为基准数,但也可以使用最后一个,随机的一个数字等等
- start从前往后找比基准数大的,end从后往前找比基准数小的。
- 在指向的过程种需要我们注意的是:一定是
end
先移动,之后才是start
这样才可以保证基准数前面的数字都是比基准数小的- 如果是倒序排序,大的数字数放前面,则需要
start
现移动
- 找到之后交换
start
和end
指向的元素, 并循环这一过程,直到start
和end
处于同一个位置,该位置是基准数在数组中应存入的位置,再让基准数归
位。 - 归位后的效果: 基准数左边的比基准数小, 基准数右边的比基准数大
如果是倒序排列,大的数字放在前面则与上面相反
给出代码示例:
public static void main(String[] args) {
int[] arr = {6, 4, 9,2,4, 5,0,11,19};
int i = 0;
int j = arr.length - 1;
QulickSort(arr, i ,j);
for (int i1 = 0; i1 < arr.length; i1++) {
System.out.print(arr[i1] + " ");
}
}
public static void QulickSort(int[] arr,int i ,int j) {
int start = i;
int end = j;
//最后:加上递归出口
if (start > end) {
return ;
}
int baseNumber = arr[i];
while (start != end) {
//end移动,找到比基准数小的数
while (true) {
if (end <= start || arr[end] < baseNumber) {
break;
}
end--;
}
//start移动找到比基准数大的数字
while (true) {
if (end <= start || arr[start] > baseNumber) {
break;
}
start++;
}
//交换位置
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
//完成了上面的交换,基准数归位
int temp = arr[start];
arr[start] = arr[i];
arr[i] = temp;
//递归的访问基准数左边和右边
QulickSort(arr, i, start - 1);
QulickSort(arr, start + 1, j);
}
5.其他排序方法
- 希尔排序
- 归并排序
- 堆排序
- 计数排序
- 桶排序
- 基数排序
9. Array类
这个类是操作数组的工具类
这里给出成员方法
方法 | 说明 |
---|---|
public static String toString(数组) | 把数组拼接成一个字符串 |
public static int binarySearch(数组,查找的元素) | 二分查找法查找元素 |
public static int[ ] copyOf(原数组,新数组长度) | 拷贝数组 |
public static int[ ]copyOfRange(原数组,起始索引,结束索引) | 拷贝数组(指定范围) |
public static void fill(数组,元素) | 填充数组 |
public static void sort(数组) | 按照默认方式进行数组排序 |
public static void sort(数组,排序规则) | 按照指定的规则排序 |
a.方法详解
给出一个数组:int[] arr = {1,5,7,9,10,11,18,29,33};
toString
//toString
System.out.println(Arrays.toString(arr));
binarySearch
二分查找我们需要特别注意以下的几点:
- 查找的数组需要是有序的
- 当查找的元素不在数组中时,方法会返回- 插入点 - 1(插入点指的是,这个元素假如在这个有序数组中,应该在的位置)
-1 是为了防止当插入点在0索引位置时,出现 -0
//二分查找
System.out.println(Arrays.binarySearch(arr, 10));//4
System.out.println(Arrays.binarySearch(arr, 11));//5
System.out.println(Arrays.binarySearch(arr, 34));//-10
copyOf
//拷贝数组
int[] arr2 = Arrays.copyOf(arr, 11);
System.out.println(Arrays.toString(arr2));
返回创建的一个新数组,这个数组的长度也决定了拷贝的内容
- 新数组的长度小于元素组:不完全拷贝
- 长度等于原数组:完全拷贝
- 大于:多出的位置补零
copyOfRange
//拷贝指定范围数组
int[] arr3 = Arrays.copyOfRange(arr, 0, 8);
//包头不包尾
System.out.println(Arrays.toString(arr3));
包左不包右:即在结束索引位置的元素不会被拷贝
在上述代码中:数组时从0 ~8 索引的,但是在拷贝时最后一位没有被拷贝,如要完全拷贝需要结束索引为9
sort
默认在底层使用的是快速排序
//sort排序
int[] arr4 = {3,1,11,19,4,6,3,6};
Arrays.sort(arr4);
System.out.println(Arrays.toString(arr4));
- 重载的
sort
方法
上述的sort
方法只能实现升序的排序,如果我们使用降序需要另一个方法
sort(引用类型数组, 接口的实现类)
底层使用的是插入排序 + 二分查找的方法
Integer[] arr5 = {3,1,11,19,4,6,3,6};
Arrays.sort(arr5, new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
// o1 - o2 升序
// o2 - o1 降序
System.out.println(Arrays.toString(arr5));
上述我们使用内部类来代替了接口的实现类,更加的方便。同时升序降序的不同在于内部类中重写方法的返回值不同 :
- o1 - o2 升序
- o2 - o1 降序
同时传入的数组是引用类型的数组,这里使用了int的包装类
10.lambda表达式
lambda表达式的作用是简化函数式接口的匿名内部类写法
例如我们上面写的代码:
Arrays.sort(arr5, new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
我们可以简写为:
Arrays.sort(arr5, (Integer o1, Integer o2) -> {
return o1 - o2;
}
);
lambda表达式是在JDK8之后的新语法
基本格式:
() -> {
//方法体
}
- ():代表匿名内部类中重写方法的形参
- ->:固定格式
- {}:对应方法的方法体
在这里我们回顾一下匿名内部类
- 匿名内部类是为了解决只用一次或很少次数的接口的实现类的简便写法,使用匿名内部类就不用再创建实现类
- 匿名内部类实际上是创建了一个实现类的对象,这个类实现的接口就是new后面的名字所代表的接口
我们说:Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:
- 有且仅有一个抽象方法的接口叫做函数式接口, 接口上方可以
@Functionallnterface
注解 - 加上注解之后可以判断是否为函数式接口
代码示例:
public static void main(String[] args) {
//基本的匿名内部类写法
method(new Swim() {
@Override
public void swimming() {
System.out.println("正在游泳 ~~~ ”);
}
});
//2.利用lambda表达式进行改写
method(
()->{
System.out.println("正在游泳 ~~~ ”);
}
}
//方法
public static void method(Swim s){
s.swimming();
}
//函数式接口
interface Swim{
public abstract void swimming();
}
上面我们介绍了表达式的完整格式,现在我们来学习它的省略格式
核心思想是:可推导,可省略
lambda的省略规则:
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略。
代码示例:
之前的写法:
Arrays.sort(arr5, new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//完整格式
Arrays.sort(arr5, (Integer o1, Integer o2) -> {
return o1 - o2;
}
);
简略格式:
Arrays.sort(arr5,(o1, o2)-> o1 - o2);
完。