查找
顺序查找
Int seqsch (ElemTYpe a[], int n, KeyType k)
{
for (int I = 0; I < n; l++)
{
if (a[l].key == k)
Break;
}
If ( I < n)
return I;
else
return -1;
}
顺序查找的改进法,改进后的算法省略了对下标越界的检查,所以提高了速度:
Int seqsch (ElemTYpe a[], int n, KeyType k)
{
A[n].key = k; // 设置岗哨
for (int I = 0;; l++)
{
if (a[l].key == k)
Break;
}
If ( I < n)
return I;
else
return -1;
}
二分查找(又称折半查找)
1. 递归法
Int Binsch(ElemType a[], int left, int right, KeyType k)
{ left 和 right的值分别为 0 – n-1
If (left <= hight)
{
Int mid = (left+right)/2;
中间位置
If (k == a[mid].key)
return mid;
else if (k < a[mid].key)
return Binsch(a, left, mid -1,k);
// 在左子表上查找
else if (k > a[mid].key)
return Binsch(a, mid + 1, right k);// 在右子表上查找
}
Else
Return -1;
查找失败
}
2. 非递归
Int Binsch(ElemType a[], KeyType k)
{ left 和 right的值分别为 0 – n-1
Int left = 0, right = n-1;
while (left <= hight)
{
Int mid = (left+right)/2;
中间位置
If (k == a[mid].key)
return mid;
else if (k < a[mid].key)
right = mid -1;
// 在左子表上查找
else if (k > a[mid].key)
left = mid + 1; // 在右子表上查找
}
Else
Return -1;
查找失败
}
插入排序(直接插入排序)
Void InsertSort (ElemType a[], int n)
{
ElemType x;
Int I,j;
for (i=1; i<n; i++)
{ // i表示插入次数,共进行n-1次插入
X = a[i];
// 暂存待插入有序表中的元素a[i]值
for (j = i-1; j>=0; j--)
If (x.stn < a[j])
a[j+1] = a[j];
// 进行顺序比较和移动
else
break;
// 查询到j+1位置时离开
a[j+1] = x; // 把原a[i]的值插入到下标为j+1位置
}
}
插入排序(希尔排序)
Void ShellSort(ElemType a[], int n)
{
ElemType x;
Int I,j,d;
for (d=n/2;d>=1;d/=2)
{//按不同分量进行排序
for (I = d; i<n; i++)
{// 将a[i]元素直接插入到对应分组的有序表中
X = a[i];
for (j=i-d;j>=0;j-=d)
{// 在组内向前顺序进行比较和移动
If(s.stn<a[j].stn)
A[j+d] = a[j];
else
break; // 查找到就退出
}
a[j+d]=x; // 将a[i]的值放入到合适位置
}
}
}
直接选择排序
Void SelectSort(ElemType a[], int n)
{
ElemTYpe x;
Int I,j,k;
For (i=1;i<=n-1;i++)
{
K=i-1; //用K保存当前得到的最小排序码元素下标,初值为i-1
For(j=I;j<=n-1;j++)
{//从当前排序区间中顺序查找出具有最小排序码的元素a[k]
If(a[j].stn < a[k].stn)
k=j;
}
If (k!=i-1)
{// 把a[k]对调到此排序区间的第一个位置,即i-1位置
X=a[i-1];a[i-1]=a[k];a[k]=x;
}
}
}
堆排序
Void HeapSort(ElemType a[], int n)
{
ElemTYpe x;
Int I;
For (i=n/2-1; i>=0; i--)
Sift(a,n,i);//建立初始堆
For(i=1;i<=n-1;i++)
{//进行n-1次for,完成堆排序
X=a[0];a[0]=a[n-i];a[n-i]=x; // 将树根结点的值同当前区间内最后一个结点的值对换
Sift(a,n-I,0); // 筛a[0]结点,得到n-i个结点堆
}
}
假定待排序的n相元素存放于一维数组a中,则对a[i]进行筛选的算法为:
Void sift(ElemTYpe a[], int n)
{
ElemTYpe x = a[i]; // 待筛结点暂存于x中
Int j = 2*i+1; //a[j]是a[i]的左孩子
While (j <=n-1)
{// 当a[i]的左孩子不为空时循环
If (j<n-1 && a[j].stn < a[j+1].stn)
J++; //若右孩子的排序码较大,则把j修改为右孩子的下标
If (x.stn <a[j].stn)
{
A[i] = a[j]; // 将a[j]调到双亲位置上
I = j; j=2*i+1;
// 修改i和j的值,以便继续向下筛
}
Else
Break; // 若找到x的最终位置,终止
}
A[i] = x; // 被筛结点的值放入最终位置
}
交换排序(气泡排序)
Void BubbleSort(ElemType a[], int n)
{
ElemType x;
Int I,j,flag;
For(i=1;i<=n-1;i++)
{
Flag=0; //表示每一趟是否有交换,在每一趟前置为0,表示无交换台
For (j=n-1; j>=I; j--) // 进行第i趟排序
If(a[j].stn < a[j-1].stn)
{
X = a[j-1];a[j-1]=a[j];a[j]=x;
Flag = 1;
}
If (flag == 0)
Return ;
// 进行后一趟排序
}
}
交换排序(快带排序)
Void QuickSort(ElemType a[], int s, int t)
{ // 采用快速排序方法对数组a中a[s]-a[t]区间进行排序
//开始进行非递归调用时s和t的初值应分别为0和n-1
// 对当前排序区间进行一次划分
Int I = s, j = t+1; // 给I 和 j初值
ElemType x = a[s]; // 把基准元素的值暂存X中
Do
{
Do i++; whie(a[i].stn <x.stn); // 从前先后顺序查找一个要向后一区间交换的元素
Do j--; while(a[j].stn > x.stn); // 从后先前顺序查找一个要向前一区间交换的元素
If(i<j)
{
// 当条件成立时交换a[i] a[j]的值
ElemType temp = a[i];
A[i] = a[j]; a[j] = temp;
}
}While(i<j); // 条件成立时继续进行一次划分中的比较和交换
A[s] = a[j]; a[j] = x; // 交换a[s]和a[j]的值,得到前后两后两个子区间
// 在当前左区间内超过一个元素的情况下递归处理左区间
If (s<j-1) QuickSort(a,s,j-1);
// 在当前右区间内超过一个元素的情况下递归处理右区间
If (j+1 <) QuickSort(a,j+1,t)
}
单链表作为存储结构实现直接插入排序算法。
#define int KeyType //定义KeyType 为int型
typedef struct node{
KeyType key; //关键字域
OtherInfoType info; //其它信息域,
struct node * next; //链表中指针域
}RecNode; //记录结点类型
typedef RecNode * LinkList ; //单链表用LinkList表示
void InsertSort(LinkList head)
{//链式存储结构的直接插入排序算法,head是带头结点的单链表
RecNode *p,*q,*s;
if ((head->next)&&(head->next->next))//当表中含有结点数大于1
{
p=head->next->next;//p指向第二个节点
head->next=NULL;
q=head;//指向插入位置的前驱节点
while(p)&&(q->next)&&(p->key<q->next->key)
q=q->next;
if (p)
{s=p;p=p->next;// 将要插入结点摘下
s->next=q->next;//插入合适位置:q结点后
q->next=s;
}
}
}
#define int KeyType //定义KeyType 为int型
typedef struct node{
KeyType key; //关键字域
OtherInfoType info; //其它信息域,
struct node * next; //链表中指针域
}RecNode; //记录结点类型
typedef RecNode * LinkList ; //单链表用LinkList表示
void InsertSort(LinkList head)
{//链式存储结构的直接插入排序算法,head是带头结点的单链表
RecNode *p,*q,*s;
if ((head->next)&&(head->next->next))//当表中含有结点数大于1
{
p=head->next->next;//p指向第二个节点
head->next=NULL;
q=head;//指向插入位置的前驱节点
while(p)&&(q->next)&&(p->key<q->next->key)
q=q->next;
if (p)
{s=p;p=p->next;// 将要插入结点摘下
s->next=q->next;//插入合适位置:q结点后
q->next=s;
}
}
}
二分法查找
1
、二分查找
(Binary Search)
二分查找又称折半查找,它是一种效率较高的查找方法。
二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
二分查找又称折半查找,它是一种效率较高的查找方法。
二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
2
、二分查找的基本思想
二分查找的基本思想是:(设 R[low..high] 是当前的查找区间)
( 1 )首先确定该区间的中点位置:
( 2 )然后将待查的 K 值与 R[mid].key 比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找,具体方法如下:
① 若 R[mid].key>K ,则由表的有序性可知 R[mid..n].keys 均大于 K ,因此若表中存在关键字等于 K 的结点,则该结点必定是在位置 mid 左边的子表 R[1..mid-1] 中,故新的查找区间是左子表 R[1..mid-1] 。
② 类似地,若 R[mid].key<K ,则要查找的 K 必在 mid 的右子表 R[mid+1..n] 中,即新的查找区间是右子表 R[mid+1..n] 。下一次查找是针对新的查找区间进行的。
因此,从初始的查找区间 R[1..n] 开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为 K 的结点,或者直至当前的查找区间为空 ( 即查找失败 ) 时为止。
二分查找的基本思想是:(设 R[low..high] 是当前的查找区间)
( 1 )首先确定该区间的中点位置:
( 2 )然后将待查的 K 值与 R[mid].key 比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找,具体方法如下:
① 若 R[mid].key>K ,则由表的有序性可知 R[mid..n].keys 均大于 K ,因此若表中存在关键字等于 K 的结点,则该结点必定是在位置 mid 左边的子表 R[1..mid-1] 中,故新的查找区间是左子表 R[1..mid-1] 。
② 类似地,若 R[mid].key<K ,则要查找的 K 必在 mid 的右子表 R[mid+1..n] 中,即新的查找区间是右子表 R[mid+1..n] 。下一次查找是针对新的查找区间进行的。
因此,从初始的查找区间 R[1..n] 开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为 K 的结点,或者直至当前的查找区间为空 ( 即查找失败 ) 时为止。
3
、二分查找算法
int BinSearch(SeqList R , KeyType K)
{ // 在有序表 R[1..n] 中进行二分查找,成功时返回结点的位置,失败时返回零
int low=1 , high=n , mid ; // 置当前查找区间上、下界的初值
while(low<=high){ // 当前查找区间 R[low..high] 非空
mid=(low+high)/2 ;
if(R[mid].key==K) return mid ; // 查找成功返回
if(R[mid].kdy>K)
high=mid-1; // 继续在 R[low..mid-1] 中查找
else
low=mid+1 ; // 继续在 R[mid+1..high] 中查找
}
return 0 ; // 当 low>high 时表示查找区间为空,查找失败
} //BinSeareh
二分查找算法亦很容易给出其递归程序【参见练习】
int BinSearch(SeqList R , KeyType K)
{ // 在有序表 R[1..n] 中进行二分查找,成功时返回结点的位置,失败时返回零
int low=1 , high=n , mid ; // 置当前查找区间上、下界的初值
while(low<=high){ // 当前查找区间 R[low..high] 非空
mid=(low+high)/2 ;
if(R[mid].key==K) return mid ; // 查找成功返回
if(R[mid].kdy>K)
high=mid-1; // 继续在 R[low..mid-1] 中查找
else
low=mid+1 ; // 继续在 R[mid+1..high] 中查找
}
return 0 ; // 当 low>high 时表示查找区间为空,查找失败
} //BinSeareh
二分查找算法亦很容易给出其递归程序【参见练习】
4
、
二分查找算法的执行过程
设算法的输入实例中有序的关键字序列为
(05 , 13 , 19 , 21 , 37 , 56 , 64 , 75 , 80 , 88 , 92)
要查找的关键字 K 分别是 21 和 85 。具体查找过程【参见动画演示】
设算法的输入实例中有序的关键字序列为
(05 , 13 , 19 , 21 , 37 , 56 , 64 , 75 , 80 , 88 , 92)
要查找的关键字 K 分别是 21 和 85 。具体查找过程【参见动画演示】
5
、二分查找判定树
二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树 (Decision Tree) 或比较树 (Comparison Tree) 。
注意:
判定树的形态只与表结点个数 n 相关,而与输入实例中 R[1..n].keys 的取值无关。
【例】具有 11 个结点的有序表可用下图所示的判定树来表示。
二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树 (Decision Tree) 或比较树 (Comparison Tree) 。
注意:
判定树的形态只与表结点个数 n 相关,而与输入实例中 R[1..n].keys 的取值无关。
【例】具有 11 个结点的有序表可用下图所示的判定树来表示。
(
1
)二分查找判定树的组成
① 圆结点即树中的内部结点。树中圆结点内的数字表示该结点在有序表中的位置。
② 外部结点:圆结点中的所有空指针均用一个虚拟的方形结点来取代,即外部结点。
③ 树中某结点 i 与其左 ( 右 ) 孩子连接的左 ( 右 ) 分支上的标记 "<" 、 "(" 、 ">" 、 ")" 表示:当待查关键字 K<R[i].key(K>R[i].key) 时,应走左 ( 右 ) 分支到达 i 的左 ( 右 ) 孩子,将该孩子的关键字进一步和 K 比较。若相等,则查找过程结束返回,否则继续将 K 与树中更下一层的结点比较。
① 圆结点即树中的内部结点。树中圆结点内的数字表示该结点在有序表中的位置。
② 外部结点:圆结点中的所有空指针均用一个虚拟的方形结点来取代,即外部结点。
③ 树中某结点 i 与其左 ( 右 ) 孩子连接的左 ( 右 ) 分支上的标记 "<" 、 "(" 、 ">" 、 ")" 表示:当待查关键字 K<R[i].key(K>R[i].key) 时,应走左 ( 右 ) 分支到达 i 的左 ( 右 ) 孩子,将该孩子的关键字进一步和 K 比较。若相等,则查找过程结束返回,否则继续将 K 与树中更下一层的结点比较。
(
2
)二分查找判定树的查找
二分查找就是将给定值 K 与二分查找判定树的根结点的关键字进行比较。若相等,成功。否则若小于根结点的关键字,到左子树中查找。若大于根结点的关键字,则到右子树中查找。
【例】对于有 11 个结点的表,若查找的结点是表中第 6 个结点,则只需进行一次比较;若查找的结点是表中第 3 或第 9 个结点,则需进行二次比较;找第 1 , 4 , 7 , 10 个结点需要比较三次;找到第 2 , 5 , 8 , 11 个结点需要比较四次。
由此可见,成功的二分查找过程恰好是走了一条从判定树的根到被查结点的路径,经历比较的关键字次数恰为该结点在树中的层数。若查找失败,则其比较过程是经历了一条从判定树根到某个外部结点的路径,所需的关键字比较次数是该路径上内部结点的总数。
【例】待查表的关键字序列为: (05 , 13 , 19 , 21 , 37 , 56 , 64 , 75 , 80 , 88 , 92) ,若要查找 K=85 的记录,所经过的内部结点为 6 、 9 、 10 ,最后到达方形结点 "9-10" ,其比较次数为 3 。
实际上方形结点中 "i-i+1" 的含意为被查找值 K 是介于 R[i].key 和 R[i+1].key 之间的,即 R[i].key<K<R[i+1].key 。
二分查找就是将给定值 K 与二分查找判定树的根结点的关键字进行比较。若相等,成功。否则若小于根结点的关键字,到左子树中查找。若大于根结点的关键字,则到右子树中查找。
【例】对于有 11 个结点的表,若查找的结点是表中第 6 个结点,则只需进行一次比较;若查找的结点是表中第 3 或第 9 个结点,则需进行二次比较;找第 1 , 4 , 7 , 10 个结点需要比较三次;找到第 2 , 5 , 8 , 11 个结点需要比较四次。
由此可见,成功的二分查找过程恰好是走了一条从判定树的根到被查结点的路径,经历比较的关键字次数恰为该结点在树中的层数。若查找失败,则其比较过程是经历了一条从判定树根到某个外部结点的路径,所需的关键字比较次数是该路径上内部结点的总数。
【例】待查表的关键字序列为: (05 , 13 , 19 , 21 , 37 , 56 , 64 , 75 , 80 , 88 , 92) ,若要查找 K=85 的记录,所经过的内部结点为 6 、 9 、 10 ,最后到达方形结点 "9-10" ,其比较次数为 3 。
实际上方形结点中 "i-i+1" 的含意为被查找值 K 是介于 R[i].key 和 R[i+1].key 之间的,即 R[i].key<K<R[i+1].key 。
(
3
)二分查找的平均查找长度
设内部结点的总数为 n=2h-1 ,则判定树是深度为 h=lg(n+1) 的满二叉树 ( 深度 h 不计外部结点 ) 。树中第 k 层上的结点个数为 2k-1 ,查找它们所需的比较次数是 k 。因此在等概率假设下,二分查找成功时的平均查找长度为:
ASLbn≈lg(n+1)-1
二分查找在查找失败时所需比较的关键字个数不超过判定树的深度,在最坏情况下查找成功的比较次数也不超过判定树的深度。即为:
二分查找的最坏性能和平均性能相当接近。
设内部结点的总数为 n=2h-1 ,则判定树是深度为 h=lg(n+1) 的满二叉树 ( 深度 h 不计外部结点 ) 。树中第 k 层上的结点个数为 2k-1 ,查找它们所需的比较次数是 k 。因此在等概率假设下,二分查找成功时的平均查找长度为:
ASLbn≈lg(n+1)-1
二分查找在查找失败时所需比较的关键字个数不超过判定树的深度,在最坏情况下查找成功的比较次数也不超过判定树的深度。即为:
二分查找的最坏性能和平均性能相当接近。
6
、二分查找的优点和缺点
虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费 O(nlgn) 的时间。
二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。
对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。
虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费 O(nlgn) 的时间。
二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。
对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。
二分法排序
#include <stdlib.h>
#include <stdio.h>
void TwoInsertSort(int array[],int n)
{
int left,right,num;
int middle,j,i;
for(i = 1;i < n;i++)
{
left = 0;// 准备
right = i-1;
num = array[i];
while( right >= left)// 二分法 查找插入位置
{
middle = ( left + right ) / 2; // 指向已排序好的中间位置
if( num < array[middle] )// 即将插入的元素应当在在左区间
right = middle-1;
else // 即将插入的元素应当在右区间
left = middle+1;
}
for( j = i-1;j >= left;j-- )// 后移排序码大于 R[i] 的记录
array[j+1] = array[j];
array[left] = num;// 插入
}
}
#include <stdio.h>
void TwoInsertSort(int array[],int n)
{
int left,right,num;
int middle,j,i;
for(i = 1;i < n;i++)
{
left = 0;// 准备
right = i-1;
num = array[i];
while( right >= left)// 二分法 查找插入位置
{
middle = ( left + right ) / 2; // 指向已排序好的中间位置
if( num < array[middle] )// 即将插入的元素应当在在左区间
right = middle-1;
else // 即将插入的元素应当在右区间
left = middle+1;
}
for( j = i-1;j >= left;j-- )// 后移排序码大于 R[i] 的记录
array[j+1] = array[j];
array[left] = num;// 插入
}
}
int rcmp( const int *a, const int *b)
{
return (*a-*b);
}
void main()
{
int array[50];
int i;
printf("The original array is :/n");
for( i=0; i<50; i++ )// 数组初始化并显示
{
array[i] = 50-i;
printf("array[%d]:%d/n", i, array[i]);
}
TwoInsertSort(array,sizeof(array)/sizeof(int));// 二分法 排序
printf("/nAfter sorted :/n");
for( i=0; i<50; i++ )
printf("array[%d]:%d/n", i, array[i]);
{
return (*a-*b);
}
void main()
{
int array[50];
int i;
printf("The original array is :/n");
for( i=0; i<50; i++ )// 数组初始化并显示
{
array[i] = 50-i;
printf("array[%d]:%d/n", i, array[i]);
}
TwoInsertSort(array,sizeof(array)/sizeof(int));// 二分法 排序
printf("/nAfter sorted :/n");
for( i=0; i<50; i++ )
printf("array[%d]:%d/n", i, array[i]);
//
库函数
bsearch
用
二分法
查找一个有序数组中的一个特定数,并返回该数的地址
a = (int *)bsearch(&b, numarray, sizeof(numarray)/sizeof(numarray[0]), sizeof(int),rcmp);
}
}