我们讲了各种数据 结构之后,比如讲了线性表了,讲了栈和队列,讲了树和二叉树,讲了图之后呢,我们最后还有两个专题,
一个叫查找,一个叫排序,我们先看看查找,查找包括哪些内容啊,第一个线性表的查找,数组或者链表的查找,这是一个,还有一个呢,
树的查找,涉及到的一个概念二叉查找树,还有一个是哈希表的查找,哈下表的查找开始我们讲集合的时候其实已经接触过了,
最后我们再讲一下Java里面有哪些查找树和哈希表,这就是我们查找树的内容,线性表的查找我们以数组为例来进行讲解,
线性查找有顺序查找和折半查找,跟底层采用什么存储结构是没有关系的,只不过我们目前是采用数组来实现,大家也可以
采用相关的链表,现在我们先来看这一部分
1. 顺序查找:
比如我们这里面定义了一个数组,这个数组里面存了分数,我要在这里面找一个分数,找100分,在成绩中查找分数是100的
第一个分数,怎么办,有没有顺序,他是不是大小排列的,不是大小排列的,他不是大小排列的,所以这个时候你只能干什么,
只能逐个来比较,最终找到第一个100的,这个效率是高还是低,这个效率是低的,但是你没有办法,你只能这么来,要是学生
呢,就复杂了呗,数组里面放的就不是int了,成了Student了,那也类似吗,但是还有一种情况,提高效率的话,这个数组里面
放的不能是分数了,他现在是不是有序的,有序的我们就能改变一种思路,你没有必要一个一个找了,可以折半查找,我要找
8份,你怎么找8分,我先找最中间的那个,这个6居然比8小,只要这一半,可以再折半,每次砍掉长度的½,这个效率是非常高的,
但是他有前提条件,有什么条件,它是有序的,还有一点呢,那对这个折半查找,他要求采用数组,要是你不采用数组的话,
他底层链表也是不连续的,这样一来效率也不怎么高,这是我们所说的
我们再来看看折半查找,折半查找也叫二分查找,使用这种查找的话需要满足两个条件
1. 第一个要求是顺序存储结构
2. 第二个必须是有序的,比如这个数组从5一直到52,是不是一直都是有序的,这种情况下我们要是再按照顺序查找的话
我们就会发现他的缺点,效率比较低,那么我们怎么来办,折半查找,我们来看一下,我们要在这里找21,该怎么找,就是
这么来找,我们上面写的0到10是什么意思,是索引,在往下边是分数,是两个指针,也就是两个整形的变量,low首先指向
第一个,是0吧,high指向最后,是10,(0+10)/2=5,整除就可以了,5,mid是什么意思,中间,索引就是5,你找的是谁,我们找
的就是21,拿着21跟56比,这一半就全部排出了,肯定不在有一半了,一共有一亿个树,一下子就消灭了5000万,5000万
不用考虑了,哪还有5000万呢,在分一次就是2500万,是不是又没有了,我们讲过2倍2倍的增,现在我们是½的减,这个速度是
非常快的,那告诉我下一步该怎么办了,这个Low变不变,;low还是0,low是不变的,mid是计算出来的,下次改的是high,
起点还是low,终点是high,它的索引是4,怎么成了4了,之前那个幂的是5,5前面这个一个呗,找他就可以了,
high=mid-1=4,一个是0,一个是4,头0尾4,取中间值,(0+4)/2=2,mid等于2,拿着这个21和19比,这个肯定不会的,就排除了
那下一步告诉我指针该怎么变,low和high该怎么变,high要不要变,high不变,范围又是这一块了,high不变还是4,
low应该是几,是mid+1,是不是3,(3+4)/2你可别来个3.5,没有3.5,3,mid指向3,指向3了,找到了,他这个时间复杂度是多少啊
就是我们之前说的log2 n,每次他都会减少二分之一,这个log2 n是什么含义,这个n和log2 n有什么差别,如果n是10亿次,
如果你的级别是n,你就得找10亿次,但是如果你是log2 n,n给他10亿的话,就是2的30次方就是10亿,30次就够了,20次和
10亿次天差地别,这你知道这个效率有多高,明确一下,这个效率是特别高的,这是我们讲的一个内容,我们举了这个例子
好在什么程度,我们的high动了一次,high要变成mid-1,下次再比的话low又要变一下,low要变成mid+1,这里面就是
我们要写的一个算法,你告诉我他什么时候可以结束啊,什么时候开始结束,这个值等于21的时候就结束了,是那样吗
那你给我找个85,找个85的,这里面没有85,一个80,一个88,那怎么办,那就是按照刚才的规则一直在动,动来动去,
就会出现一个情况,什么情况,这个图已经很明白了,low,high,取中间,low等于9,mid等于9,到这儿还没有结束呢,
结果拿着88和85相比,他是不是在这一边呢,结果变成low等于9,mid现在也是9,他在这边的就是high了,high等于mid-1
他的前一个,high变成8了,你看怎么了,low比high还大,说明没有找不到,所以找到没找到,内容都在这儿,
这是我们讲的递归,明确折半查找的思路,第二个知道他的时间复杂度,log2 n级别的,这个效率特别高,下边我们
就来写这个代码了
package com.learn.search;
/**
* 查找功能:在分数中查询指定分数的索引,在第几个位置
*
* 关于这个顺序的查找非常的简单,
* 那我们分析一下这个时间复杂度是多少,
* 他的时间复杂度写一个大欧,
* T(n) = O(n)
* 如果这个数组的长度是n个的话,
* 那平均下来会找一半,去掉系数是O(n)
* 所以这个数如果几百万的话,查找次数还是挺多的
* 效率还是挺低的
* 再写一个S(n),空间复杂度是多少
* 不需要用到什么变量,只要一个index,一个i
* 别的他不需要,所以这个不需要占用更多的空间
* S(n) = O(1)
* 效率低下
* 这个讲数组操作的时候都应该已经讲过
* 非常简单
* 逐个比较,如果找到,返回数据或者索引
* 找不到就返回null或者-1
* 可以是顺序表,也可以是链表
* 在各个节点查找概率相同的情况下,
* 默认的查询长度是一半,
* 所以时间复杂度是O(n)
* 这个非常的简单
* @author Leon.Sun
*
*/
public class TestSearch1 {
public static void main(String[] args) {
/**
* 第一步给定分数数组
*
* 给定数组
*/
int [] scoreArr = {89,45,78,45,100,98,86,100,65};
/**
* 给定要查找的分数
*
* 分数就是他了
*
* 找个65
*/
// int score = 100;
/**
* 一共9个数,索引是8
*/
// int score = 65;
/**
* 找个650,是-1
*/
int score = 65;
/**
* 完成查找
*
* 最终我们会得到一个index
*
* 默认的index是-1
*/
// int index = -1;
/**
* 最难的这一步怎么办
* 能怎么办,来一个for循环
*
* 但是对于查找我们要提取一个方法
*/
// for(int i=0;i<scoreArr.length;i++) {
// /**
// * 逐个判断
// */
// if(scoreArr[i]==score) {
// /**
// * 那怎么办,找到了没有
// * 能return 吗,return那main方法就结束了
// * 这不能用return,index就等于i
// * 找到就是i
// */
// index = i;
// /**
// * 再加个操作
// * 找到100后面就不比了,
// *
// * break一下也就不循环了
// */
// break;
// }
// }
/**
* 输出结果
*
* 输出一下,怎么输出啊
* 那就看index等于几了呗
* 如果index等于-1,那就输出一下不存在
*
* 一共是分4步,我们都一斤完成3步了
*
*/
// if(index==-1) {
// System.out.println("该分数不存在");
// }else {
// /**
// * 100的索引是4,是4吗
// *
// */
// System.out.println(score + "索引是:" + index);
// }
/**
* 怎么查找,刚才我们要查找的话,你不仅知道你要干什么
* 你还要知道怎么来完成,你们知道这个思路吗,
* 现在不用了,你只要知道你想干什么就行了
* 我想查找,直接调用一个search,
* 这个他里面是怎么实现的,我才不管呢
*
* 这边传谁,传一个scoreArr,再传一个score
* 有没有问题,没有了,
*/
int index = search(scoreArr,score);
}
/**
* 返回值是什么,是int,索引吗
* 需要传一个什么内容,
*
* 这里面我们应该传一个数组,还要穿一个score
*
* 方法提取,这个方法就可以重用
*
* 我们这里改一下不叫scoreArr,难道只能改分数吗
* 不仅可以找分数,还可以找其他的
*/
public static int search(int[] arr,int key) {
int index = -1;
for(int i=0;i<arr.length;i++) {
if(arr[i]==key) {
index = i;
break;
}
}
if(index==-1) {
System.out.println("该分数不存在");
}else {
System.out.println(key + "索引是:" + index);
}
/**
* 返回这个索引就行了
*/
return index;
}
}
package com.learn.search;
/**
* 告诉我他又什么前提,
* 前提是什么,第一个顺序结构,第二个按照关键字有序排列
* 满足这两个条件
* @author Leon.Sun
*
*/
public class TestSearch2 {
public static void main(String[] args) {
/**
* 第一步干什么啊,给定数组
*/
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
/**
* 第二步给定要查找的值
* 你要找谁,找key
*/
// int key = 2;
/**
* 10的索引是几
*/
int key = 10;
/**
* 再找一个6
*/
// int key = 6;
/**
* 进行查找
* 这个查找叫折半查找或者叫二分查找
*/
// int index = -1;
int index = binarySearch(array,key);
/**
* 输出结果
*
* 最终要返回他的索引
*/
if(index==-1) {
System.out.println("不存在");
}else {
System.out.println(key + "的索引是" + index);
}
}
/**
* 你知道我们这里面该怎么办
* 两种方案:
* 第一个方案:不使用递归
* @param array
* @param key
* @return
*/
public static int binarySearch(int[] array,int key) {
/**
* 首先指定low和high
*/
int low = 0;
int high = array.length - 1;
/**
* 折半查找
*
* while一直查找
* 如果low一直小于high就可以一直进行
* 如果Low小于high就一直循环着,
* 循环怎么办,
*
* 你看他什么时候循环就结束了,
* 第一个return了就结束了
* 找到了就结束了,还有一个循环完了就结束了
* low大于high了及
* 这里改成小于等于,
* 所以有些细节方面,
* 总体思路明白当是我们写代码的时候要注意细节,
* 不使用递归的我们先想到这儿,
* 这和递归有关系吗,
* 折半查找还有递归
* 差不多吧,我觉得和递归有关系
* 有什么关系,
* 首先这个数组我要进行折半查找,
* 起始点是0,终止点是10,
* 下一次我要在这个数组里面再次进行折半查找
* 我们把这部分看成是一个数组呗,
* 起始是0,这是4吗,
* 下一次我要在这两个里面进行折半查找吗
* 这是3这是4,
* 递归查找,我们可以把数组的范围缩小
* 指定他的起始和终止位置
* 一直在进行折半查找
* 我们的操作一直在重复
* 从这个角度来说我们再来写一个折半查找的程序
*/
while(low<=high) {
/**
* 下一步要求得mid
*
* 这个mid半天没变过
*/
int mid = (low+high)/2;
/**
* 判断它是否等于
*
* 我们看一下这一步是不是可以省略
* 直接进行折半查找就可以了
* 是否等于,等于就直接返回
* 这是key等于它
*/
if(key==array[mid]) {
return mid;
}else if(key<array[mid]) {
/**
* key小于array[mid],
* 就是key是21,你找的是21,
* 中间是56,是不是给high,low不用给
*
*/
high = mid-1;
}else {
/**
* 这个else意味着一个含义
* key>array[mid]
*/
low = mid + 1;
}
}
/**
* 实在找不到先返回-1
*
* 如果没找到就返回-1
*/
return -1;
}
}
package com.learn.search;
/**
* 这是我们说的折半查找
* 折半查找给了大家两种思路,
* 哪两种思路啊,
* 第一个是非递归的,
* 第二个是递归的,
* 那非递归的告诉我时间复杂度是多少,
* 然后空间复杂度
* S(n) = O(1);
* T(n) = O(log2 n)
* 不管你循环了多少次,
* 并没有分配更多的变量,
*
* 当我们采用的递归的话,
* 递归怎么办,
* 他是不是还是每次减少一半,
* 这实现上还是差不多的,
* T(n) = O(log2n);
* 花的时间是差不多的,
* 因为都是每次找一半,
* 但是空间复杂度S(n)呢,
* 每一次的空间复杂度是1,
* 但是他这个方法调用了logn次呗
* 我只调用一次这个方法是有限的,
* 可是调用了log2 n次了,
* 所以我们可以让1*log2n
* S(n) = O(1*log2n);
* 所以最后就是log2n
* S(n) = O(log2 n);
* 重复的调用,重复的分配空间,
* 这样他花的空间就比较多的,
* 从这个角度来说我们应该有哪一个,
* 使用非递归的,
* 那我们怎么还来讲一下递归的,
* 在这个例子来说代码差不多
* 就找这种分析问题的思路,
* 扩展一下这种思路,
* 也是可以这么来解决的,
* 到这里我们就把线性表查找里的顺序查找和折半查找给大家讲了
* 顺序查找非常的简单,折半查找是我们的一个重点,
* 首先掌握算法,
* 然后能写出代码,
* 最后一步一定要知道折半查找的时间复杂度和空间复杂度
* 尤其他的时间复杂度,logn这个性能非常高的,
*
* @author Leon.Sun
*
*/
public class TestSearch3 {
public static void main(String[] args) {
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// int key = 2;
// int key = 10;
int key = 60;
// int index = -1;
int index = binarySearch(array,key);
if(index==-1) {
System.out.println("不存在");
}else {
System.out.println(key + "的索引是" + index);
}
}
/**
* 我们要使用递归
* 大家想一下,刚才我们使用递归的话,是什么不一样
* 一样的是什么,不一样的是什么,
* 这是一次递归,这又是一次递归,
* 这还是一次递归,相同的是什么,
* 你永远找到的是21,你找的数据永远在这儿,
* 不同的是什么意思,
* low和high不一样,范围不一样了
* 初始状态从0到10
* 那么基于这一点的话我们开始写程序了
* 我们要进行这样的一个查找,
* 我们要在这里面找这个值,
* @param array
* @param key
* @return
*/
public static int binarySearch(int[] array,int key) {
int low = 0;
int high = array.length - 1;
/**
* 第一次找的话整个数组,key是21,low是0,high是最后一个元素
*
*/
return binarySearch(array, key, low, high);
}
/**
* 我们增加两个参数,low和high
* 因为每次调用array和key是相同的,
* 但是low和high两个是一样的,这两个是不一样的
*
* 怎么来理解,一下子传入4个参数,
*
* 每次递归array和key都是一样的,
* 不一样的是low和high
* 两个都相当于指针,
*
* @param array
* @param key
* @param low
* @param high
* @return
*/
public static int binarySearch(int[] array,int key,int low,int high) {
/**
* 请问这里面该怎么写,递归
* 首先要写递归的结束条件,
* 如果low大于high了,
* 你传进来的low已经大于high了,
* 那就没找到呗,
* 这是个结束条件
*
* low大于high,
*/
if(low>high) {
return -1;
}
/**
* 这是中间
*
* 获取mid
*/
int mid = (low+high)/2;
/**
* 找到就结束了
*/
if(key==array[mid]) {
return mid;
}else if(key<array[mid]) {
/**
* key找的是它,
* 下面我要重复刚才的操作呗
* 数据在array里面放着呢,
* 查找的还是key,你现在找的是21,
* 现在找到56这里了,我们要在前面的位置找,
* low还是0,high等于mid-1,
* low没有变,high=mid-1
*
* 我们都写上一个return
*
* 小于就递归查找
*/
return binarySearch(array, key, low, mid-1);
}else {
/**
* 再来个else,就相当于key>array[mid]
* 他在右边找,
* 这是哪个内容,21大于19了,
* high没变,low变了,
* 还是要进行折半查找,
* 那这个时候要怎么办,
* 比如high没有变,
* low变成mid+1,
* 别忘了写了return
*
* 大于也递归查找
*/
return binarySearch(array, key, mid+1, high);
}
}
}