查找(Searching)是指在查找表中确定一个关键字等于给定值的数据元素或记录。
静态查找表:查找某元素是否存在,或检索某元素的属性。
动态查找表:查找时能够插入或删除元素。
有序表查找
前提是线性表记录的关键字有序,比如从小到大,比如对无序表先做个排序。下面列了三种方法:
二分查找(Binary Search)
条件:线性表记录是关键码有序比如从小到大;线性表采用顺序存储。
思想:取中间记录为比较对象,给定值比其小则在左半区间继续找,比其大则在右半区间继续找,重复上述过程。
int Binary_Search(int *a, int key)
{
int low, high, mid;
low = 0;
high = int(sizeof(a)/sizeof(int)) - 1;
while (low <= high)
{
mid = (low + high) / 2;
if (key < a[mid])
high = mid - 1;
else if (key > a[mid])
low = mid + 1;
else
return mid;
}
return 0;
}
插值查找(Interpolation Search)
思想:根据key与表中最大最小记录比较后的查找方法,关键是一个插值的计算公式。
int Interpolation_Search(int *a, int key)
{
int low, high, mid;
low = 0;
high = int(sizeof(a) / sizeof(int)) - 1;
while (low <= high)
{
mid = low + (high - low)*(key - a[low]) / (a[high] - a[low]); /*插值*/
if (key < a[mid])
high = mid - 1;
else if (key > a[mid])
low = mid + 1;
else
return mid;
}
return 0;
}
斐波那契查找(Fibonacci Search)
/*Fibonacci查找, F={0,1,1,2,3,5,8,13,21,34,...}*/
int Fibonacci_Search(int *a, int key)
{
int low, high, mid, i, k, n;
low = 0;
high = int(sizeof(a) / sizeof(int))-1;
n = high;
k = 0;
while (n > F[k]-1) /*找最后一位在Fibonacci数列中的位置*/
k++;
for (i = n; i < F[k]-1; i++) /*把表中不到F[k]的用最后一位补全*/
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;
}
二叉排序树(查找,插入,删除)
二叉排序树(Binary Sort Tree)是动态查找最重要的数据结构。它不为空的时候具有以下性质:
- • 若左子树不为空,左子树上所有结点的值都小于根结点的值
- • 若右子树不为空,右子树上所有结点的值都小于根结点的值
- • 左、右子树也都是二叉排序树
也就是说,这个二叉树的结点间满足一定的次序关系,显然查找就更容易了。构造二叉排序树不是为了排序,而是为了提高查找、插入、删除的速度。
/*二叉树的结构*/
typedef struct BiTreeNode
{
int data;
struct BiTreeNode *lchild, *rchild;
}BiTreeNode, *BiTree;
/* 递归查找二叉排序树中是否存在key */
bool BinarySortTreeSearch(BiTree T, int key, BiTree f, BiTree *p)
{
if (!T) /*若查找不成功,把当前结点(是NULL)的父结点(肯定是个叶子)给p*/
{
*p = f;
return false;
}
else if (key == T->data)
{
*p = T;
return true;
}
else if (key < T->data)
return BinarySortTreeSearch(T->lchild, key, T, p);
else if (key > T->data)
return BinarySortTreeSearch(T->rchild, key, T, p);
}
/* 二叉排序树插入操作 */
bool BinarySortTreeInsert(BiTree T, int key)
{
BiTree p, s;
if (!BinarySortTreeSearch(T, key, NULL, &p)) //查找不到,则插入
{
s = (BiTree)malloc(sizeof(BiTreeNode));
s->data = key;
s->lchild = NULL;
s->rchild = NULL;
if (!p)
T = s; //p为空,说明原树为空,s成为新的根结点
else if (p->data > key)
p->lchild = s;
else if (p->data < key)
p->rchild = s;
return true;
}
else //查找到了说明树中有相同的结点,则不插入
return false;
}
/* 二叉排序树的构建,利用插入操作 */
void BinarySortTreeBuild()
{
int i;
int a[10] = { 65, 34, 78, 23, 45, 76, 89, 12, 32, 86 };
BiTree T = NULL;
for (i = 0; i < 10; i++)
BinarySortTreeInsert(T, a[i]);
}
/* 删除二叉排序树中等于key的数据元素结点 */
bool BinarySortTreeDelete(BiTree T, int key)
{
if (!T)
return false;
else
{
if (key == T->data)
return BiSTNodeDelete(&T); //删除结点p,重接它的左树或右树
else if (key < T->data)
return BinarySortTreeDelete(T->lchild, key);
else if (key > T->data)
return BinarySortTreeDelete(T->rchild, key);
}
}
bool BiSTNodeDelete(BiTree *p)
{
BiTree q, s;
if ((*p)->lchild == NULL)
{
q = *p; (*p) = (*p)->rchild; free(q);
}
else if ((*p)->rchild == NULL)
{
q = *p; (*p) = (*p)->lchild; free(q);
}
else //左右子树都不为空
{
s = (*p)->lchild;
while (s->rchild) //比待删结点(*p)小且最接近它的结点
{
q = s; s = s->lchild;
}
(*p)->data = s->data;
if (q != (*p))
q->rchild = s->lchild;
else
q->lchild = s->lchild;
free(s);
}
}
平衡二叉树(AVL树)
如果二叉树不平衡,在查找深度较深的结点时效率反而高。
平衡的意思是,每个结点的平衡因子(BF)是-1,0,1。平衡因子是指结点的左子树深度减右子树深度。
举例说明:对同一个数组,a[10] = {3,2,1,4,5,6,7,10,9,8},用前面的方法构造二叉树会如图(左),而理想的平衡二叉树如图(右)
如果构建的二叉树不平衡,那么频繁的查找、插入、删除的效率会非常低。所以就在构建时让它是平衡二叉树,这样才是一种比较理想的动态查找表算法。
散列表查找(哈希表)
散列表是一种非常高效的数据查找结构。
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem; //数组存储地址,动态分配
int count;
}HashTable;
int m = 0;
/* 初始化散列表 */
void 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;
}
/* 定义散列函数 */
int Hash(int key)
{
return key%m; //除留余数法
}
/* 插入关键字进散列表 */
void InsertHash(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 UNSUCCESS; //地址元素为空或是循环回到原点,都说明key不存在
}
return SUCCESS;
}
如果不发生冲突,查找的复杂度是 O(1),它回避了反复的比较操作,基本是一步到位。
但散列表查找关系到 HashTable 的创建,装填因子 α (=填入表的记录数/散列表长度) 越大,冲突的可能性越大,为了尽可能减少冲突,一般把散列表的长度设置的比查找集合大。虽然浪费了一点空间,但得到了效率的提升。