说实话查找平时用的的确不是很多,不过也有可能是对自己放松了要求,最近确实过的太安逸了,甚至考试也令我无动于衷,的却该收收心,专心投入学习了,再这样下去就成为废人了。。。
一,概述
这章主要还是讲查找的几个算法,比较优劣,拓宽思路,其实就是增加难度。主要介绍三种表:
1,线性表 适用静态查找,不涉及频繁插入和删除操作。
2,数表 动态查找,涉及插入删除操作。
3,散列表 是一种先计算再比较的查找算法。
1,查找性能问题
引入平均查找长度ASL,将查找算法进行的关键码的比较次数的数学期望值定义为平均查找长度
1~n求和p*c
n:问题规模,查找集合中的记录个数;
pi:查找第i个记录的概率;
ci:查找第i个记录所需的关键码的比较次数。
二,线性表的查找技术
1,带监视哨的顺序查找
将所找元素放于下标0位置,免去了在查找过程中每次比较都要判断是否越界,优化了性能
int SeqSearch(int k)
{
int i = length; //从数组高端开始比较
data[0] = k; //设置哨兵
while (data[i] != k) //不用判断下标i是否越界
i--;
return i;
}
2,折半查找
折半查找又叫二分法,相信已经非常熟悉了,是顺序存储中最常用的方法但还是有很多要注意的地方
非递归:data数组封装,独立运用需带参,注意变换边界+1 -1 的位置
int BinSearch1(int k){
int mid, low = 1, high = length; //初始查找区间是[1, n]
while (low <= high) {//当区间存在时
mid = (low + high) / 2;
if (k < data[mid])
high = mid - 1;
else if (k > data[mid])
low = mid + 1;
else
return mid; //查找成功,返回元素序号
}
return 0; //查找失败,返回0
}
递归:
int BinSearch2(int low, int high, int k){
if (low > high)
return 0; //递归的边界条件
else {
int mid = (low + high) / 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; //查找成功,返回序号
}
}
3,折半查找判定树
把折半查找用二叉树表示,常考其性能分析,以11个节点的二叉树分析:
查找成功:在表中查找任一记录的过程,即是折半查找判定树中从根结点到该记录结点的路径,和给定值的比较次数等于该记录结点在树中的层数。
如:给定一棵树,该树的ASLsucc=(1+2*2+3*4+4*4)/11=33/11=3
查找失败的过程就是走了一条从根结点到外部结点的路径,
和给定值进行的关键码的比较次数等于该不通路径上内部结点的个数(失败情况下的平均查找长度等于树的高度)。
查找不成功时的ASLusucc:= ( 3*4+4*8) /12
三,树表的查找技术
1,二叉排序树,左子树小于根节点,右子树大于根节点,仍采用树(二叉链表)的方式:
树的构建由插入完成,二叉树不空新插入的节点必为一个新的叶子节点,递归寻找位置:
#include <iostream>
using namespace std;
template <class DataType>
struct BiNode{ DataType data; BiNode *lchild, *rchild; };
class BiSortTree {
public:
BiSortTree(int a[ ], int n); //建立查找集合a[n]的二叉排序树
~ BiSortTree( ){ Release(root); } //析构函数,同二叉链表的析构函数
void InOrder( ){InOrder(root);} //中序遍历二叉树
BiNode *InsertBST(int x) {return InsertBST(root, x);} //插入记录x
BiNode *SearchBST(int k) {return SearchBST(root, k);} //查找值为k的结点
void DeleteBST(BiNode *p, BiNode *f ); //删除f的左孩子p
private:
void Release(BiNode *bt);
BiNode *InsertBST(BiNode *bt , int x);
BiNode *SearchBST(BiNode *bt, int k);
void InOrder(BiNode *bt); //中序遍历函数调用
BiNode *root; //二叉排序树的根指针
};
BiNode *BiSortTree::InsertBST(BiNode *bt, int x)
{
if (bt == NULL) { //找到插入位置
BiNode *s = new BiNode;
s->data = x;
s->lchild = NULL;
s->rchild = NULL;
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 = NULL;
for (int i = 0; i < n; i++)
root = InsertBST(root, a[i]);
}
此外二叉排序树的删除也是个麻烦事,分为三种情况:
二叉排序树的查找性能取决于二叉排序树的形状,在O(log2n)和O(n)之间。
2,平衡二叉树(首先是一棵二叉排序树)
根结点的左子树和右子树的深度最多相差1,根结点的左子树和右子树也都是平衡二叉树。
平衡因子:结点的平衡因子是该结点的左子树的深度与右子树的深度之差。 平衡因子小于等于1.
注意最小不平衡二叉树:在平衡二叉树的构造过程中,以距离插入结点最近的、且平衡因子的绝对值大于1的结点为根的子树。
构造平衡二叉树
每插入一个节点都要判断是否破坏平衡,从底向上找到不平衡点,根据前两步走法分类:
设结点A为最小不平衡子树的根结点,对该子树进行平衡调整归纳起来有以下四种情况:
1. LL型
2. RR型
3. LR型
4. RL型
LL,RR都是中间节点做新根,RL,LR末节点做新根,插入新节点包括三步:
(1) 查找应插位置, 同时记录离插入位置最近的可能失衡结点A(A的平衡因子不等于0)。
(2) 插入新结点S, 并修改从A到S路径上各结点的平衡因子。
(3) 根据A、 B的平衡因子, 判断是否失衡以及失衡类型, 并做相应处理。
3,B_树(平衡有序)
m阶B-树:是满足下列特性的树:
1, 树中每个结点至多有m棵子树;(求m 空指针算上找最大m)
2, 若根结点不是终端结点,则至少有两棵子树;
3,除根结点外,其他非终端结点至少有(m/2)向上取整 棵子树;
4,所有叶子结点都在同一层上,B树是高平衡的。
四,散列表(hash)的查找技术
特殊存储,特殊查找。在存储位置和关键码之间建立联系,是一种计算式的查找。
散列函数:将关键码映射为散列表中适当存储位置的函数。
散列地址:由散列函数所得的存储位置址,相当于数组下标
冲突:对于两个不同关键码ki≠kj,有H(ki)=H(kj),即两个不同的记录需要存放在同一个存储位置,ki和kj相对于H称做同义词。
*关键
1,散列函数的设计,广泛采用除留余数法,选p为小于或等于表长(最好接近表长)的最小素数
2,冲突的处理:
2.1,开散列法(链地址法,拉链法)
将所有散列地址相同的记录,即所有同义词的记录存储在一个单链表中(称为同义词子表),在散列表中存储的是所有同义词子表的头指针。
2.2,闭散列法(开放定址发)一维数组解决
线性探测法
从冲突的下一个位置起依次寻找空地, Hi=(H(key)+di) % m 堆积:在处理冲突的过程中出现的非同义词之间对同一个散列地址争夺的现象。成功探测经过的键值不一定都是同义词,可能是相应值被挤入不同房间。
二次探测法
Hi=(H(key)+di)% m
从冲突开始先向下一个找下一个没空间找上一个,依次往复
2.3 建立公共溢出区
基本表+溢出表