开发工具与关键技术:数据结构与算法
作者:陈芝番
撰写时间:2020.5.2
目录
前言
静态查找是指在静态查找表上进行的查找操作,查找满足条件的数据元素的存储位置或各种属性。而排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序。
1.顺序表的类型定义
#define MAX_NUM 100 //用于定义表的长度
Typedef struct elemtype{
Keytype key;
......
}RecordType{MaxSize};
数据元素个数为n(n<MAX_NUM)
分别存放在数组的下标变量a[1]-a[n]中
2.完整的顺序查找算法
Int SeqSearch(RecordType r ,int n,KeyType k){
//返回关键字值等于k的数据元素在表r中的位置,n为表中元素的个数
i = n;
r [0].key! = k;//监视哨
While (r[i],key!=k)
i --;
If (i>0)
Return(i);//查找成功
Else
Return(-1);//查找失败
}//SeqSearch
也有可能i返回0;
3.折半查找的基本思想
(1)每进行一次折半查找,要么查找成功,结束查找,要么将查找范围缩小一半,如此重复。
(2)直到查找成功或查找范围缩小为空即查找失败为止。
折半查找算法如下:
Int BinSearch(RecordType r ,int n,KeyType k)
{//在有序表r中折半查找关键字值等于K的数据元素
Int low ,high ,mid;
Low = 1;high = n;//置初始查找范围的低,高端指针
While(low <=high)
{
Mid = (low+high)/2;//取表的中间位置
If(k == r[mid].key)
Return (mid);//查找成功
Else
If(k<r[mid-1].key)
High = mid-1;//在左子表中查找
Else
}
Low = mid + 1;//在右子表中查找
}
Return(-1);//查找失败
}//BinSearch
(1)二分查找又称折半查找。是对有序的顺序表进行的高效查找方法。
(2)二分查找算法平均查找长度为log2^n,比较次数少,查找速度快。
只能应用于有序的顺序表。
二分查找适用于那种一经建立就很少改动,而又经常需要查找的顺序表。
4.分块检索
分块查找又称索引顺序查找,它是一种性能介于顺序查找和二分查找之间的查找方法。
(1)“分块有序”的线性表
线性表R被均分为若干块,每一块中的关键字不一定有序,但前一块中的最大关键字必须小于后一块中的最小关键字,即表是“分块有序”的。
(2)“有序的”索引表
抽取各块中的最大关键字及其起始位置和长度构成一个索引表ID中,即:
索引表中的数据元素有两个域构成,Key域为被索引的若干个数据元素中关键字的最大值,link域为被索引的若干个数据元素中第一个数据元素的位置编号。
5.查找表存储结构
查找表由“分块有序”线性表和“有序的”索引表组成。
静态查找方法比较
顺序查找 折半查找 分块查找
ASL 最大 最小 两者之间
表结构 有序表,无序表 有序表 分块有序表
存储结构 顺序存储结构,线性链表 顺序存储结构 顺序存储结构,线性链表
6.动态查找
特点:表结构本身是在查找中动态生成,对于给定值K,如表中存在,则查找成功;否则在适当的位置插入K。
动态查找的结构主要有二叉树结构和树结构两种类型。
二叉排序树又称为二叉查找树,它或者是空树,或者是满足如下性质的二叉树;
(1)若它的左子树非空,则左子树上所有节点的值均小于根节点。
(2)若它的右子树非空,则右子树上所有节点的值均大于根节点的值。
(3)它的左,右子树本身又各是一棵二叉排序树。
例:
有关键字值序列{50,70,20,10,60,30,80,5,75,35,95}构成的一棵二叉排序树如
按中序遍历该树所得到的中序序列是一个递增有序序列是一个递增有序序列
如上:(5,10,20,30,35,50,60,70,75,80,95)
7.查找过程如下
(1)若二叉树为空树,则查找失败
(2)将给定值k与根节点的关键字值比较,若相等,则查找成功
(3)若给定值k小于根节点的关键字值,则在左子树中继续搜索,如果左子树为空,则查找失败。
(4)否则,在右子树中继续查找,如果右子树为空,则查找失败。
二叉排序树结构的定义:
Typedef struct BSTNode
{
KeyType key;
Struct BSTNode * lchild ,*rchild;
}BSTNode,*BSTree;
算法如下:
BSTree f;//f指向移动指针p的双亲
BSTree BSTearch(BSTree t,KeyType k)
{//在根结点为t的二叉排序树中查找关键字值等于K的结点
P = t;f=NULL;
While(p! =NULL)
{
If(k == p -> key)
Return (p);//查找成功
Else
If(k<p->key)
{f=p;p=p->lchild;}//在左子树中继续查找
Else
{f = p;p=p->rchild;}//在右子树中继续查找
}
Return(NULL);//查找失败
}//BSTSearch
8.哈希表又称散列表
存储基本思想:
以每个记录的关键字k为自变量,通过一种函数H(k)计算出一个函数值。
(1)把这个值解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该记录存储到这个单元中。
(2)在此称该函数H为哈希函数或散列函数。
例:假设将关键字值序列{24,8,37,19,55,42},存储到编号为0到6的表长为7的哈希表中:
计算存储地址的哈希函数为模7运算。即H(k)= k%7
即构造好的哈希表如下:
24除以7的余数是3,则对应上图位置3;
8除以7的余数是1,则对应上图位置1;
同上,......
查找方法:通过关键字直接得到记录的存储地址
冲突:
理想的哈希函数是在关键字和存储地址之间建立一一对应的关系,从而使得查找只需一次计算即可完成。但由于关键字值得某种随机性,使得这种一一对应关系难以发现或构造。因而可能会出现不同关键字对应一个存储地址。即k1不等于k2,但H(k1)= H(k2),这种现象称为冲突。
然而对于哈希技术,主要研究两个问题:
(1)如何设计哈希函数以使冲突尽可能少的发生。
(2)发生冲突后如何解决。
使用最多的就是:除留余数法
取关键字被某个不大于哈希表表长m的数p除后所的余数作哈希地址,即:
H(Key)=key MOD(p<=m)
这是一种较简单,也是较常见的构造方法。这种方法的关键是选择好的哈希表的长度m。使得数据集合中的每一个关键字通过该函数转化后映射到哈希表的任意地址上的概率相等。理论研究表明,在m取值为素数(质数)时,冲突可能性相对较少。
冲突的解决办法有两个
(1)开放定址法
(2)建立散列表
线性探查法的探查序列为
Hi = (h(key)+i%)m 0《i《m-1
在这里:m = 13
H0 = (41 + 0)%13=2 0《 i《12
当冲突发生时,形成一个探查序列;沿此序列逐个地址探查,直到找到一个空位置(开放的地址),将发生冲突的记录放到该地址中,即h1 = (H(key)+di)MODm,i=1,2,......k(km-1)
其中:
H(key)--哈希函数
m--哈希表表长
Di--增量序列
使用除留余数法
9.直接插入排序算法如下
Void InsertSort(RecData r , int n)
//对记录数组r[1.....n]做直接插入排序
{int i ,j;
For(i=2;i<=n;i++)
{r[0]=r[i];//r[i]存入监视哨r[0]
J = i-1;
While (r[0].key<r[j].key)
{r[j + 1]=r[j];
//将关键字大于r[i].key的记录后移
J--;
}
R[j+1]=r[0];//r[i]插入到正确的位置
}
}//InsertSort
10.效率分析
(1)从空间来看,他只需要一个元素的辅助空间。
(2)从时间分析,最少比较次数(正序)为O(n),最多比较次数(逆序)O(n^2)。因此,直接插入排序的时间复杂度为O(n^2)。
(3)直接插入算法的元素移动是顺序的,该方法是稳定的。
(4)适用于对排序元素较少,且基本有序的数据元素排序。
问题:直接插入排序在无序数插入到有序数中寻找位置,是经过一个个数值比较后才能得到适当的位置,能否改进呢?
方法:第一趟比较前两个数,然后把第二个数按大小插入到有序表中;第二趟把第三个数与前两个数从后向前扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。
快速排序算法如下:
Int QuickPass(RecData r ,int i,int j)
//对序列r[i...j]进行一趟快速排序
{
r[0]=r[i];//选择第i个记录做基准记录
While(i<j)
{
While(i<j && r[j].key>=r[0].key)
j--;//j从右向左找小于人【0】。Key的记录
If(i<j)
{
r[i]=r[j];//找到小于r【0】。Key的记录,交换
i++
}
While(i<j && r[i].key<r[0].key)
i++;//i从左向右找大于r[0].key的记录
if(i<j)
{r[j] = r[i];//找到大于r[0].key的记录,交换
j--;
}
}
r[i]=r[0];
return;
}//QuickPass
11.;递归算法:
Void QuickSort(RecData r ,int I,int h)
{
Int k;
If(i<h)
{
K=QuickPass(r,I,h);//对r[I...h]划分
QuickSort(r ,I ,K-1);//对左区间递归排序
QuickSort(r,k+1,h);//对右区间递归排序
}
}//QuickSort
快速排序算法的时间效率取决于划分子序列的次数,对于有n个记录的序列进行划分,共需n-1次关键字的比较,在最好情况下,假设每次划分得到两个大致等长的记录子序列,时间复杂度为0(log2^n)。快速排序是一种不稳定的排序方法。
选择排序有两种:
(1)直接选择排序和堆排序。
(2)直接选择排序算法如下:
Void SelectSort (RecData r,int n)
{
Int i,j,k;
For (i=1 ;i<=n-1;++i)
{
K= 1;
For(j=i+1;j<=n;++j)
If(r[j].key<r<[k].key)//选出关键字最小的记录
K= j;//k保存当前找到的最小关键字的记录位置
If(k!=i){
交换r[k]和r[i]
}
}
}//SelectSort
在直接选择排序过程中需要的关键字的比较次数与序列原始顺序无关,当i=1时(外循环执行第一次),内循环比较n-1次,i=2时,内循环比较n-2次,以此类推,算法的从次数为(1+2+3.....+n-1)=n(n-1)/2。因此,直接选择排序的时间复杂度为O(n^2),由于只用一个变量做辅助空间,故空间复杂度为0(1),直接选择排序是不稳定的。
感悟
排序比查找要复杂一些,要求能比较插入排序,快速排序等不同算法的优劣,能够额外空间消耗,平均时间复杂度和最差时间复杂度等方面去比较它们的优缺点。其中快速排序尤其重要。