查找
目录
查找算法的分类:
1.静态查找和动态查找:动态和静态都是相对于表而言的。动态表中有删除和插入操作。
2.无序查找和有序查找:被查找的数列是否有序。
1.顺序查找
基本思想:顺序查找就是按照顺序,从数据结构的一端,顺序扫描,直到查找成功。
平均查找长度ASL:(n+1)/2
时间复杂度:O(n)
代码:
/**
* 顺序查找
* 思想:从前到后遍历,直到找到要找的元素,返回元素下标,
* 优点:二叉查找对于等概率的查找的性能是最优的。
* 时间复杂度:O(n)
* @param list
* @param x
*/
public static void Sort_Search(int list[],int x){
for(int i=0;i<list.length;i++){
if(list[i] == x){
System.out.println("元素处在第"+i+"位");
}
}
}
2.二分查找
前提:元素必须是有序的。而且是顺序表存储结构
基本思想:将目标元素与位于(1+n)/2位置的元素相比较,如果小于就在小于(1+n)/2的子表中继续上述操作,如果大于,就在大于中间值的子表中查找。
时间复杂度:O(logn)
代码:
/**
* 二分查找
* 前提:队列必须是有序的
* 思想:确定中间位置,然后与待查询值相比,
* 如果小于,则与左边数组中间值比较,
* 如果等于,则返回这个值,如果大于,则与右边数组中间值相比较
*时间复杂度:O(logn)
* @param list
* @param x
*/
public static void Binary_Search(int list[],int x){
Quick_Sort(list,0,list.length-1);
show_list(list);
int ad = Recursion(list,x,0,list.length-1);
System.out.println("元素处在第"+ad+"位");
}
/**
* 二分查找的递归
*/
public static int Recursion(int list[],int x,int left,int right){
int mid = (left+right)/2;
if(list[mid] == x )return mid;
else if(list[mid] > x) return Recursion(list,x,left,mid-1);
else return Recursion(list,x,mid+1,right);
}
3.插值查找
基本思想:差值查找法是基于二分法进行改进的算法,与二分法不同的是,差值法选择的不是中间值。而是根据所要查找的数进行自适应选择。mid=low+(key-a[low])/(a[high]-a[low])*(high-low)。
特点:当有序表的元素分布比较均匀时,插值查找比折半查找要好得多。
如果分布不均匀,则会花费更多时间。
时间复杂度:O(log(logn))
/**
* 插值查找
* 思想:基于二分查找
* 定义mid为mid = (high-low)*(key-arr[low])/(arr[high]-arr[low])
* 中间值的定义,与数组的极大值和极小值有关。
* 时间复杂度:平均O(loglogn),最坏O(logn)
* 优点:对于表长较大,而关键字分布又比较**均匀**的查找表来说,插值查找的平均性能比折半查找要好。
*/
public static void Interpolation_Search(int list[],int x){
Quick_Sort(list,0,list.length-1);
show_list(list);
int ad = Recursion(list,x,0,list.length-1);
System.out.println("元素处在第"+ad+"位");
}
/**
* 插值查找的递归
*/
public static int Interpolation_Recursion(int list[],int x,int left,int right){
int mid = 0;
mid = (right-left)*(x-list[left])/(list[right]-list[left]);
if(list[mid] == x )return mid;
else if(list[mid] > x) return Interpolation_Recursion(list,x,left,mid-1);
else return Interpolation_Recursion(list,x,mid+1,right);
}
4.斐波拉契查找(黄金分割查找){1,1,2,3,5,8,13,21,34}
基本思想:斐波拉契查找是相对于二分查找和插值查找来区分的。具体思想差不多,主要区别在于插值的选取。
假设有待查找数组array[n]和斐波那契数组F[k],并且n满足n>=F[k]-1&&n < F[k+1]-1,则它的第一个拆分点middle=F[k]-1。意思就是所,取下标为斐波拉契数的数值。
特点:此查找的key只涉及加法运算,相比于二分查找用到了除法,可能斐波拉契查找的速度要快一些。
时间复杂度:O(logn)
二分查找,插值查找,斐波拉契查找,三种查找的区别
当数据分布较为均匀时,插值查找的时间复杂度会大大降低。但每次运算获得插值时,运算比较比较耗费时间。
二分查找与斐波拉契查找哪个更优,暂时没有找到答案。但当二分查找中的除法使用>>1来代替,可能效率将更高一点。
参考:斐波拉契和二分哪个更快?
5.分块查找(索引查找)
要求:分块查找的要求,就是要,数据基本有序或者,分块有序。
也就是要保证,每一块中最小值要大于前一块的最大值。
分块查找的思想就是,将数据分成多个块,先判断数据在哪个块范围,再在块内找。
分块查找的实现建立一个索引结构,保存每个块中的首地址和最小数据。如果每块不是有序的,需要先将块排序。然后再将value与每块的最小值比较,判断在那一块,然后在块中查找对应的value。
代码实现引用用c实现块查找
#include<stdio.h>
#include<stdlib.h>
//定义块结构
struct index
{
int key; //块的key值
int start; //块的起始值
}newIndex[3];//此处同时创建三个块索引
//定义查找函数
int search(int key, int a[]) {
int i, startValue;
i = 0;
while (i<3 && key>newIndex[i].key)
{
//确定在哪个块中,遍历每个快,确定key在哪个块中
i++;
}
if (i >= 3) {
//大于分得的块数,则返回0
return -1;
}
startValue = newIndex[i].start; //startValue等于块范围的起始值
while (startValue <= startValue + 5 && a[startValue] != key)
{
startValue++;
}
if (startValue > startValue + 5)
{
//如果大于块范围的结束值,则说明没有要查找的数
return -1;
}
return startValue;
}
//定义比较函数:用于给块排序
int cmp(const void *a, const void* b)
{
return (*(struct index*)a).key > (*(struct index*)b).key ? 1 : -1;
}
int main() {
int i, j = -1, k, key;
int a[] = { 33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53 };
//确认模块的起始值和最大值
for (i = 0; i < 3; i++) {
newIndex[i].start = j + 1; //start指向首地址
j += 6;
for (int k = newIndex[i].start; k <= j; k++) {
if (newIndex[i].key < a[k]) {
newIndex[i].key = a[k]; //key保存最小值
}
}
}
//对结构体按照key值进行排序
//qsort函数:快速排序函数
//函数有4个参数值:第一个为待排序数组的首地址,
// 第二个为数组的长度,
// 第三个为数组元素所占的字节,
// 第四个为自定义比较函数。
qsort(newIndex, 3, sizeof(newIndex[0]), cmp);
//输出要查询的数,并调用函数进行查找
printf("请输入你想要查找的数:\n");
scanf_s("%d", &key);
k = search(key, a);
//输出查找的结果
if (k > 0) {
printf("查找成功!位置是:%d\n", k + 1);
}
else {
printf("查找失败!您要找的数不在数组中。\n");
}
system("pause");
return 0;
}
6.哈希查找
**思想**:hash查找是一种以空间换时间的算法,比如原来一个长度为100的数组,对其查找,只需要遍历匹配即可,从空间复杂度上来说,加入数组存储的是byte型数据,name该数组占用100byte空间。现在我们采用Hash算法,用来约束键与存储位置的关系,那么就需要固定一个长度的hash表,如果仍为100byte,那么此时我们需要的总空间为200byte,而且用于记录规则的表的大小会根据规则的变化改变,大小可能不确定。
**流程**:1.用哈希函数,构造哈希表。
2.根据选择冲突处理方法解决地址冲突,常见的解决冲突的方法:拉链法和线性探测法。
代码实现:
import java.io.IOException;
import java.util.Scanner;
public class HashSearch {
// 初始化哈希表
static int hashLength = 7;
static int[] hashTable = new int[hashLength];
// 原始数据
static int[] list = new int[]{13, 29, 27, 28, 26, 30, 38};
public static void main(String[] args) throws IOException {
System.out.println("*******哈希查找*******");
// 创建哈希表
for (int i = 0; i < list.length; i++) {
insert(hashTable, list[i]);
}
System.out.println("展示哈希表中的数据:" + display(hashTable));
while (true) {
// 哈希表查找
System.out.print("请输入要查找的数据:");
int data = new Scanner(System.in).nextInt();
int result = search(hashTable, data);
if (result == -1) {
System.out.println("对不起,没有找到!");
} else {
System.out.println("数据的位置是:" + result);
}
}
}
/**
* 方法:哈希表插入
*/
public static void insert(int[] hashTable, int data) {
// 哈希函数,除留余数法
int hashAddress = hash(hashTable, data);
// 如果不为0,则地址已被占用,说明发生冲突
while (hashTable[hashAddress] != 0) {
// 利用 开放定址法 解决冲突
hashAddress = (++hashAddress) % hashTable.length;
}
// 将待插入值存入字典中
hashTable[hashAddress] = data;
}
/**
* 方法:哈希表查找
*/
public static int search(int[] hashTable, int data) {
// 哈希函数,除留余数法
int hashAddress = hash(hashTable, data);
while (hashTable[hashAddress] != data) {
// 利用 开放定址法 解决冲突
hashAddress = (++hashAddress) % hashTable.length;
// 查找到开放单元 或者 循环回到原点,表示查找失败
if (hashTable[hashAddress] == 0 || hashAddress == hash(hashTable, data)) {
return -1;
}
}
// 查找成功,返回下标
return hashAddress;
}
/**
* 方法:构建哈希函数(除留余数法)
*
* @param hashTable
* @param data
* @return
*/
public static int hash(int[] hashTable, int data) {
return data % hashTable.length;
}
/**
* 方法:展示哈希表
*/
public static String display(int[] hashTable) {
StringBuffer stringBuffer = new StringBuffer();
for (int i : hashTable) {
stringBuffer = stringBuffer.append(i + " ");
}
return String.valueOf(stringBuffer);
}
}
哈希冲突解决方法
待总结!!!
7.树表查找
基于各种树的数表查找
待总结!!!