查找算法:
静态查找和动态查找
静态查找:数据集合稳定,不需要添加、删除元素的查找操作
动态查找:数据集合在查找的过程中需要同时添加或删除元素的查找操作
查找结构:
静态查找:线性表结构,顺序查找算法(对关键字进行排序:折半查找或斐波那契查找算法)
动态查找:二叉排序查找,散列表结构
顺序查找:从第一个找到最后一个
实现方式:数组,一个一个比较
/*顺序查找*/
#define n 10;
int main()
{
int a[n];
int i = n;
int key;
key = a[0];
while(key != a[i])
{
i--;
}
return i;
}
插值查找(按比例查找)(适合数据均匀分布:递增或递减):折半查找的优化
mid = low + ((key-a[low])/(a[high]-a[low]))*(high-low);
斐波那契查找:
黄金比例查找,使用斐波那契数列后两数的比无限接近于0.618的特点,将mid赋值为中间的斐波那契数,达到高效的目的
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 20
void Fibonacci(int *f)
{
int i = 2;
f[0] = 1;
f[1] = 1;
while( i < MAXSIZE )
{
f[i] = f[i-1] + f[i-2];
i++;
}
}
int search(int *a, int key, int n)
{
int low = 0;
int high = n-1;
int mid = 0;
int k = 0;
int F[MAXSIZE];
int i;
Fibonacci(F);
while(n > F[k]-1) //根据数组现有元素个数判断需要使用的斐波那契数
{
k++;
}
if( n < F[k] ) //对于无法达到斐波那契数的原数组进行元素补充
{
for(i=n; i<F[k]; i++)
{
a[i] = a[high];
}
}
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 //若原数组已被填充,查找数字为最大数时可能出现mid大于high的情况
{
if( mid <= high )
{
return mid;
}
else
{
return high;
}
}
}
return -1;
}
int main()
{
int a[MAXSIZE] = {2, 6, 10, 13, 15, 19, 25, 28, 36, 39};
int key, pos;
printf("请输入待查找的数字:\n");
scanf("%d", &key);
pos = search(a, key, 10);
if( pos == -1 )
{
printf("\n查找失败!\n");
}
else
{
printf("\n关键字%d所在位置为%d,查找成功!\n", key, pos);
}
return 0;
}
线性索引查找:
稠密索引(数组+链表):对每个元素的关键码进行排序,需要查找时对排序后的索引表进行折半查找(适用于数据量不大的无序数据组合)
分块索引(数组+链表):索引表只存储各块内的最大关键字以及各块内的起始地址字(各块内数据无序,各块间数据有序)
倒排索引(浏览器索引原理之一):由属性查找记录,需要对记录号进行增减
二叉排序树的查找、插入和删除:
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 25
typedef int Elemtype;
typedef struct TreeNode
{
Elemtype data;
struct TreeNode *lchild;
struct TreeNode *rchild;
}TreeNode, *BiTree;
int x = 0;
void InitArrar(int *a)
{
FILE *fp;
int i = 0;
int j;
if((fp=fopen("c:\\tree.txt", "r")) == NULL)
{
printf("Can not open the file!\n");
exit(1);
}
while( !feof(fp) )
{
fscanf(fp, "%d,", &j);
a[i] = j;
i++;
}
fclose(fp);
}
void CreateBitree(BiTree *T, int *a)
{
if( 0 == a[x] )
{
*T = NULL;
x++;
}
else
{
*T = (TreeNode *)malloc(sizeof(TreeNode));
(*T)->data = a[x];
x++;
CreateBitree(&(*T)->lchild, a);
CreateBitree(&(*T)->rchild, a);
}
}
//递归查找二叉排序树T中是否存在key
//指针f指向T的双亲,其初始调用值为NULL
int SearchTree(BiTree T, int key, BiTree f, BiTree *p)
{
if( T == NULL ) //查找到该处结点为空
{
*p = f; //p赋值为其双亲的值,返回0
return 0;
}
else if( key == T->data )
{
*p = T; //p赋值为该值,返回1
return 1;
}
else if( key < T->data )
{
return SearchTree(T->lchild, key, T, p);
}
else
{
return SearchTree(T->rchild, key, T, p);
}
}
int InsertTree(BiTree *T, int key)
{
BiTree s, p;
if( !SearchTree(*T, key, NULL, &p) ) //树中找不到该元素
{
s = (BiTree)malloc(sizeof(TreeNode));
s->data = key;
s->lchild = s->rchild = NULL;
if( !p )
{
p = s;
}
else if( key < p->data )
{
p->lchild = s;
}
else
{
p->rchild = s;
}
return 1;
}
else
{
return 0;
}
/*
if( (*T) == NULL )
{
*T = (BiTree)malloc(sizeof(TreeNode));
(*T)->data = key;
(*T)->lchild = (*T)->rchild = NULL;
return 1;
}
else if( (*T)->data == key )
{
return 0;
}
else if( key < (*T)->data )
{
return InsertTree(&(*T)->lchild, key);
}
else
{
return InsertTree(&(*T)->rchild, key);
}
*/
}
int Delete(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 //左右子树都不为空
{
q = *p;
s = (*p)->lchild;
while( s->rchild ) //找到待删除结点的直接前驱结点
{
q = s;
s = s->rchild;
}
(*p)->data = s->data;
if( q != *p ) //待删除结点的左子树有往下的右子树
{
q->rchild = s->lchild;
}
else //待删除结点的左子树没有往下的右子树
{
q->lchild = s->lchild;
}
free(s);
}
return 1;
}
int DeleteTree(BiTree *T, int key)
{
if( !*T )
{
return 0;
}
else
{
if( key == (*T)->data )
{
return Delete(T);
}
else if( key < (*T)->data )
{
return DeleteTree(&(*T)->lchild, key);
}
else
{
return DeleteTree(&(*T)->rchild, key);
}
}
}
void visit(int i)
{
printf("%d ", i);
}
void MidOrderTraverse(BiTree T)
{
if( T )
{
MidOrderTraverse(T->lchild);
visit(T->data);
MidOrderTraverse(T->rchild);
}
}
int main()
{
BiTree T = NULL;
BiTree p;
int a[MAXSIZE];
int key;
int i;
InitArrar(a);
CreateBitree(&T, a);
MidOrderTraverse(T);
printf("\n请输入需要查找的值:\n");
scanf("%d", &key);
i = SearchTree(T, key, NULL, &p);
if(1 == i)
{
printf("查找成功!\n");
}
else
{
printf("查找失败...\n");
}
printf("\n请输入需要插入的值:\n");
scanf("%d", &key);
i = InsertTree(&T, key);
if(1 == i)
{
printf("插入成功!\n");
}
else
{
printf("插入失败...此数已存在\n");
}
printf("\n");
MidOrderTraverse(T);
printf("\n");
printf("\n请输入需要删除的值:\n");
scanf("%d", &key);
i = DeleteTree(&T, key);
if(1 == i)
{
printf("删除成功!\n");
}
else
{
printf("删除失败...此数不存在\n");
}
printf("\n");
MidOrderTraverse(T);
printf("\n");
return 0;
}
平衡二叉树:左子树与右子树高度差最多为1
平衡因子(BF):左子树和右子树的深度之差
多路查找树:降低磁盘IO操作
2-3树:多路查找树中的每一个结点都具有两个孩子或者三个孩子或者没有孩子,即子树只存在满树或者空树的情况,且所有叶子结点均在同一层
2-3树的插入原理:
一、空树: 直接插入数据
二、数据应该插入的部分是2树:直接插入数据,2树转3树
三、数据应该插入的部分是3树:
1.双亲结点为2树:双亲结点转3树,插入数据根据大小插入适当的位置,原插入部分分解,其中一个作为其原双亲结点的一个数据或者一个子树
2.双亲结点为3树,双亲结点往上(包括根节点)为2树:由下往上找到的第一个2树转3树,判断插入数据在该结点的左(右)并将该结点的右(左)子树作为中间的子树,插入数据格局大小插入适当的位置
3.所有双亲结点(包括根结点)均为3树:增加层数,从数据应该插入的部分(最底层)由下往上开始拆分,一般有转化为二叉树的趋势
2-3树的删除原理:
一、要删除的数据位于是3树的叶子结点上:直接删除
二、要删除的数据位于是2树的叶子结点上:
1.其双亲为2树的结点,兄弟结点为3树结点:删除数据,双亲和其兄弟结点旋转(左右根据删除数据是左还是右)
2.其双亲为2树的结点,兄弟结点也为2树结点:利用中序遍历找出其直接前驱或直接后继,再调整相应的位置
3.其双亲为3树的结点:删除数据,将双亲转为2树结点,将其子树补为三树结点
三、树为满二叉树,删除叶子结点:缩小一层,把原来的2树结点转为3树结点
四、要删除的数据位于是2树的非叶子结点上,且其子树存在3树结点:删除数据,通过数据大小选择子3树结点的数据补齐原来删除的部分
五、要删除的数据位于是2树的非叶子结点上,且其子树不存在3树结点:利用中序遍历找出其直接前驱或直接后继,再调整相应的位置
六、要删除的数据位于是3树的非叶子结点上,且其子树存在3树结点:删除数据,通过数据大小选择子3树结点的数据补齐原来删除的部分
七、要删除的数据位于是3树的非叶子结点上,且其子树不存在3树结点:删除数据,将子树根据数据大小转为3树结点
2-3-4树:多路查找树中的每一个结点都具有两个孩子或者三个孩子或者四个孩子或者没有孩子,即子树只存在满树或者空树的情况,且所有叶子结点均在同一层
B树:一种平衡的多路查找树,2-3树和2-3-4树均为B树的特例
m阶B树的特性:
-如果根节点不是叶子结点,则其至少有两棵子树
-每一个非根的分支结点都有k-1个元素和k个孩子,其中k满足:[m/2] <= k <= m
-所有叶子结点都位于同一层次
-每一个分支结点包含下列信息数据:
n,A0,K1,A1,K2,A2,K3,A3,......其中K为关键字,且Ki<Ki+1,Ai为指向子树根结点的指针
散列表(哈希表)查找(不适用于关键字重复的情况):
记录的存储位置 = f(关键字)
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)
冲突(不被允许):不同的关键字通过同一个散列函数得到相同的结果
构造散列函数的两个基本原则:
计算简单,分布均匀
1.直接定址法:取关键字的某个线性函数值为散列地址,即:f(key) = a*key + b;
2.数字分析法(适用于关键字位数比较大的情况):抽取其中的确定数字作为散列地址
3.平方取中法:将关键字平方之后取中间若干位数字作为散列地址
4.折叠法:将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长取后几位作为散列地址
5.除留余数法:f(key) = key mod p (p<m),p的选择很重要
6.随机数法(适用于关键字的长度不等的情况):选择一个随机数,取关键字的随机函数作为它的散列地址
根据以下因素采用不同的散列函数:
计算散列地址所需的时间
关键字的长度
散列表的大小
关键字的分布情况
记录查找的频率
处理散列冲突的方法:
1.开放定址法:一旦发生冲突,立即寻找下一个空的散列地址(散列表足够大) fi(key) = (key + di) mod m, (di = 1,2,3,...,m-1)
二次探测法:可以修改di的取值方式,例如使用平方运算: fi(key) = (key + di) mod m, (di = 1^2,-1^2,2^2,-2^2,...,q^2,-q^2 q<m/2)
随机探测法:对di采用随机函数获得
2.再散列函数法:尝试其他散列函数
3.链地址法:利用单链表的形式存储冲突的数据
4.公共溢出区法:基本表存储第一个数据,溢出表存储冲突的数据
散列表查找的代码实现:
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem; //数据元素的基址,动态分配数组
int count; //当前数据元素的个数
}HashTable;
int InitHashTable(HashTable *H)
{
int i;
H->count = HASHSIZE;
H->elem = (int *)malloc(HASHSIZE * sizeof(int));
if( !H->elem )
{
return -1;
}
for(i=0; i<HASHSIZE; i++)
{
H->elem[i] = NULLKEY;
}
return 0;
}
//使用除留余数法法
int Hash(int key)
{
return key % HASHSIZE;
}
//插入关键字到散列表
void InsertHash(HashTable *H, int key)
{
int addr;
addr = Hash(key);
while( H->elem[addr] != NULLKEY )
{
addr = (addr + 1) % HASHSIZE; //开放定址法的线性探测
}
H->elem[addr] = key;
}
//在散列表里边查找关键字
int SerachHash(HashTable H, int key, int *addr)
{
*addr = Hash(key);
while( H.elem[*addr] != key )
{
*addr = (*addr + 1) % HASHSIZE;
if( H.elem[*addr] == NULLKEY || *addr == Hash(key) )
{
return -1;
}
}
return 0;
}