查找的基本概念
关键码
由同一类型的数据元素组成的集合
键值
关键码的值
主关键码
可以唯一的标识一个记录的关键码
次关键码
不能唯一的标识一个记录的关键码
静态查找
不涉及插入和删除操作的查找
动态查找
涉及删除和插入操作的查找
静态查找适用于:查找一经生成,便只对其进行查找,而不进行删除操作
动态查找适用于:查找和插入和删除操作在同一个阶段进行。
所以
线性表:适用于静态查找
树表:适用于动态查找
散列表:均适用,主要采用散列技术。
查找算法的性能:
ASL=(求和1-n)pi*ci;
其中n为问题规模,pi为查找第i条记录的概率;
ci为查找第i个记录所需要的比较次数;
线性表的查找技术
线性表查找结构类定义:
const int MaxSize = 100;
class LineSearch
{
public:
LineSearch(int a[], int n);//构造函数
~LineSearch() {};
int SeqSearch(int k);//顺序查找
int BinSearch(int k);//折半非递归查找
int BinSearch2(int low, int high, int k);//折半递归查找
private:
int data[MaxSize];//查找集合为整形
int length;//查找集合的元素个数
};
LineSearch::LineSearch(int a[], int n)
{
for (int i = 0; i < n; ++i)
data[i + 1] = a[i];//查找元素从下标1开始存放
length = n;
}
顺序查找
基本思想等省略
直接上代码
int LineSearch::SeqSearch(int k)
{
int i=length;//从数组高端开始比较
data[0]=k;//设置哨兵哨兵就是待查值
while(data[i]!=k)//不用判断下标i是否越界
i--;
return i;
}
折半查找:
基本思想:
假设有序表按升序排列,取中间的值作为比较对象,若给定值的中间记录相等,则查找成功。
若给定值小于中间记录,则在有序表的左半区进行查找,否则在右半区进行查找。不断重复上述过程,直到查找成功或者查找失败。
折半查找的非递归算法:
int LineSearch::BinSearch(int k)
{
int mid, low = 1, high = length;
while (low <= high)
{
mid = (low + high) / 2;
if (k < data[mid])
high = mid - 1;
else if (k > data[mid])low = high + 1;
else
return mid;//查找成功返回元素序号
}
return 0;//查找失败返回0;
}
折半查找递归算法
int LineSearch::BinSearch2(int low,int high,int k)
{
if (low > high)
return 0;//递归结束条件
else {
int mid = (high +low)/2;
if (k < data[mid])
return BinSearch2(low, mid - 1, k);
else if (k > data[mid])return BinSearch2(mid + 1, high, k);
else
return mid;//查找成功返回序号;
}
}
折半查找的平均时间复杂度为log2n;
树表的查找技术
二叉排序树:
性质:
若左子树不空,那么左子树上的所有结点的值均小于根节点的值;
若右子树不空,那么右子树上的所有结点的值均大于根结点的值;
左右子树也都是二叉排序树;
二叉排序树通常用二叉链表来存储,其结点结构可以用二叉链表的结点结构存储。
template <class DataType>
struct BiNode
{
DataType data;
BiNode<DataType>* lchild, * rchild;
};
class BiSortTree
{
public:
BiSortTree(int a[], int n);//建立a[n]的二叉排序树
~BiSortTree() { Release(root); }//同二叉链表的析构函数
BiNode<int>* InsertBST(int x) { return InsertBST(root, x); }
//插入记录x
void DeleteBST(BiNode<int >* p, BiNode<int >* f);//删除f的左孩子p
BiNode<int>* BiSearchBST(int k) { return SearchBST(root, k); }//查找值为k的结点
private:
BiNode<int >* InsertBST(BiNode<int >* bt, int x);
BiNode<int>* SearchBST(BiNode<int >* bt, int k);
void Release(BiNode<int>* bt);
BiNode<int>* root;//二叉排序树的根指针;
};
BiNode<int>* BiSortTree::SearchBST(BiNode<int>* bt, int k)
{
if (bt == nullptr)
return nullptr;
if (bt->data == k)
return bt;
else if (bt->data > k)//bt的值大于k则在左子树上查找
return SearchBST(bt->lchild, k);
else if (bt->data < k)//bt的值小于k则在右子树上查找
return SearchBST(bt->rchild, k);
}
二叉排序树的插入
向二叉排序树中插入一个结点首先需要查找该结点的位置,然后在进行插入操作
新节点作为叶子插入到二叉排序树当中,无论是插在左子树还是右子树,都是按同样的方法进行处理,所以插入过程是递归的。
//二叉排序树的插入
BiNode<int>* BiSortTree::InsertBST(BiNode<int>* bt, int x)
{
if (bt == nullptr)
{
BiNode<int>* s = new BiNode<int >; s->data = x;
s->lchild = s->rchild = nullptr;
bt = s;
return bt;
}
else if (bt->data > x)
bt->lchild = InsertBST(bt->lchild, x);
else bt->rchild = InsertBST(bt->rchild, x);
}
构造一颗二叉排序树
BiSortTree::BiSortTree(int a[], int n)
{
root = nullptr;
for (int i = 0; i < n; ++i)
{
root = InsertBST(root, a[i]);
}
}
二叉排序树的删除
void BiSortTree::DeleteBST(BiNode<int >* p, BiNode<int>* f)
{
if (p->lchild == nullptr&&p->rchild==nullptr)
{
f->lchild == nullptr;
delete p;
return;
}
else if (p->rchild == nullptr)
{
f->lchild = p->lchild;
delete p;
return;
}
else if (p->lchild == nullptr)
{
f->lchild = p->rchild;
delete p;
return;
}
BiNode<int >* par, * s = p->rchild;//p的左右子树都不为空
while (s->lchild != nullptr)
{
par = s;
s = s->lchild;
}
p->data = s->data;
if (par == p)
par->rchild = s->rchild;
else par->lchild = s->rchild;
delete s;
}
时间性能
log2n 和n之间;
平衡二叉树,平衡二叉树的调整办法
首先什么是平衡二叉树:
或者是一颗空的二叉排序树
或者是具有下列两种性质的二叉排序树
1.根结点的左右子树的深度最多相差1。
2.根结点的左右子树都是平衡二叉树。
结点平衡因子是,左右子树深度之差。
所以平衡二叉树的平衡因子为-1或0或1
最小不平衡子树是指再平衡二叉树的就够构造过程中,以距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树。这个根结点称为最小不平衡结点。
类型
LL型,RR,LR,RL型。
平衡方法:
原则:(1)降高度。
(2)二叉排序树原则。
在这个基础上进行排序
B树
B树是一种平衡的多路查找树,常用在问价系统中。
定义如下
一个m阶的B树或者为空树或者为满足以下特性的m二叉树。
1.每个结点最多右m棵子树
2.根节点至少有两棵子树。
3.根节点除外其余结点至少有m/2棵子树。
4.所有结点包含以下数据(n,A0,K1,A1,K2,。。。Kn,An);
5.所有的结点都出现在同一层,所以B树是树高平衡。
散列表的查找技术
两个关键的技术
1散列表的构造
最直接定址法
除留余数法
数字分析法
平方取中法
折叠法
2冲突处理方法
开放地址法
链地址法
建立公共溢出区
散列的基本思想
在记录的存储地址和它的关键码之间建立一个确定的关系。这样,不经过比较,一次读取就能的到所查元素的查找方法
散列表:采用散列技术将存储在这一块的存储空间中,这块连续的存储空间称为散列表
散列函数:将关键码映射为散列表中适当的存储位置的函数
散列地址:由散列函数所得的存储位置址
散列是主要面向查找的存储结构
不适用于允许多个记录有同样的关键码的情况
不适用于范围查找
冲突:对于两个不同的关键码,即两个不同的记录需要存放在同一个存储位置,ki和kj相对于H称作同义词
散列函数
设计散列函数一般准寻以下两个原则
(1)计算简单。散列函数不应该有很大的计算量,否则会降低查找效率
(2)函数值即散列地址分布均匀。函数值要尽量均匀散步在地址空间,这样才能保证存储空间的有效利用并减少冲突。
直接定址法
H(key)=a*key+b;
例关键码集合为{10,30,50,70,80,90}
H(key)=key mod p;
一般情况下,选p为小于或等于表长(最好接近表长)的最小素数
数字分析法
根据关键码在各位上的分布情况,选取分布比较均匀的若干位组成散列地址。
平方取中法
对关键码平方后,按散列表大小,取中间的若干位作为散列地址
折叠法
将关键码从左到右分割成位数相等的几部分,将这几部分叠加求和,取后几位作为散列地址。
冲突处理
开散列表法
闭散列表法
建立公共溢出区
开放地址法
由关键码得到的散列地址一旦产生了冲突,就去寻找下一个空的散列地址,并将记录存入。
线性探测法
当发生冲突时,从冲突位置的下一个位置起,一次寻找空的散列地址。
对于键值key设H(key)=d,闭散列表的长度为m,则发生冲突时,寻找下一个散列地址的公式为
H(H(key)+di)%m;
堆积:在处理冲突 的过程中出现的非同义词之间对同一个散列地址争夺的现象
int HashSearch1(int ht[ ], int m, int k)
{
j=H(k);
if (ht[j]==k) return j; //没有发生冲突,比较一次查找成功
i=(j+1) % m;
while (ht[i]!=Empty && i!=j)
{
if (ht[i]==k) return i; //发生冲突,比较若干次查找成功
i=(i+1) % m; //向后探测一个位置
}
if (i==j) throw "溢出";
else ht[i]=k; //查找不成功时插入
}
拉链法:
基本思想
将所有散列地址相同的记录,即所有同义词的记录存储在一个单链表中,在散列表中存储的是所有同义词子表的头指针。
用拉链法处理冲突构造的散列表叫做开散列表。