查找功能是数据处理的一个基本功能。数据查找并不复杂,但是如何实现数据又快又好地查找呢?
顺序查找
顺序查找是在一个已知无(或有序)序队列中找出与给定关键字相同的数的具体位置。
【算法原理】
让关键字与队列中的数从最后一个开始逐个比较,直到找出与给定关键字相同的数为止。
【算法描述】
public static int ordersearch(int[] arr,int des){ for(int i=0;i<=arr.length-1;i++){ if(des==arr[i]) return i; } return -1; }
二分查找
二分查找法是对一组有序的数字中进行查找,传递相应的数据,进行比较查找到与原数据相同的数据,查找到了返回对应的数组下标,没有找到返回-1;
【算法描述】
1. 递归方式
public static int binSearch(int srcArray[], int start, int end, int key) { int mid = (end - start) / 2 + start; if (srcArray[mid] == key) { return mid; } if (start >= end) { return -1; } else if (key > srcArray[mid]) { return binSearch(srcArray, mid + 1, end, key); } else if (key < srcArray[mid]) { return binSearch(srcArray, start, mid - 1, key); } return -1; }
2. 非递归方式
public static int binSearch(int srcArray[], int key) { int mid; int start = 0; int end = srcArray.length - 1; while (start <= end) { mid = (end - start) / 2 + start; if (key < srcArray[mid]) { end = mid - 1; } else if (key > srcArray[mid]) { start = mid + 1; } else { return mid; } } return -1; }
插值查找
插值查找比较灵活,并不是简单的从中间进行的,它是根据我们需要查询的值的渐进进行搜索的。插值查找的不同点在于每一次并不是从中间切分,而是根据离所求值的距离进行搜索的。它是二分查找的变形。
【算法描述】
public static boolean search(int[] arr,int key,int left,int right){ while(left<right){ int middle=left+(right-left)*((key-arr[left])/(arr[right]-arr[left])); if(arr[middle]==key){ return true; } if(key<arr[middle]){ right=middle-1; }else{ left=middle+1; } } return false; }
二叉树查找
二叉查找树(Binary Search Tree),又被称为二叉搜索树。它是特殊的二叉树:对于二叉树,假设x为二叉树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。那么,这棵树就是二叉查找树。
【如图】
【二叉查找树特点】
1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 任意节点的左、右子树也分别为二叉查找树。
4. 没有键值相等的节点(no duplicate nodes)。【二叉查找树节点定义】
public class BSTNode<T extends Comparable<T>> { T key; // 关键字(键值) BSTNode<T> left; // 左孩子 BSTNode<T> right; // 右孩子 BSTNode<T> parent; // 父结点 public BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right) { this.key = key; this.parent = parent; this.left = left; this.right = right; } } public class BSTree<T extends Comparable<T>> { private BSTNode<T> mRoot; // 根结点 ...... } BSTree是二叉树,它保护了二叉树的根节点mRoot;mRoot是BSTNode类型,而BSTNode是二叉查找树的节点,它是BSTree的内部类。 BSTNode包含二叉查找树的几个基本信息: (01) key -- 它是关键字,是用来对二叉查找树的节点进行排序的。 (02) left -- 它指向当前节点的左孩子。 (03) right -- 它指向当前节点的右孩子。 (04) parent -- 它指向当前节点的父结点。
【二叉树遍历】
若二叉树非空,则执行以下操作 1. 前序遍历 (01) 访问根结点 (02) 先序遍历左子树 (03) 先序遍历右子树 2. 中序遍历 (01) 中序遍历左子树 (02) 访问根结点 (03) 中序遍历右子树 3. 后序遍历 (01) 后序遍历左子树 (02) 后序遍历右子树 (03) 访问根结点
【如图所示】
对于上面的二叉树而言: (01) 前序遍历结果: 3 1 2 5 4 6 (02) 中序遍历结果: 1 2 3 4 5 6 (03) 后序遍历结果: 2 1 4 6 5 3
【算法描述】
//查找"二叉树x"中键值为key的节点 private BSTNode<T> search(BSTNode<T> x, T key) { if (x==null) return x; int cmp = key.compareTo(x.key); if (cmp < 0) return search(x.left, key); else if (cmp > 0) return search(x.right, key); else return x; } public BSTNode<T> search(T key) { return search(mRoot, key); }
哈希查找
哈希查找是通过计算数据元素的存储地址进行查找的一种方法。哈希查找的本质是先将数据映射成它的哈希值。哈希查找的核心是构造一个哈希函数,它将原来直观、整洁的数据映射为看上去似乎是随机的一些整数。
哈希查找的操作步骤: 1) 用给定的哈希函数构造哈希表 2) 根据选择的冲突处理方法解决地址冲突 3) 在哈希表的基础上执行哈希查找
建立哈希表的步骤: 1) step1取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的存储空间还没有被占用,则将该元素存入;否则执行step2解决冲突。 2) step2根据选择的冲突处理方法,计算关键字key的下一个存储地址。若下一个存储地址仍被占用,则继续执行step2,直到找到能用的存储地址为止。
常用做哈希的方法: 第一种:"直接定址法" 很容易理解,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)链地址法 将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。
【算法描述】
实现哈希函数为"除法取余法",解决冲突为"开放地址线性探测法"
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; } 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; } 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("没有此原始"); }