数据结构 第七章学习小结
数据结构第七章学习小结
- 查找
- 7.1 查找的基本概念
(1) 查找表 查找表是由同一类型的数据元素(或记录)构成的集合。如线性表、树表及散列表等。 (2)关键字 关键字是数据元素(或记录) 中某个数据项的值,用它可以标识一个数据元素(或记录)。若此关键字 可以唯一地标识一个记录,则称此关键字为主关键字。反之,称用以识别若千记录的关键字为次关键字。 (3)查找 查找是指根据给定的某个值,在查找表中确定一个其关键字等千给定值的记录或数据元素。 (4)动态查找表和静态查找表 若在查找的同时对表做修改操作(如插入和删除),则相应的表称之为动态查找表,否则称之为静态查找表。 (5)平均查找长度 为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值,称为查找算法在查找成功时的平均查找长度(ASL)。
- 7.2 线性表的查找
- 顺序查找
- 类型定义
typedef struct { KeyType key; //关键字域 InfoType otherinfo; //其他域 ) ElemType; typedef struct{ ElemType *R; //存储空间基地址 int length; //当前长度 }SSTable;
- 普通算法
int Search_Seq(SSTable ST,KeyType key) {//在顺序表 ST中顺序查找其关键字等千key的数据元素。若找到 , 则函数值为该元素在表中的位置,否则为0 for(i=ST.length;i>=l;--i) if(ST.R[i] .key==key) return i; ///从后往前找 return 0; } 在查找过程中每步都要检测整个表是否查找完毕,即每步都要有循环变量是否满足 条件i >=1的检测。
- "监视哨"算法
int Search_Seq(SSTable ST,KeyType key) {//在顺序表 ST 中顺序查找其关键字等于 key 的数据元素。若找到,则函数值为该元素在表中的位置,否则为 0 ST.R[O] .key=key; //"哨兵” for(i=ST.length;ST.R[i] .key!=key;--i); //从后往前找 return i; }
- 算法分析
通过设置监视哨,免去查找过程中每一步都要检测整个表是否查找完毕。时间复杂度为 O(n)。 顺序查找的优点是:算法简单,对表结构无任何要求,既适用于 顺序结构,也适用千链式结构,无论记录是否按关键字有序均可应用。 缺点是: 平均查找长度较大,查找效率较低,所以当n很大时,不宜采用顺序查找。
- 类型定义
- 折半查找(二分查找)
- 算法
int Search_Bin(SSTable ST,KeyType key) {//在有序表ST 中折半查找其关键字等于key的数据元素。若找到, 则函数值为该元素在表中的位置, 否则为0 low= l;high=ST.length; //置查找区间初值 while(low<=high) { mid=(low+high)/2; 矿~- if{key==ST.R[mid] .key) return mid; //找到待查元素 else if{key<ST.R[mid] .key) high=mid-1; //继续在前一子表进行查找 else low=mid+l; //继续在后一子表进行查找 } return 0; //表中不存在待查元素 }
- 算法分析
折半查找的时间复杂度为 O(log2n),折半查找的效率比顺序查找高,但折半查找只适用千有序表,且限千顺序存储结构。 折半查找的优点是:比较次数少,查找效率高。 其缺点是:对表结构要求高,只能用于顺序存储的有序表。查找前需要排序,而排序本身是一种费时的运算。同时为了保持顺序表的有序性,对有序表进行插入和删除时,平均比较和移动表中一半元素,这也是一种费时的运算。折半查找不适用于数据元素经常变动的线性表。
- 算法
- 分块查找
- 分块查找又称索引顺序查找,是一种性能介于顺序查找和折半查找之间的一种查找方法。分块查找的平均查找长度为:ASLbs =Lb +Lw ( Lb 为查找索引表确定所在块的平均查找长度,Lw为在块中查找元素的平均查找长度。 )
分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块,就可以在该 块内进行插入和删除运算。 由于块内是无序的,故插入和删除比较容易,无需进行大量移动。 如果线性表既要快速查找又经常动态变化,则可采用分块查找。 其缺点是:要增加一个索引表的存储空间并对初始索引表进行排序运算。
- 分块查找又称索引顺序查找,是一种性能介于顺序查找和折半查找之间的一种查找方法。分块查找的平均查找长度为:ASLbs =Lb +Lw ( Lb 为查找索引表确定所在块的平均查找长度,Lw为在块中查找元素的平均查找长度。 )
- 顺序查找
- 7.3 树表的查找
- 二叉排序树
- 算法
- 类型定义
//- - - - -二叉排序树的二叉链表存储表示----- typedef struct { KeyType key; //关键字项 InfoType otherinfo; //其他数据项 }ElemType; //每个结点的数据域的类型 typedef struct BSTNode { ElemType data; //每个结点的数据域包括关键字项和其他数据项 struct BSTNode *lchild,*rchild; //左右孩子指针 }BSTNode,*BSTree;
- 递归查找
BSTree SearchBST (BSTree- T, KeyType key) _, {//在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素 //若查找成功 , 则返回指向该数据元素结点的指针, 否则返回空指针 if ((! T) || key==T->data. key) return T; //查找结束 else if (key<T->data. key) return SearchBST (T->lchild, key); //在左子树中继续查找 else return SearchBST (T->rchild, key) ; //在右子树中继续查找 }
- 插入(复杂度:O(log2n))
void InsertBST(BSTree &T,ElemType e} {//当二叉排序树 T中不存在关键字等千e.key的数据元素时, 则插入该元素 if (!T) { //找到插入位置 , 递归结束 S=new BSTNode; //生成新结点*S S->data=e; //新结点*S的数据域置为e S->lchild=S->rchild=NULL; //新结点*S作为叶子结点 T=S; //把新结点*S链接到已找到的插入位置 } else if (e. key<T->data. key) InsertBST(T->lchild, e ); //将*S插入左子树 else if (e.key> T->da七a.key) InsertBST(T->rchild, e); //将*S插入右子树
- 创建(复杂度:O(nlog2n))
void CreatBST(BSTree &T) {//依次读人一个关键字为key的结点, 将此结点插人二叉排序树T中 T=NULL; //将二叉排序树T初始化为空树 cin>>e; while(e.key!=ENDFLAG) //ENDFLAG为自定义常址,作为输入结束标志 { InsertBST(T,e); //将此结点插入二叉排序树T中 cin>>e; } }
- 删除(复杂度:O(log2n))
void DeleteBST(BSTree &T,KeyType key) {//从二叉排序树 T 中删除关键字等千 key 的结点 p=T;f=NULL; //初始化 /*-------下面的 while 循环从根开始查找关键字等于 key 的结点*p-------*/ while (p) { if(p->data.key==key) break; //找到关键字等于 key 的结点*p, 结束循环 f=p; //*f 为*p 的双亲结点 if(p->data.key>key) p=p->lchild; //在*p 的左子树中继续查找 else p=p->rchild; //在*p 的右子树中继续查找 } if (!p) return; //找不到被删结点则返回 /*-考虑3种情况实现p 所指子树内部的处理: *p 左右子树均不空、 无右子树、 无左子树-*/ if ((p->lchild) && (p->rchild)) //被删结点*p 左右子树均不空 { q=p; s=p->lchild; while (s->rchild) ///在*p 的左子树中继续查找其前驱结点,即最右下结点 { q=s; s=s->rchild; //向右到尽头 } p->data=s->data; //s 指向被删结点的 “前驱" if(q!=p) q->rchild=s->lchild; //重接*q 的右子树 else q->lchild=s->lchild;//重接*q 的左子树 delete s; return; } else if (! p->rchild) //被删结点*p 无右子树, 只需重接其左子树 { q=p; p=p->lchild; } else if (!p- > lchild) //被删结点*p 无左子树, 只需重接其右子树 { q=p; p=p->rchild; } /*--将 p 所指的子树挂接到其双亲结点*f 相应的位置--*/ if(!f) T=p; //被删结点为根结点 else if(q==f->lchild) f->lchild=p; //挂接到*f 的左子树位置 else f->rchild=p; /挂接到*f 的右子树位置 delete q; }
- 类型定义
- 算法
- 平衡二叉树(AVL树)
具有如下特征的二叉排序树: (1 )左子树和右子树的深度之差的绝对值不超过1; (2)左子树和右子树也是平衡二叉树。 若将二叉树上结点的平衡因子定义为该结点左子树和右子树的深度之差,则平衡二叉树上所有结点的平衡因子只可能觅-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1 则该二叉树就是不平衡的。 查找的时间复杂度是O(log2n)
- B-树(具有平衡、有序、多路的特点)
- 算法
- 类型定义
#define m 3 //8-树的阶,暂设为3 typedef struct BTNode { int keynum; //结点中关键字的个数,即结点的大小 struct BTNode *parent; //指向双亲结点 KeyType key [m+ 1] ; //关键字向址,0号单元未用 struct BTNode *ptr[m+1]; //子树指针向量 Record *recptr[m+1]; //记录指针向量,0号单元未用 }BTNode,*BTree; //B-树结点和 B-树的类型 typedef struct { BTNode *pt; //指向找到的结点 int i; //1 .. m, 在结点中的关键字序号 int tag; //1: 查找成功, 0: 查找失败 }Result;//B-树的查找结果类型
- 查找
Result SearchBTree(BTree T,KeyType key) {//在 m 阶 B-树 T 上查找关键字 key, 返回结果(pt,i, tag) //若查找成功,则特征值 tag=l, 指针 pt 所指结点中第 J. 个关键字等千 key //否则特征值 tag=O, 等千 key 的关键字应插入在指针 pt 所指结点中第 1 和第迁1 个关键字之间 p=T;q=NULL;found=FALSE;i=O; //初始化, p 指向待查结点, q 指向 p 的双亲 while (p&& ! found) { i=Search(p,key); //在 p-> key [ 1 .. keynum]中查找 i, 使得: p->key[i] <=key<p->key[i+l] if(i>O&&p->key[i]==k) found=TRUE; //找到待查关键字 else{q=p; p=p->ptr[i]; } if (found) return (p, i, 1); //查找成功 else return(q,i,0); //查找不成功,返回K的插人位置信息 }
- 插入
Status InsertBTree(BTree &T,KeyType K,BTree q,int i) {//在m阶B-树T上结点*q的key[i) 与 key[i+l)之间插人关键字k //若引起结点过大, 则沿双亲链进行必要的结点分裂调整 , 使T仍是m阶B-树 x=K;ap=NULL;finished=FALSE; //x表示新插入的关键字, ap 为一个空指针 while (q&& ! finished) { Insert(q,i,x,ap);/ /将x和ap分别插入到q->key[i+l)和q->ptr[i+1] if (q->keynum<m) finished=TRUE; //插入完成 else //分裂结点*q { s=「rn/2l sp让t(q,s,ap); x=q->key[s]; //将 q->key[ s+ 1. . rn] , q->ptr [ s .. rn]和q->recptr[ s+ 1. . rn]移入新结点*ap q=q->parent; if(q) i=Search (q, x); //在双亲结点*q 中查找 x的插入位置 } } if (!finished) //T是空树(参数q初值为NULL)或者根结点已分裂为结点*q和*ap NewRoot(T,q,x,ap);//生成含信息(T,x,ap)的新的根结点*T, 原T和ap为子树指针 return OK; }
- 类型定义
- 算法
- B+树
- B-树和B+树的差异
(1)有n棵子树的结点中含有n个关键字; (2) 所有的叶子结点中包含了全部关键字的信息,以及指向含这些关键字记录的指针,且叶 子结点本身依关键字的大小自小而大顺序链接; (3)所有的非终端结点可以看成是索引部分,结点中仅含有其子树(根结点)中的最大(或最小)关键字。
- B-树和B+树的差异
- 二叉排序树
- 7.4 散列表的查找
- 基本概念
(1)散列函数和散列地址:在记录的存储位置p和其关键字key 之间建立一个确定的对应关 系H,使 p=H(key), 称这个对应关系H为散列函数,p为散列地址。 (2)散列表:一个有限连续的地址空间,用以存储按散列函数计算得到相应散列地址的数据 记录。通常散列表的存储空间是一个一维数组,散列地址是数组的下标。 (3)冲突和同义词:对不同的关键字可能得到同一散列地址,即key吊key2 ,而H(key1 )=H(key2),这种现象称为冲突。具有相同函数值的关键字对该散列函数来说称作同义词,key1 与key2互称为同义词.
- 构造方法
- 数字分析法
- 平方取中法
- 折叠法
- 除留余数法
H(key) = key%p
- 处理冲突的方法
- 开放地址法
Hi = (H(key) +di)%m i = 1, 2, …,k(k≤m-1)
- 线性探测法
di = l, 2, 3, …, m-1
- 二次探测法
di = 1^2,1^2,2^2,-2^2,3^2.....+k^2,-k^2(k≤m/2)
- 伪随机探测法(二次聚集或堆积)
- 线性探测法
- 链地址法
把具有相同散列地址的记录放在同一个单链表中,称为同义词链表。
- 开放地址法
- 算法
- 查找
#define NULLKEY 0 //单元为空的标记 int SearchHash(HashTable HT,KeyType key) {//在散列表 HT中查找关键字为key的元素,若查找成功, 返回散列表的单元标号,否则返回-1 HO=H (key); //根据散列函数H (key)计算散列地址 if(HT[HO) .key==NULLKEY) return -1; //若单元HO为空, 则所查元素不存在 else if(HT[HO] .key==key) return HO; //若单元HO中元素的关键字为key, 则查找成功 else { for(i=l;i<m;++i) { Hi=(HO+i)兮m; //按照线性探测法计算下一个散列地址Hi if(HT[Hi] .key==NULLKEY) return -1; //若单元肛为空, 则所查元素不存在 else if(HT[Hi] .key==key) return Hi; //若单元肛中元素的关键字为key, 则查找成功 } return -1; } }
- 算法分析
查找过程中需和给定值进行比较的关键字的个数取决千三个因素:散列函数、处理冲突 的方法和散列表的装填因子。 散列表的装填因子a定义为a=表中填入的记录数/散列表的长度。 a标志散列表的装满程度。直观地看,a越小,发生冲突的可能性就越小;反之,a越大, 表中已填入的记录越多,再填记录时,发生冲突的可能性就越大,则查找时,给定值需与之进行比较的关键字的个数也就越多。 散列函数的 “好坏“ 首先影响出现冲突的频繁程度。 影响平均查找长度的因素只有两个--处理冲突的方法和装填因子 a
- 查找
- 基本概念
- 7.1 查找的基本概念
- 心得体会
- 这一章节主要学习查找的算法,主要分为顺序查找、二分查找、树表(包括二叉排序树、平衡二叉树、B-树、B+树)、散列表
- 顺序查找
- 找到下的ASL=(C1+C2+C3+....Cn)/n (C是指探测次数)
- 找不到下的ASL=(1+n)/2
- 二分查找ASL=log2n
- 二叉排序树的比较次数为h[树高]。(最好情况O(log2n),最坏的情况是单链表O(h)) AVL:逼近log2n
- 由于我个人觉得计算平均查找长度的时候会比较难,所以会上网搜索一些资料加深自己的理解(https://www.cnblogs.com/ygsworld/p/10238729.html),主要是结合例题就懂怎么算了。
- 因为这章每节的查找方法都比较多,需要好好梳理,什么查找方法对应什么样的查找方式。而且感觉大部分都是手画手算,代码的实现比较少,但是一旦写起代码又比较难。
- 实践1还是比较容易解决的,然后作业题是英文题目,害!写代码难度不大,读题难度大点哈哈哈哈哈。然后关于实践2,我一开始第一感觉是利用多个字符进行不同的操作,但是后来还得存储数据,就会很难很麻烦,然后我就上网搜索看了一下别人的代码,结果都是利用了STL的map,但我就只是大略看了一下,因为最近期末好多作业和大作业。临近期末考了,感觉之前学的好多知识都不太记得了,也还没复习,其他科也是,最近在开始规划怎么复习跟完成各科作业以及大作业。希望期末能考好点吧,努力不会被辜负~