基本查找算法简介
一、查找的基本概念
查找,也可称检索,是在大量的数据元素中找到某个特定的数据元素而进行的工作。查找是一种操作。
二、顺序查找
针对无序序列的一种最简单的查找方式。
时间复杂度为O(n)。
三、折半查找
针对已排序序列的一种查找方式。并且只适用于顺序存储结构的序列。要求序列中的元素基本不变,在需要做删除和插入操作的时候,会影响检索效率。时间复杂度为O(logN)。
四、二叉排序树(Binary Sort Tree)。
1、概念:
它或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于左子树所在树的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于右子树所在树的根结点的值;
(3)左、右子树也分别为二叉排序树;
2、查找:
时间复杂度与树的深度的有关。
步骤:若根结点的关键字值等于查找的关键字,成功。
否则:若小于根结点的关键字值,递归查左子树。
若大于根结点的关键字值,递归查右子树。
若子树为空,查找不成功。
3、插入:
首先执行查找算法,找出被插结点的父亲结点。
判断被插结点是其父亲结点的左儿子还是右儿子。将被插结点作为叶子结点插入。
若二叉树为空。则首先单独生成根结点。
注意:新插入的结点总是叶子结点,所以算法复杂度是O(h)。
4、删除:
如果删除的结点没有孩子,则删除后算法结束;
如果删除的结点只有一个孩子,则删除后该孩子取代被删除结点的位置;
如果删除的结点有两个孩子,则选择该结点的后继结点(该结点右孩子为根的树中的左子树中的值最小的点)作为新的根,同时在该后继结点开始,执行前两种删除算法,删除算法结束。
五、散列(hash)表(哈希查找)
关键字:哈希函数、装填因子、冲突、同义词;
关键字和和存储的地址建立一个对应的关系:
(一) 哈希函数Address =Hash(key) 可以用以下的规则定义:
1. 直接定址法:
比如有一组0-100的数据,用来表示人的年龄那么,采用直接定址的方法构成的哈希表为:
0 | 1 | 2 | 3 | 4 | 5 |
0岁 | 1岁 | 2岁 | 3岁 | 4岁 | 5岁 |
.....这样的一种定址方式,简单方便,适用于元数据能够用数字表述或者原数据具有鲜明顺序关系的情形。
2. 数字分析法:
有这样一组数据,用于表述一些人的出生日期
年 | 月 | 日 |
75 | 10 | 1 |
75 | 12 | 10 |
75 | 02 | 14 |
分析一下,年和月的第一位数字基本相同,造成冲突的几率非常大,而后面三位差别比较大,所以采用后三位
3. 平方取中法
取关键字平方后的中间几位作为哈希地址
4. 折叠法:
将关键字分割成位数相同的几部分,最后一部分位数可以不相同,然后去这几部分的叠加和(取出进位)作为哈希地址,比如有这样的数据20-1445-4547-3,可以
5473
+ 4454
+ 201
= 10128
取出进位1,取0128为哈希地址
5. 取余法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。H(key)=key MOD p (p<=m)
6. 随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。
(二) 解决冲突的方法有以下两种:
(1)开放地址法
如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。当程序查找哈希表时,如果没有在第一个对应的哈希表项中找到符合查找要求的数据元素,程序就会继续往后查找,直到找到一个符合查找要求的数据元素,或者遇到一个空的表项。
(2)链地址法
将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。
算法实现
一、顺序查找
说明:顺序查找适合于存储结构为顺序存储或链接存储的线性表
/**
* 在s[0]-s[n-1]中顺序查找关键字为Key的记录 ,查找成功时返回该记录的下标序号;失败时返回-1
*/
int SequelSearch(elemtype s[], keytype Key, int n)
{
int i;
i = 0;
while(i < n && s[i].Key != Key)
i++;
if(s[i].Key == Key)
return i;
else
return -1;
}
二、二分查找
前提:元素必须是有序的,如果是无序的则要先进行排序操作
1.递归实现
/**
* 在下届为low,上界为high的数组a中折半查找数据元素x
*/
int binarySearch(elemtype a[], elemtype x, int low, int high)
{
int mid;
if(low > high)
return -1;
mid = (low + high)/2;
if(x == a[mid])
return mid;
if(x < a[mid])
return binarySearch(a, x, low, mid-1);
else
return binarySearch(a, x, mid+1, high);
}
2.非递归实现
int binarySearch(elemtype a[], keytype key, int n)
{
int low, high, mid;
low = 0;
high = n - 1;
while(low <= high){
mid= (low + high) / 2;
if(a[mid].key == key)
return mid;
else if (a[mid].key < key)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
三、分块查找
typedef intkeytype;
typedef struct {
keytype Key;
}elemtype;
typedef struct{
keytype Key;
int Link;
}indextype;
/**
* 分块查找关键字为Key的记录。索引表为ls[0]-ls[m-1],顺序表为s,块长为l
*/
int IndexSequelSearch(indextype ls[], elemtype s[], int m, int l, keytype Key)
{
int i,j;
/*在索引表中顺序查找*/
i = 0;
while(i<m && Key>ls[i].Key) i++;
if(i>=m)
return -1;
else{
/*在顺序表中顺序查找*/
j = ls[i].Link;
while(Key!=s[j].Key && j-ls[i].Link<l)
j++;
if(Key==s[j].Key)
return j;
else
return -1;
}
}
四、二叉树查找
1.二叉树查找算法
a.递归算法
btree *search(btree *b,int x)
{
/*在二叉树b中查找x的过程*/
if(b == NULL)
return NULL;
else {
if(b->data == x)
return b;
if(x < b->data)
return search(b->left, x);
else
return search(b->right, x);
}
}
b.非递归算法
bsnodetype *Search(bsnodetype *bt, keytype Key)
{
/*在二叉树bt中查找元素为Key的元素*/
bsnodetype *p;
if(bt == NULL)
return bt;
p=bt;
while(p->Key != Key){
if(Key < p->Key)
p = p->Lchild;
else
p = p->Rchild;
if(p == NULL)
break;
}
return p;
}
2.生成二叉树
a、向一个二叉树b中插入一个结点s的函数如下:
void insert(btree *b, btree *s)
{
if(b == NULL)
b = s;
else if(s->data == b->data)
return;
else if(s->data < b->data)
insert(b->left, s);
else if(s->data > b->data)
insert(b->right, s);
}
b、创建二叉树
void create(btree *b)
{
int x;
bnode *s;
b == NULL;
while(x != -1){
scanf("%d",&x);
s = (bnode *)malloc(sizeof(bnode));
s->data = x;
s->left = NULL;
s->right = NULL;
insert(b,s);
}
}
c、从二叉树中删除一个结点
bnode *Delete(bnode *bt, keytype Key)
{
/*在bt为根结点的二叉树中删除值为Key的结点*/
bnode *p,*q;
if(bt->Key==Key){
/*bt的左右子树均为空*/
if(bt->Lchild==NULL && bt->Rchild==NULL) {
free(bt); /*删除叶结点*/
return NULL;
} else if(bt->Lchild == NULL) {/*bt的左子树为空*/
p = bt->Rchild;
free bt;
return p;
} else if(bt->Rchild == NULL) {/*bt的右子树为空*/
p = bt->Lchild;
free bt;
return p;
} else {
p = q = bt->Rchild;
while(p->Lchild != NULL)
p = p->Lchild;
p->Lchild = bt->Lchild;
free bt;
return q;
}
}
/*在bt->Lchild为根结点的二叉树中删除值为Key的结点*/
if(bt->Key>Key && bt->Lchild!=NULL)
bt->Lchild = Delete(bt->Lchild, Key);
/*在bt->Rchild为根结点的二叉树中删除值为Key的结点*/
if(bt->Key<Key && bt->Rchild!=NULL)
bt->Rchild=Delete(bt->Rchild,Key);
return bt;
}
五、哈希查找
实现哈希函数为“除法取余法”,解决冲突为“开放地址线性探测法”,代码如下:
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 ,就是第一个就是待查找的远射,最差的查找状况是O(n),就是最后一个是待查找的元素。
二、折半查找
折半查找是将待查找的数组元素不断的分为两部分,每次淘汰二分之一,但是有个大前提是,元素必须是有序的,如果是无序的则要先进行排序操作,这种查找的方法,类似于找英文字典的Java,我们可以一下子找到字母J开头的,再仔细找。
最佳的状况时间是1,就是第一次分开就查找到了,最差的查找状态是O(n),便是待查找的数据出现在最后一次。
三、插补查找
插补查找是一种类似折半查找的查找方法,插补查找是以比例的概念,求出待查找数据的可能位置,然后进行比较,如果该值比待查找的小,表示待查找的值可能出现在该值之前的范围,就这样一直缩小范围来确定最终的目标。
四、二叉查找树
二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。
这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。
五、哈希查找
查找找效率不依赖于数据长度n,查找效率非常快,很多能达到O(1),查找的效率是a(装填因子)的函数,而不是n的函数。因此不管n多大都可以找到一个合适的装填因子以便将平均查找长度限定在一个范围内。