书接上回:算法系列二:插值查找、斐波那契查找
二叉排序树有前、中、后三种遍历方式,我们这里只说中序遍历。上个二叉树的例子:
中序遍历的方式为:左中右(左节点,根节点,右节点)。该二叉树中序遍历的结果:
1,3,4,6,7,8,10,13,14
二叉查找树查找步骤:
①先建立一颗二叉排序树;
② 再进行查找。
class BinaryTree{
int value;
BinaryTree left;
BinaryTree right;
public BinaryTree(int value){
this.value = value;
}
}
int[] array = {35,76,6,22,16,49,49,98,46,9,40};
BinaryTree root = new BinaryTree(array[0]);
for(int i = 1; i < array.length; i++){
createBST(root, array[i]);
}
具体创建树的过程,就是一个不断与根节点比较,然后添加到左侧、右侧或不添加(相等时)的过程。
public static void createBST(BinaryTree root, int element){
BinaryTree newNode = new BinaryTree(element);
if(element > root.value){
if(root.right == null)
root.right = newNode;
else
createBST(root.right, element);
}else if(element < root.value){
if(root.left == null)
root.left = newNode;
else
createBST(root.left, element);
}else{
System.out.println("该节点" + element + "已存在");
return;
}
}
查找元素是否在树中的过程,就是一个二分查找的过程,不过查找的对象从左右子序列转换成了左右子树而已。
public static void searchBST(BinaryTree root, int target, BinaryTree p){
if(root == null){
System.out.println("查找"+target+"失败");
}else if(root.value == target){
System.out.println("查找"+target+"成功");
}else if(root.value >= target){
searchBST(root.left, target, root);
}else{
searchBST(root.right, target, root);
}
}
public class BinarySortTree {
public static void main(String[] args) {
int[] array = {35,76,6,22,16,49,49,98,46,9,40};
BinaryTree root = new BinaryTree(array[0]);
for(int i = 1; i < array.length; i++){
createBST(root, array[i]);
}
System.out.println("中序遍历结果:");
midOrderPrint(root);
System.out.println();
searchBST(root, 22, null);
searchBST(root, 100, null);
}
/*创建二叉排序树*/
public static void createBST(BinaryTree root, int element){
BinaryTree newNode = new BinaryTree(element);
if(element > root.value){
if(root.right == null)
root.right = newNode;
else
createBST(root.right, element);
}else if(element < root.value){
if(root.left == null)
root.left = newNode;
else
createBST(root.left, element);
}else{
System.out.println("该节点" + element + "已存在");
return;
}
}
/*二叉树中查找元素*/
public static void searchBST(BinaryTree root, int target, BinaryTree p){
if(root == null){
System.out.println("查找"+target+"失败");
}else if(root.value == target){
System.out.println("查找"+target+"成功");
}else if(root.value >= target){
searchBST(root.left, target, root);
}else{
searchBST(root.right, target, root);
}
}
/*二叉树的中序遍历*/
public static void midOrderPrint(BinaryTree rt){
if(rt != null){
midOrderPrint(rt.left);
System.out.print(rt.value + " ");
midOrderPrint(rt.right);
}
}
}
输出结果:
该节点49已存在
中序遍历结果:
6 9 16 22 35 40 46 49 76 98
查找22成功
查找100失败
哈希查找涉及到的两个东西,哈希表和哈希函数。
哈希表,是根据关键值而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表(哈希表)
哈希函数指将哈希表中元素的关键键值映射为元素存储位置的函数。
我们可以知道,哈希查找比较的不是待查找数值,而是一个关键码。
既然如此,我们就需要先构造一个哈希表。那么常见的几种构造方法有:
哈希地址:f(key) = a*key+b (a、b为常数)。
这种方法的优点是:简单、均匀、不会产生冲突。但是需要事先知道 key 的分布情况,适合查找表较小并且连续的情况。
假设关键字是R进制数(如十进制)。并且哈希表中可能出现的关键字都是事先知道的,则可选取关键字的若干数位组成哈希地址。选取的原则是使得到的哈希地址尽量避免冲突,即所选数位上的数字尽可能是随机的。
举个例子:比如11位手机号码“136xxxx5889”,其中前三位是接入号,一般对应不同运营公司的子品牌,中间四位表示归属地,最后四位才是用户号,此时就可以用后4位来作为哈希地址。
取key平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,仅取其中的几位为地址不一定合适。而一个数平方后的中间几位数和数的每一位都相关, 由此得到的哈希地址随机性更大。如key是1234,那么它的平方就是1522756,再抽取中间的3位就是227作为 f(key) 。
折叠法是将 key 从左到右分割成位数相等的几个部分(最后一部分位数不够可以短些),然后将这几部分叠加求和,并按哈希表的表长,取后几位作为 f(key) 。
比如key是9876543210,哈希表的表长为3位,我们将 key 分为4组,987 | 654 | 321 | 0 ,然后将它们叠加求和 987+654+321+0=1962,再取后3位即得到哈希位置是:962 。
取关键字被某个不大于哈希表表长 m 的数 p 除后所得的余数为哈希地址。即 f(key) = key % p (p ≤ m)。这种方法是最常用的哈希函数构造方法。
哈希地址:random(key) ,这里random是随机函数,当 key 的长度不等时,采用这种方法比较合适。
我们在使用以上方法计算key对应的哈希地址时,偶尔会遇到两个key不相等,但是计算出来的哈希地址却相同的情况,该情况就被称为“冲突”。在构造哈希表时,有常用几种方式解决冲突:
该方法指的是两个key在计算出相同的哈希地址时,后者继续在哈希表中向后寻找空位置,存放改key的方法。举个例子:假如原始的key中有8、15两个元素,哈希表中的长度为7,当使用key % length求余时,两个key会计算出相同的哈希位置。假设哈希表中的1位置已经存放了8,那么15就要从1位置往后寻找空位,假如2位置是空的,就可以把15存放到2位置;假如2位置不空,就要往3位置寻找,以此类推。
三种方式:
a. 线性探测再散列:
顺序查看下一个单元,直到找出一个空单元填入或查遍全表。
b. 二次(平方)探测再散列:
在表的左右进行跳跃式探测,直到找出一个空单元或查遍全表.
c. 伪随机探测再散列:
- 建立一个伪随机数发生器,并给一个随机数作为起点
- di=伪随机数序列。具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。
例如,已知哈希表长度m=11,哈希函数为:H(key)= key % 11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。
如果用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,还是冲突,继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时不再冲突,将69填入5号单元。
如果用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,将69填入2号单元。
如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,………,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址为H2=(3 + 5)% 11 = 8,此时不再冲突,将69填入8号单元
简单来说,就是对于相同的哈希值,使用链表进行连接(如HashMap)
即创建一个List,存储相同位置上不同值的key
优点:
①处理冲突简单,无堆积现象。即非同义词决不会发生冲突,因此平均查找长度较短;
②适合总数经常变化的情况。(因为拉链法中各链表上的结点空间是动态申请的)
③占空间小。装填因子可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计
④删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:
①查询时效率较低。(存储是动态的,查询时跳转需要更多的时间)
②在key-value可以预知,以及没有后续增改操作时候,开放定址法性能优于链地址法。
③不容易序列化
提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值。
优缺点:不易产生聚集;增加计算时间。
将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
/*用除留余数法计算要插入元素的地址*/
public static int hash(int[] hashTable, int data) {
return data % hashTable.length;
}
/*将元素插入到哈希表中*/
public static void insertHashTable(int[] hashTable, int target) {
int hashAddress = hash(hashTable, target);
/*如果不为0,则说明发生冲突*/
while (hashTable[hashAddress] != 0) {
/*利用开放定址法解决冲突,即向后寻找新地址*/
hashAddress = (++hashAddress) % hashTable.length;
}
/*将元素插入到哈希表中*/
hashTable[hashAddress] = target;
}
public static int searchHashTable(int[] hashTable, int target) {
int hashAddress = hash(hashTable, target);
while (hashTable[hashAddress] != target) {
/*寻找原始地址后面的位置*/
hashAddress = (++hashAddress) % hashTable.length;
/*查找到开放单元(未存放元素的位置)或 循环回到原点,表示查找失败*/
if (hashTable[hashAddress] == 0 || hashAddress == hash(hashTable, target)) {
return -1;
}
}
return hashAddress;
}
public class HashSearch {
/*待查找序列*/
static int[] array = {13, 29, 27, 28, 26, 30, 38};
/* 初始化哈希表长度,此处哈希表容量设置的和array长度一样。
* 其实正常情况下,哈希表长度应该要长于array长度,因为使用
* 开放地址法时,可能会多使用一些空位置
*/
static int hashLength = 7;
static int[] hashTable = new int[hashLength];
public static void main(String[] args) {
/*将元素插入到哈希表中*/
for (int i = 0; i < array.length; i++) {
insertHashTable(hashTable, array[i]);
}
System.out.println("哈希表中的数据:");
printHashTable(hashTable);
int data = 28;
System.out.println("\n要查找的数据"+data);
int result = searchHashTable(hashTable, data);
if (result == -1) {
System.out.println("对不起,没有找到!");
} else {
System.out.println("在哈希表中的位置是:" + result);
}
}
/*将元素插入到哈希表中*/
public static void insertHashTable(int[] hashTable, int target) {
int hashAddress = hash(hashTable, target);
/*如果不为0,则说明发生冲突*/
while (hashTable[hashAddress] != 0) {
/*利用开放定址法解决冲突,即向后寻找新地址*/
hashAddress = (++hashAddress) % hashTable.length;
}
/*将元素插入到哈希表中*/
hashTable[hashAddress] = target;
}
public static int searchHashTable(int[] hashTable, int target) {
int hashAddress = hash(hashTable, target);
while (hashTable[hashAddress] != target) {
/*寻找原始地址后面的位置*/
hashAddress = (++hashAddress) % hashTable.length;
/*查找到开放单元(未存放元素的位置)或 循环回到原点,表示查找失败*/
if (hashTable[hashAddress] == 0 || hashAddress == hash(hashTable, target)) {
return -1;
}
}
return hashAddress;
}
/*用除留余数法计算要插入元素的地址*/
public static int hash(int[] hashTable, int data) {
return data % hashTable.length;
}
public static void printHashTable(int[] hashTable) {
for(int i=0;i<hashTable.length;i++)
System.out.print(hashTable[i]+" ");
}
}
结果:
哈希表中的数据:
27 29 28 30 38 26 13
要查找的数据28
在哈希表中的位置是:2
活动地址:CSDN21天学习挑战赛