1、顺序查找
- public class OrderSearch {
- /**顺序查找平均时间复杂度 O(n)
- * @param searchKey 要查找的值
- * @param array 数组(从这个数组中查找)
- * @return 查找结果(数组的下标位置)
- */
- public static int orderSearch(int searchKey,int... array){
- for(int i=0;i<array.length;i++){
- if(array[i]==searchKey){
- return i;
- }
- }
- return -1;
- }
- /**测试查找结果
- * @param args
- */
- public static void main(String[] args) {
- int[] test=new int[]{1,2,29,3,95,3,5,6,7,9,12};//升序序列
- int index=OrderSearch.orderSearch(95, test);
- System.out.println("查找到的位置 :"+ index);
- }
- }
二分查找
算法思想:又叫折半查找,要求待查找的序列有序。每次取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。直到查找到了为止,否则序列中没有待查的关键字。
实现:
1.非递归代码
public static int biSearch(int []array,int a){ int lo=0; int hi=array.length-1; int mid; while(lo<=hi){ mid=(lo+hi)/2; if(array[mid]==a){ return mid+1; }else if(array[mid]<a){ lo=mid+1; }else{ hi=mid-1; } } return -1; }
2.递归实现
public static int sort(int []array,int a,int lo,int hi){ if(lo<=hi){ int mid=(lo+hi)/2; if(a==array[mid]){ return mid+1; } else if(a>array[mid]){ return sort(array,a,mid+1,hi); }else{ return sort(array,a,lo,mid-1); } } return -1; }
时间复杂度为 O(logN)
查找第一个元素出现的位置(元素允许重复)
public static int biSearch(int []array,int a){ int n=array.length; int low=0; int hi=n-1; int mid=0; while(low<hi){ mid=(low+hi)/2; if(array[mid]<a){ low=mid+1; }else{ hi=mid; } } if(array[low]!=a){ return -1; }else{ return low; } }
查询元素最后一次出现的位置
public static int biSearch(int []array,int a){ int n=array.length; int low=0; int hi=n-1; int mid=0; while(low<hi){ mid=(low+hi+1)/2; if(array[mid]<=a){ low=mid; }else{ hi=mid-1; } } if(array[low]!=a){ return -1; }else{ return hi; } }
有序表查找
1 2 3 4 5 6 7 8 |
|
一、折半查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
二、插值查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
三、斐波那契查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
四、三种查找方法的比较
平均性能:斐波那契>折半>插值,因为折半查找是加法与除法的运算,插值为四则运算,斐波那契加减运算。
分块查找
分块查找是将顺序查找与折半查找相结合的一种查找方法。
基本思想:
1. 首先将查找表分成若干块,在每一块中数据元素的存放是任意的,但块与块之间必须是有序的(假设这种排序是按关键字值递增的,也就是说在第一块中任意一个数 据元素的关键字都小于第二块中所有数据元素的关键字,第二块中任意一个数据元素的关键字都小于第三块中所有数据元素的关键字,依次类推);
2. 建立一个索引表,把每块中最大的关键字值按块的顺序存放在一个辅助数组中,这个索引表也按升序排列;
3. 查找时先用给定的关键字值在索引表中查找,确定满足条件的数据元素存放在哪个块中,查找方法既可以是折半方法,也可以是顺序查找。
4. 再到相应的块中顺序查找,便可以得到查找的结果。
案例分析:
一个查找表共有15个数据元素,现将它分成三块,每块5个数据元素,各块采用顺序方式独立存放在数组B1、B2、B3之中,辅助数组A的每个元素 包含两个域,关键字域中存放该块中关键字的上确界(本块中最大的关键字值),指针域存放该块的数组始址,存放状态如图6-5所示。
如果要查找关键字为66的数据元素,首先用66与A中各个元素的key比较,由于66<74,确定66在第三块中(如果存在),然后按A[2].link 到数组B3中采用顺序查找找到B3[2],查找成功。
/**
* 分块查找
*
* @param index 索引表,其中放的是各块的最大值
* @param st 顺序表,
* @param key 要查找的值
* @param m 顺序表中各块的长度相等,为m
* @return
*/
private int BlockSearch(int[ ] index, int[ ] st, int key, int m)
{
// 在序列st数组中,用分块查找方法查找关键字为key的记录
// 1.在index[ ] 中折半查找,确定要查找的key属于哪个块中
int i = partSearch(index, key);
if(i >= 0)
{
int j = i > 0 ? i * m : i;
int len = (i + 1) * m;
// 在确定的块中用顺序查找方法查找key
for(int k = j; k < len; k++)
{
if(key == st[k])
{
System.out.println("查询成功");
return k;
}
}
}
System.out.println("查找失败");
return -1;
}
public int partSearchs(int[] data, int tmpData)
{
int mid;
int low = 0;
int high = data.length - 1;
while(low <= high)
{
mid = (low + high) / 2; // 中间位置
if(tmpData == data[mid])
{
return mid;
}
else if(tmpData < data[mid])
{
high = mid - 1;
}
else
{
low = mid + 1;
return low;
}
}
return -1; // 没有查找到
}
优点:
①在表中插入或删除一个记录时,只要找到该记录所属的块,就在该块内进行插入和删除运算。
②因块内记录的存放是任意的,所以插入或删除比较容易,无须移动大量记录。
分块查找的主要代价是增加一个辅助数组的存储空间和将初始表分块排序的运算。
分块查找算法的效率介于顺序查找和二分查找之间。
若表中有10000个结点,则应把它分成100个块,每块中含100个结点。用顺序查找确定块,分块查找平均需要做100次比较,而顺序查找平均需做5000次比较,二分查找最多需14次比较。
分块查找(索引顺序查找)
它是顺序查的的一种改进方法。在此查找法中,除本身外,尚需建立一个"索引表"。索引表里有m个记录,每个索引n个元素,m*n等于数组的长度。其中包括两项内容:关键字(其值为该子表最大关键字)和指针(指示表第一个记录在表中的位置)
过程分两步进行:1.先用二分法查找索引表,确定要找的记录在哪一块; 2.然后再在相应块用顺序查找找到目标记录。分块查找又称为索引顺序查找。
使用了二分查找法和顺序查找法,所以其时间复杂度应为二者之合。即:O(log(2m))+O(n)=O(log(2m)+n)=O(log(2m)+length/m)
哈希查找
哈希查找是通过计算数据元素的存储地址进行查找的一种方法。O(1)的查找,即所谓的秒杀。哈希查找的本质是先将数据映射成它的哈希值。哈希查找的核心是构造一个哈希函数,它将原来直观、整洁的数据映射为看上去似乎是随机的一些整数。
哈希查找的操作步骤:
1) 用给定的哈希函数构造哈希表;
2) 根据选择的冲突处理方法解决地址冲突;
3) 在哈希表的基础上执行哈希查找。
建立哈希表操作步骤:
1) step1 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的存储空间还没有被占用,则将该元素存入;否则执行step2解决冲突。
2) step2 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若下一个存储地址仍被占用,则继续执行step2,直到找到能用的存储地址为止。
哈希查找步骤为:
1) Step1 对给定k值,计算哈希地址 Di=H(k);若HST为空,则查找失败;若HST=k,则查找成功;否则,执行step2(处理冲突)。
2) Step2 重复计算处理冲突的下一个存储地址 Dk=R(Dk-1),直到HST[Dk]为空,或HST[Dk]=k为止。若HST[Dk]=K,则查找成功,否则查找失败。
比如说:”5“是一个要保存的数,然后我丢给哈希函数,哈希函数给我返回一个”2",那么此时的”5“和“2”就建立一种对应关系,这种关系就是所谓的“哈希关系”,在实际应用中也就形成了”2“是key,”5“是value。
那么有的朋友就会问如何做哈希,首先做哈希必须要遵守两点原则:
①: key尽可能的分散,也就是我丢一个“6”和“5”给你,你都返回一个“2”,那么这样的哈希函数不尽完美。
②:哈希函数尽可能的简单,也就是说丢一个“6”给你,你哈希函数要搞1小时才能给我,这样也是不好的。
其实常用的做哈希的手法有“五种”:
第一种:”直接定址法“。
很容易理解,key=Value+C;这个“C"是常量。Value+C其实就是一个简单的哈希函数。
第二种:“除法取余法”。
很容易理解, key=value%C;解释同上。
第三种:“数字分析法”。
这种蛮有意思,比如有一组value1=112233,value2=112633,value3=119033,
针对这样的数我们分析数中间两个数比较波动,其他数不变。那么我们取key的值就可以是
key1=22,key2=26,key3=90。
第四种:“平方取中法”。此处忽略,见名识意。
第五种:“折叠法”。
这种蛮有意思,比如value=135790,要求key是2位数的散列值。那么我们将value变为13+57+90=160,然后去掉高位“1”,此时key=60,哈哈,这就是他们的哈希关系,这样做的目的就是key与每一位value都相关,来做到“散列地址”尽可能分散的目地。
影响哈希查找效率的一个重要因素是哈希函数本身。当两个不同的数据元素的哈希值相同时,就会发生冲突。为减少发生冲突的可能性,哈希函数应该将数据尽可能分散地映射到哈希表的每一个表项中。
解决冲突的方法有以下两种:
(1) 开放地址法
如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。当程序查找哈希表时,如果没有在第一个对应的哈希表项中找到符合查找要求的数据元素,程序就会继续往后查找,直到找到一个符合查找要求的数据元素,或者遇到一个空的表项。
(2) 链地址法
将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。
实现哈希函数为“除法取余法”,解决冲突为“开放地址线性探测法”,代码如下:
[java] view plain copy
- public class HashSearch {
- public static void main(String[] args) {
- //“除法取余法”
- int hashLength = 13;
- int [] array = { 13, 29, 27, 28, 26, 30, 38 };
- //哈希表长度
- int[] hash = new int[hashLength];
- //创建hash
- for (int i = 0; i < array.length; i++)
- {
- insertHash(hash, hashLength, array[i]);
- }
- int result = searchHash(hash,hashLength, 29);
- if (result != -1)
- System.out.println("已经在数组中找到,索引位置为:" + result);
- else
- System.out.println("没有此原始");
- }
- /****
- * Hash表检索数据
- *
- * @param hash
- * @param hashLength
- * @param key
- * @return
- */
- public static int searchHash(int[] hash, int hashLength, int key) {
- // 哈希函数
- int hashAddress = key % hashLength;
- // 指定hashAdrress对应值存在但不是关键值,则用开放寻址法解决
- while (hash[hashAddress] != 0 && hash[hashAddress] != key) {
- hashAddress = (++hashAddress) % hashLength;
- }
- // 查找到了开放单元,表示查找失败
- if (hash[hashAddress] == 0)
- return -1;
- return hashAddress;
- }
- /***
- * 数据插入Hash表
- *
- * @param hash 哈希表
- * @param hashLength
- * @param data
- */
- public static void insertHash(int[] hash, int hashLength, int data) {
- // 哈希函数
- int hashAddress = data % hashLength;
- // 如果key存在,则说明已经被别人占用,此时必须解决冲突
- while (hash[hashAddress] != 0) {
- // 用开放寻址法找到
- hashAddress = (++hashAddress) % hashLength;
- }
- // 将data存入字典中
- hash[hashAddress] = data;
- }
- }
- 运行结果:
- 已经在数组中找到,索引位置为:3
索引查找是在索引表和主表(即线性表的索引存储结构)上进行的查找。
索引查找的过程是:
1) 首先根据给定的索引值K1,在索引表上查找出索引值等于KI的索引项,以确定对应予表在主表中的开始位置和长度,
2) 然后再根据给定的关键字K2,茬对应的子表中查找出关键字等于K2的元素(结点)。对索引表或子表进行查找时,若表是顺序存储的有序表,则既可进行顺序查找,也可进行二分查找,否则只能进行顺序查找。
一提到“索引”,估计大家第一反应就是“数据库索引”,对的,其实主键建立“索引”,就是方便我们在海量数据中查找。
实现索引查找时常使用的三个术语:
1) 主表:这个很简单,要查找的对象。
2) 索引项:一般我们会用函数将一个主表划分成几个子表,每个子表建立一个索引,这个索引叫做索引项。
3) 索引表:索引项的集合也就是索引表。
一般“索引项”包含三种内容:index,start,length
第一: index,也就是索引指向主表的关键字。
第二:start,也就是index在主表中的位置。
第三:length, 也就是子表的区间长度。
代码实现:
[java] view plain copy
- public class IndexSearch {
- // 主表
- static int[] students = { 101, 102, 103, 104, 105, 0, 0, 0, 0, 0, 201, 202,
- 203, 204, 0, 0, 0, 0, 0, 0, 301, 302, 303, 0, 0, 0, 0, 0, 0, 0 };
- // 索引表
- static IndexItem[] indexItem = { new IndexItem(1, 0, 5),
- new IndexItem(2, 10, 4), new IndexItem(3, 20, 3), };
- // 查找数据
- public static int indexSearch(int key) {
- IndexItem item = null;
- // 建立索引规则
- int index = key / 100;
- // 首先去索引找
- for (int i = 0; i < indexItem.length; i++) {
- if (indexItem[i].index == index) {
- item = new IndexItem(index, indexItem[i].start,
- indexItem[i].length);
- break;
- }
- }
- // 如果item为null,则说明在索引中查找失败
- if (item == null)
- return -1;
- for (int i = item.start; i < item.start + item.length; i++) {
- if (students[i] == key) {
- return i;
- }
- }
- return -1;
- }
- // / 插入数据
- public static int insert(int key) {
- IndexItem item = null;
- // 建立索引规则
- int index = key / 100;
- int i = 0;
- for (i = 0; i < indexItem.length; i++) {
- // 获取到了索引
- if (indexItem[i].index == index) {
- item = new IndexItem(index, indexItem[i].start,
- indexItem[i].length);
- break;
- }
- }
- if (item == null)
- return -1;
- // 更新主表
- students[item.start + item.length] = key;
- // 更新索引表
- indexItem[i].length++;
- return 1;
- }
- public static void main(String[] args) {
- int value = 205;
- // 将205插入集合中,过索引
- int index = insert(value);
- insert(308);
- // 如果插入成功,获取205元素所在的位置
- if (index == 1) {
- System.out.println("\n插入后数据:" + Arrays.toString(students));
- System.out.println("\n数据元素:205在数组中的位置为 " + indexSearch(205) + "位");
- }
- }
- }
- // 索引项实体
- class IndexItem {
- // 对应主表的值
- public int index;
- // 主表记录区间段的开始位置
- public int start;
- // 主表记录区间段的长度
- public int length;
- public IndexItem() {
- }
- public IndexItem(int index, int start, int length) {
- this.index = index;
- this.start = start;
- this.length = length;
- }
- }
- 运行结果:
- 原数据为:[101, 102, 103, 104, 105, 0, 0, 0, 0, 0, 201, 202, 203, 204, 0, 0, 0, 0, 0, 0, 301, 302, 303, 0, 0, 0, 0, 0, 0, 0]
- 插入后数据:[101, 102, 103, 104, 105, 0, 0, 0, 0, 0, 201, 202, 203, 204, 205, 0, 0, 0, 0, 0, 301, 302, 303, 308, 0, 0, 0, 0, 0, 0]
- 数据元素:205在数组中的位置为 14位
其时间复杂度为O(1);
来源:oschina
链接:https://my.oschina.net/u/2822116/blog/783799