查找算法总结:
一、顺序表查找
int sequebtial_search(int *arr, int n, int key)
{
int i = 0;
for(i = 0; i < n; i++)
{
if(key == arr[i])
return i;
}
return -1;
}
上述代码有个问题,每for循环一次,就要判断是否溢出(i < n),设置一个哨兵的方式可以解决上述问题
int sequebtial_search(int *arr, int n, int key)
{
if(key == arr[0])
{
return -1;//返回-1,key = arr[0]
}
else
{
arr[0] = key;
i = n -1 ;
while(key != arr[i])
{
i--;
}
return i;//返回0则没有找到
}
}
注意:也可以把arr[n-1]当作哨兵。
二、有序表查找: 首先确保被查找的序列有序。
1、折半查找(二分查找)O(logn)
int binary_search(int *arr, int n, int key)
{
int low, mid, high;
low = 0;
high = n - 1;
while(low <= high)
{
mid = (low + high) / 2; //这个地方可能会溢出,最好写成
// mid = low + (high - low) / 2
if(key == arr[mid])
{
return mid;
}
else if(key < arr[mid])
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
2、插值查找 O(logn)
二分查找:mid = 1/2 * (low + high) = low + 1/2 * (high - low)
也就是每次都是从high和low的正中间查找,如果key正好在正中间,查找速度很快,但是大多数情况下,key都是偏左或者偏右的,我们可以根据key值在arr[low]和arr[high]的大概比例,更快速的找到key。
mid = low + (key - arr[low]) / (arr[high] - arr[high]) * (high - low)
代码只需要修改计算mid的一行即可。
但是,有一个问题,如果数据分布比较跨度比较大的时候,这种算法并不见得比二分查找有效。适用于数据分布比较均匀的情况。
3、斐波那契查找 O(logn)
斐波那契数列:F[n] = F[n - 1] + F[n - 2] (n >= 2)
0, 1, 1, 2, 3, 5, 8, 13, 21, 34……
把数据补最大值到F[k] - 1个元素,low为最左端值,high为最右端值,mid把数据分成两部分,F[k - 1] - 1 和 F[k - 2] - 1,再依次分下去,直到找到key
int Fibonacci_search(int *a, int n, int key)
{
int low, mid, high, i, k;
low = 1;
high = n;
k = 0;
while(n > F[k] - 1) //判断当前数据表和数列最小值差差多少个元素
k++;
for(i = n; i < F[k] - 1; i++) //依次补最大元素
{
a[i] = a[n];
}
while(low < high)
{
mid = low + F[k-1] - 1;
if(key < a[mid])
{
high = mid -1;
k = k - 1;
}
else if(key > a[mid])
{
low = mid + 1;
k = k - 2;
}
else
{
if(mid <= n)
return mid;
else
return n;
}
}
return 0;
}
相比mid = 1/2 * (low + high) = low + 1/2 * (high - low) 和 mid = low + (key - arr[low]) / (arr[high] - arr[high]) * (high - low) ,mid = low + F[k - 1] - 1,在大批量数据的情况下,效率更高。
三、树
1、二叉查找树(二叉排序树)
左结点的值都小于根结点的值,右结点的值都大于根结点的值,左右子树也是二叉查找树。
(1)查找
二叉查找树的数据查找和二分查找思想相同,先从根结点开始,如果小于根结点,查找根的左子树,否则查找右子树。
typedef struct BiTNode
{
int data;
struct BiTNode *left, *right;
}BiTNode;
BiTNode *f = NULL, *p = NULL;
int SearchBST(BiTNode *T, int key, BiTNode *f, BiTNode *p) //p为返回指针, f为找到的结点的双亲结点的指针
{
BiTNode *T_mid = T;
if(T_mid = NULL)
{
p = f; //最后没有查到,p返回上一个结点的指针
return -1;
else if(T_mid->data == key)
{
p = T_mid;
return 0;
}
else if(key > T_mid->data)
{
SearchBST(T_mid->right, key, T_mid, p);
}
else
{
SearchBST(T_mid->left, key, T_mid, p);
}
}
(2)插入
为了方便,插入一般都是在叶子结点进行的。
int InsertBST(BiTNode *T, int key)
{
BiTNode *p, *s;
if(!SearchBST(T, key, NULL, p))) //查找不到key,并且已经到叶子节点
{
s = ( BiTNode *)mallco(sizeof(BiTNode));
s->data = key;
s->right = s->left = NULL;
if(!p)
T = s;
else if(p->data > key)
p->left = s;
else
p->right = s;
return 0;
}
else
return -1;
}
(3)删除
删除操作必须考虑,删除该结点后,会不会影响整棵树。所以必须考虑以下三种情况:
1)、删除叶子结点:直接删除即可
2)、删除的结点只有左子树或者右子树,删除后,还需要把原来的左子树或者右子树移到当前删除结点的位置
3)、删除结点既有左子树又有右子树,需要从子树的叶子结点中找一个替代删除结点(找删除结点左子树中最右的点,或者右子树中最左的点,注意找到的结点可能依然有左子树或者右子树,需要重新连接上)
int DeleteBST(BiTNode *T, int key)
{
if(!T)
return -1;
else
{
if(T->data == key)
Delete(T);
else if(T->data > key)
DeleteBST(T->left, key);
else
DeleteBST(T->right, key);
}
}
int Delete(BiTNode *p)
{
BiTNode *q, *s;
if(p->left == NULL) //将上述1)的情况归为2)的情况处理
{
q = p;
p = p->right;
free(q);
}
else if(p->right == NULL)
{
q = p;
p = p->left;
free(q);
}
else
{
q = p;
s = p->left;
while(s->right)
{
q = s;
s = s->right;
}
p->data = s->data;
q->right = s->left;
free(s);
}
return 0;
}
(4)创建
二叉查找树的创建,其实就是二叉查找树依次插入每一个结点的过程,但是,同样的数据创建的二叉查找树是有很多找中的,左右子树比较均衡的二叉查找树的查找时间复杂度是O(logn),但是,极端情况下(斜树)的时间复杂度却是O(n),得不偿失,为此,我们必须尽量创建一颗左右比较均衡的二叉查找树才会提高查找效率。
2、平衡二叉树(AVL树)
AVL树是一种高度平衡的二叉查找树,其每一个节点的左子树和右子树的高度差不大于1。二叉树上某节点左子树深度减去右子树深度的值称为平衡因子BF。
构建平衡二叉树的过程中,每插入一个节点,必须保证插入后依然是平衡二叉树,为此,当插入某一个节点的时候,我们必须判断其BF。
typedef struct BiTNode
{
int data;
int bf;
struct BiTNode *left, *right;
}BiTNode;
//右旋
//对以p为根的二叉排序树作右旋处理
//处理后,p指向新的树根节点,即旋转处理之前的左子树的根节点
void R_Rotate(BiTNode *p)
{
BiTNode *L;
L = p->left;
p->left = L->right;
l->right = p;
p = L;
}
//左旋
void L_Rotate(BiTNode *p)
{
BiTNode *R;
R = p->right;
p->right = R->left;
R->left = p;
p = R;
}
//左平衡旋转
#define LH +1
#define EH 0
#define RH -1
void LeftBalance(BiTNode *T)
{
BiTNode *L, *Lr;
L = T->left;
switch(L->bf)
{
case LH:
T->bf = L->bf = EH;
R_Rotate(T);
break;
case RH:
Lr = L->right;
switch(Lr->bf)
{
case LH:
T->bf = RH;
L->bf = EH;
break;
case EH:
T->bf = L->bf = EH;
break;
case RH:
T->bf = EH;
L->bf = LH;
break;
}
Lr->bf = EH;
L_Rotate(T->left);
R_ROtate(T);
}
}
//主函数
int InsertAVL(BiTNode *T, int key, Status *taller)
{
if(!T)
{
T = (BiTNode *)malloc(sizeof(BiTNode));
T->data = key;
T->left = T->right = NULL;
T->bf = EH;
taller = TRUE;
}
else
{
if(key == T->data)
{
*taller = FALSE;
return -1;
}
if(key < T->data)
{
if(!InsetAVL(T->left, key, taller))
return -1;
if(taller)
{
switch(T->bf)
{
case LH;
LeftBalance(T);
taller = FALSE;
break;
case EH:
T->bf = LH;
taller = TRUE;
break;
case RH:
T->bf = EH;
taller = FALSE;
break;
}
}
}
else
{
if(!InsertAVL(T->right, key, taller))
return FALSE;
if(taller)
{
switch(T->bf)
{
case LH:
T->bf = EH;
taller = FALSE;
break;
case EH:
T->bf = RH:
taller = TRUE;
break;
case RH:
RightBalance(T);
taller = FALSE;
break;
}
}
}
}
return 0;
}
3、多路查找树(B树)
(1)B树
2-3树、2-3-4树
(2)B+树
四、散列表查找
散列表也称哈希表,与顺序表、链表不同,散列表存储数据的地址是分散的,通过散列函数求得散列地址。散列表查找主要分两步:1、构建散列函数,确定散列地址,存入数据;2、根据数据和散列函数,求得散列地址。但是,存在f(key1) = f(key2)的情况(冲突),所以在构建散列函数的时候必须解决冲突问题,另外,为了减小时间复杂度,散列函数越简单越好;为了减小空间复杂度,使存储空间得到充分利用,散列地址要分布均匀。
(1)散列函数构造
1)直接定址 f(key) = a*key + b
2)数字分析
3)平方取中法
4)折叠法
5)除留余数法 f(key) = key mod p (p<=m)
6)随机数法 伪随机,随机因子相同,结果一致
(2)处理冲突
1)开放定址 一旦发生冲突,寻找下一个空散列地址。
fi(key) = (f(key) + di ) MOD m (di = 1,2,3……,m-1)
fi(key) = (f(key) + di ) MOD m (di = 1, -1, 4, -4, ……,qq, -qq, q<=m/2) 取平方为了不让关键字聚集
fi(key) = (f(key) + di ) MOD m (di 是随机数列)
2)再散列 多个散列函数
3)链地址 当冲突时,在后面增加链表,但是查找效率变低
4)设置公共溢出区
(3)散列表查找的实现
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem;
int count;
}HashTable;
imt m = 0;
int InitHashTable(HashTable *H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(m*sizeof(int));
for(i = 0; i < m; i++)
{
H->elem[i] = NULLKEY;
}
return 0;
}
int Hash(int key)
{
return key % m;
}
void InsetHash(HashTable *H, int key)
{
int addr = Hash(key);
while(H->elem[addr] != NULLKEY)
addr = (addr + 1) % m;
H->elem[addr] = key;
}
int SearchHash(HashTable H, int key, int *addr)
{
*addr = Hash(key);
while(H.elem[*addr] != key)
{
*addr = (*addr + 1) % m;
if(H.elem[*addr] == NULLKEY || *addr == Hash(key))
{
return -1;
}
}
return 0;
}