1. 排序有几种方法
一. 冒泡排序
冒泡排序是是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把它们交换过来。遍历数列的工作是重复的进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端
1.冒泡排序算法的运作如下:
(1)比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个
(2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素还是最大的数
(3)针对所有的元素重复以上的步骤,除了最后一个
二. 选择排序
选择排序是一种简单直观的排序算法。他的工作原理如下:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置(末尾位置),然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,他们当中至少有一个将被移到最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动 元素的排序方法中,选择排序属于非常好的一种
三. 插入排序
插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在从后向前扫描的过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间
四. 快速排序
快速排序,又称划分交换排序。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都要小,然后再按此方法对两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
五 希尔排序过程
希尔排序是插入排序的一种,也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
六. 归并排序
归并排序是采用分治法(把复杂问题分解为相对简单的子问题,分别求解,最后通过组合起子问题的解的方式得到原问题的解)的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组
将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,水小九先取谁,取了后相应的指针就往后移一位。然后比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可
2. 排序算法通常使用什么数据结构和存储结构为什么
排序算法需要按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作;首先要求其具有一定的稳定性,即当两个相同的元素同时出现于某个序列之中,则经过一定的排序算法之后,两者在排序前后的相对位置不发生变化。
换言之,即便是两个完全相同的元素,它们在排序过程中也是各有区别的,不允许混淆不清。
(2)排序算法分布进行扩展阅读:
快速排序的基本思想:通过一趟排序算法把所需要排序的序列的元素分割成两大块,其中,一部分的元素都要小于或等于另外一部分的序列元素。
然后仍根据该种方法对划分后的这两块序列的元素分别再次实行快速排序算法,排序实现的整个过程可以是递归的来进行调用,最终能够实现将所需排序的无序序列元素变为一个有序的序列。
3. 内部排序算法比较
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序 一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序 如快速、堆和归并排序;
(3)O(n1+£)阶排序 £是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序 如桶、箱和基数排序。
各种排序方法比较 简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。 影响排序效果的因素 因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短; 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。 若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。
(4)在基于比较的排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程。 当文件的n个关键字随机分布时,任何借助于"比较"的排序算法,至少需要O(nlgn)的时间。
网络文库里也有说明,详见:http://wenku..com/view/51f3b202de80d4d8d15a4fa6.html
下面是一段测试程序:
用系统计时器算时间复杂度。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#define LIST_INIT_SIZE 50000
int bj1,yd1,n;
clock_t start_t,end_t;
typedef struct
{
int key;
}ElemType;
typedef struct
{
ElemType *elem;
int length;
}SqList;
void addlist(SqList &L)
{
int i;
a: printf("请输入你要输入的个数:");
scanf("%d",&n);
if(n>50000)
{
printf("超出范围重新输入!!!\n");
goto a;
}
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem)exit(0);
L.length=0;
for(i=1;i<n+1;i++)
{
b: L.elem[i].key=rand();
if(L.elem[i].key>30000)goto b;
++L.length;
}
}
void SelectSort(SqList &L)//选择
{
start_t=clock();
int i,j,k,bj=0,yd=0;
for(i=1;i<L.length;i++)
{
k=i;
for(j=i+1;j<L.length;j++)
{
bj++;
if(L.elem[j].key<L.elem[k].key)k=j;
}
if(i!=k)
{
L.elem[0].key=L.elem[i].key;
L.elem[i].key=L.elem[k].key;
L.elem[k].key=L.elem[0].key;
yd+=3;
}
}
end_t=clock();
printf("比较次数为 %d移动次数为 %d\n",bj,yd);
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void qipao(SqList &L)//起泡
{
start_t=clock();
int i=1,j,bj=0,yd=0;
while(i<L.length)
{
for(j=1;j<L.length;j++)
{
bj++;
if(L.elem[j].key>L.elem[j+1].key)
{
L.elem[0].key=L.elem[j].key;
L.elem[j].key=L.elem[j+1].key;
L.elem[j+1].key=L.elem[0].key;
yd+=3;
}
}
i++;
}
end_t=clock();
printf("比较次数为 %d移动次数为 %d\n",bj,yd);
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void InsertSort(SqList &L)//直接插入
{
start_t=clock();
int i,j,yd=0,bj=0;
for(i=2;i<=L.length;i++)
{
if(L.elem[i].key<L.elem[i-1].key)
{
L.elem[0].key=L.elem[i].key;
yd++;
j=i-1;
bj++;
while(L.elem[0].key<L.elem[j].key)
{
L.elem[j+1].key=L.elem[j].key;
j--;
yd++;
bj++;
}
L.elem[j+1].key=L.elem[0].key;
yd++;
}
}
end_t=clock();
printf("比较次数为 %d移动次数为 %d\n",bj,yd);
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void xier(SqList &L)//希尔
{
start_t=clock();
int i,d=L.length/2,j,w=0,k,yd=0,bj=0;
while(w<d)
{
w=1;
for(i=w;i<L.length;i=i+d)
{
k=i;
for(j=i+d;j<L.length;j=j+d)
{
if(L.elem[i].key>L.elem[j].key)
{
k=j;
bj++;
}
}
if(i!=k)
{
L.elem[0].key=L.elem[i].key;
L.elem[i].key=L.elem[k].key;
L.elem[k].key=L.elem[0].key;
yd+=3;
}
w++;
}
d=d/2;
w=1;
}
end_t=clock();
printf("比较次数为 %d移动次数为 %d\n",bj,yd);
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void BeforeSort()
{
yd1=0,bj1=0;
}
void display(int m,int n)
{
printf("比较次数为 %d移动次数为 %d\n",m,n);
}
int Partition(SqList &L,int low,int high)//快速排序
{
int pivotkey;
L.elem[0]=L.elem[low];
yd1++;
pivotkey=L.elem[low].key;
while (low<high)
{
yd1++;
while(low<high&&L.elem[high].key>=pivotkey)
--high;
L.elem[low]=L.elem[high];
bj1++;
yd1++;
while (low<high&&L.elem[low].key<=pivotkey)
++low;
L.elem[high]=L.elem[low];
bj1++;
yd1++;
}
L.elem[low]=L.elem[0];
yd1++;
return low;
}
void QSort(SqList &L,int low,int high)
{
int pivotloc;
if(low<high)
{
pivotloc=Partition(L,low,high);
QSort(L,low,pivotloc-1);
QSort(L,pivotloc+1,high);
}
}
void QuickSort(SqList &L)
{
start_t=clock();
BeforeSort();
QSort(L,1,L.length);
display(yd1,bj1);
end_t=clock();
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void Merge(ElemType R[],ElemType R1[],int low,int m,int high)//归并
{
int i=low, j=m+1, k=low;
while(i<=m&&j<=high)
{
if(R[i].key<=R[j].key)
{
bj1++;
R1[k]=R[i];
yd1++;
i++;
k++;
}
else
{
bj1++;
R1[k]=R[j];
yd1++;
j++;
k++;
}
}
while(i<=m)
{
R1[k]=R[i];
yd1++;
i++;
k++;
}
while(j<=high)
{
R1[k]=R[j];
yd1++;
j++;
k++;
}
}
void MergePass(ElemType R[],ElemType R1[],int length, int n)
{
int i=0,j;
while(i+2*length-1<n)
{
Merge(R,R1,i,i+length-1,i+2*length-1);
i=i+2*length;
}
if(i+length-1<n-1)
Merge(R,R1,i,i+length-1,n-1);
else
for(j=i;j<n;j++)
R1[j]=R[j];
}
void MSort(ElemType R[],ElemType R1[],int n)
{
int length=1;
while (length<n)
{
MergePass(R,R1,length,n);
length=2*length;
MergePass(R1,R,length,n);
length=2*length;
}
}
void MergeSort(SqList &L)
{
start_t=clock();
BeforeSort();
MSort(L.elem,L.elem,L.length);
display(yd1,bj1);
end_t=clock();
printf("排序用时为 %f\n",float(end_t-start_t)/CLK_TCK);
}
void main()
{
SqList L;
addlist(L);
printf("起泡排序: \n");
qipao(L);
addlist(L);
printf("直插排序: \n");
InsertSort(L);
addlist(L);
printf("选择排序: \n");
SelectSort(L);
addlist(L);
printf("希尔排序: \n");
xier(L);
addlist(L);
printf("快速排续: \n");
QuickSort(L);
addlist(L);
printf("归并排序: \n");
MergeSort(L);
}
4. 请问 排序算法 可以分为哪几大类
排序可分为:
1,稳定排序与不稳定排序
2,内排序和外排序
内部排序可分为:直接插入排序、冒泡排序、简单选择排序、希尔排序、快速排序、堆排序、归并排序、基数排序。
5. 常用的数据排序算法有哪些,各有什么特点举例结合一种排序算法并应用数组进行数据排序。
排序简介
排序是数据处理中经常使用的一种重要运算,在计算机及其应用系统中,花费在排序上的时间在系统运行时间中占有很大比重;并且排序本身对推动算法分析的发展也起很大作用。目前已有上百种排序方法,但尚未有一个最理想的尽如人意的方法,本章介绍常用的如下排序方法,并对它们进行分析和比较。
1、插入排序(直接插入排序、折半插入排序、希尔排序);
2、交换排序(起泡排序、快速排序);
3、选择排序(直接选择排序、堆排序);
4、归并排序;
5、基数排序;
学习重点
1、掌握排序的基本概念和各种排序方法的特点,并能加以灵活应用;
2、掌握插入排序(直接插入排序、折半插入排序、希尔排序)、交换排序(起泡排序、快速排序)、选择排序(直接选择排序、堆排序)、二路归并排序的方法及其性能分析方法;
3、了解基数排序方法及其性能分析方法。
排序(sort)或分类
所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。其确切定义如下:
输入:n个记录R1,R2,…,Rn,其相应的关键字分别为K1,K2,…,Kn。
输出:Ril,Ri2,…,Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。
1.被排序对象--文件
被排序的对象--文件由一组记录组成。
记录则由若干个数据项(或域)组成。其中有一项可用来标识一个记录,称为关键字项。该数据项的值称为关键字(Key)。
注意:
在不易产生混淆时,将关键字项简称为关键字。
2.排序运算的依据--关键字
用来作排序运算依据的关键字,可以是数字类型,也可以是字符类型。
关键字的选取应根据问题的要求而定。
【例】在高考成绩统计中将每个考生作为一个记录。每条记录包含准考证号、姓名、各科的分数和总分数等项内容。若要惟一地标识一个考生的记录,则必须用"准考证号"作为关键字。若要按照考生的总分数排名次,则需用"总分数"作为关键字。
排序的稳定性
当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一。
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序方法是不稳定的。
注意:
排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。
排序方法的分类
1.按是否涉及数据的内、外存交换分
在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序(简称内排序);反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。
注意:
① 内排序适用于记录个数不很多的小文件
② 外排序则适用于记录个数太多,不能一次将其全部记录放人内存的大文件。
2.按策略划分内部排序方法
可以分为五类:插入排序、选择排序、交换排序、归并排序和分配排序。
排序算法分析
1.排序算法的基本操作
大多数排序算法都有两个基本的操作:
(1) 比较两个关键字的大小;
(2) 改变指向记录的指针或移动记录本身。
注意:
第(2)种基本操作的实现依赖于待排序记录的存储方式。
2.待排文件的常用存储方式
(1) 以顺序表(或直接用向量)作为存储结构
排序过程:对记录本身进行物理重排(即通过关键字之间的比较判定,将记录移到合适的位置)
(2) 以链表作为存储结构
排序过程:无须移动记录,仅需修改指针。通常将这类排序称为链表(或链式)排序;
(3) 用顺序的方式存储待排序的记录,但同时建立一个辅助表(如包括关键字和指向记录位置的指针组成的索引表)
排序过程:只需对辅助表的表目进行物理重排(即只移动辅助表的表目,而不移动记录本身)。适用于难于在链表上实现,仍需避免排序过程中移动记录的排序方法。
3.排序算法性能评价
(1) 评价排序算法好坏的标准
评价排序算法好坏的标准主要有两条:
① 执行时间和所需的辅助空间
② 算法本身的复杂程度
(2) 排序算法的空间复杂度
若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1),则称之为就地排序(In-PlaceSou)。
非就地排序一般要求的辅助空间为O(n)。
(3) 排序算法的时间开销
大多数排序算法的时间开销主要是关键字之间的比较和记录的移动。有的排序算法其执行时间不仅依赖于问题的规模,还取决于输入实例中数据的状态。
文件的顺序存储结构表示
#define n l00 //假设的文件长度,即待排序的记录数目
typedef int KeyType; //假设的关键字类型
typedef struct{ //记录类型
KeyType key; //关键字项
InfoType otherinfo;//其它数据项,类型InfoType依赖于具体应用而定义
}RecType;
typedef RecType SeqList[n+1];//SeqList为顺序表类型,表中第0个单元一般用作哨兵
注意:
若关键字类型没有比较算符,则可事先定义宏或函数来表示比较运算。
【例】关键字为字符串时,可定义宏"#define LT(a,b)(Stromp((a),(b))<0)"。那么算法中"a<b"可用"LT(a,b)"取代。若使用C++,则定义重载的算符"<"更为方便。
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如桶、箱和基数排序。
各种排序方法比较
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。
4)在基于比较的排序方法中,每次比较两个关键字的大小之后,仅仅出现两种可能的转移,因此可以用一棵二叉树来描述比较判定过程。
当文件的n个关键字随机分布时,任何借助于"比较"的排序算法,至少需要O(nlgn)的时间。
箱排序和基数排序只需一步就会引起m种可能的转移,即把一个记录装入m个箱子之一,因此在一般情况下,箱排序和基数排序可能在O(n)时间内完成对n个记录的排序。但是,箱排序和基数排序只适用于像字符串和整数这类有明显结构特征的关键字,而当关键字的取值范围属于某个无穷集合(例如实数型关键字)时,无法使用箱排序和基数排序,这时只有借助于"比较"的方法来排序。
若n很大,记录的关键字位数较少且可以分解时,采用基数排序较好。虽然桶排序对关键字的结构无要求,但它也只有在关键字是随机分布时才能使平均时间达到线性阶,否则为平方阶。同时要注意,箱、桶、基数这三种分配排序均假定了关键字若为数字时,则其值均是非负的,否则将其映射到箱(桶)号时,又要增加相应的时间。
(5)有的语言(如Fortran,Cobol或Basic等)没有提供指针及递归,导致实现归并、快速(它们用递归实现较简单)和基数(使用了指针)等排序算法变得复杂。此时可考虑用其它排序。
(6)本章给出的排序算法,输人数据均是存储在一个向量中。当记录的规模较大时,为避免耗费大量的时间去移动记录,可以用链表作为存储结构。譬如插入排序、归并排序、基数排序都易于在链表上实现,使之减少记录的移动次数。但有的排序方法,如快速排序和堆排序,在链表上却难于实现,在这种情况下,可以提取关键字建立索引表,然后对索引表进行排序。然而更为简单的方法是:引人一个整型向量t作为辅助表,排序前令t[i]=i(0≤i<n),若排序算法中要求交换R[i]和R[j],则只需交换t[i]和t[j]即可;排序结束后,向量t就指示了记录之间的顺序关系:
R[t[0]].key≤R[t[1]].key≤…≤R[t[n-1]].key
若要求最终结果是:
R[0].key≤R[1].key≤…≤R[n-1].key
则可以在排序结束后,再按辅助表所规定的次序重排各记录,完成这种重排的时间是O(n)。
6. 顺序表的排序算法
给你举一些比较常用的排序法:
交换排序法
冒泡排序 | 鸡尾酒排序 | 奇偶排序 | 梳排序 | 侏儒排序 | 快速排序 |臭皮匠算法 | Bogo排序
选择排序法
选择排序 | 堆排序 | Smooth排序 | 笛卡尔树排序 | 锦标赛排序 | 循环排序
插入排序法
插入排序 | 希尔排序 | 二叉查找树排序 | 图书馆排序 | Patience排序
归并排序法
归并排序 | 多相归并排序 | Strand排序
分布排序法
美国旗帜排序 | 珠排序 | 桶排序 | 爆炸排序 | 计数排序 | 鸽巢排序 | 相邻图排序 | 基数排序 | 闪电排序
混合排序法
Tim排序 | 内省排序 | Spread排序 | 反移排序 | J排序
其他
双调排序器 | Batcher归并网络 | 两两排序网络
7. 常用的排序算法有哪些
排序另一种分法
外排序:需要在内外存之间多次交换数据才能进行
内排序:
归并排序
冒泡排序
快速排序
简单选择排序
堆排序
直接插入排序
希尔排序
插入类排序
选择类排序
交换类排序
归并类排序
8. C 排序问题
排 序:
程序员可以使用的基本排序算法有5种:
·插入排序(insertionsort.)
·交换排序(exchangesOrt)
·选择排序(selectionsort)
·归并排序(mergesort)
·分布排序(distributionsort)
为了形象地解释每种排序算法是怎样工作的,让我们来看一看怎样用这些方法对桌上一付乱序的牌进行排序。牌既要按花色排序(依次为梅花、方块、红桃和黑心),还要按点数排序(从2到A)。
插入排序的过程为:从一堆牌的上面开始拿牌,每次拿一张牌,按排序原则把牌放到手中正确的位置。桌上的牌拿完后,手中的牌也就排好序了。
交换排序的过程为:
(1)先拿两张牌放到手中。如果左边的牌要排在右边的牌的后面,就交换这两张牌的位置。
(2)然后拿下一张牌,并比较最右边两张牌,如果有必要就交换这两张牌的位置。
(3)重复第(2)步,直到把所有的牌都拿到手中。
(4)如果不再需要交换手中任何两张牌的位置,就说明牌已经排好序了;否则,把手中的牌放到桌上,重复(1)至(4)步,直到手中的牌排好序。
选择排序的过程为:在桌上的牌中找出最小的一张牌,拿在手中;重复这种操作,直到把所有牌都拿在手中。
归并排序的过程为:把桌上的牌分为52堆,每堆为一张牌。因为每堆牌都是有序的(记住,此时每堆中只有一张牌),所以如果把相邻的两堆牌合并为一堆,并对每堆牌进行排序,就可以得到26堆已排好序的牌,此时每一堆中有两张牌。重复这种合并操作,就可以依次得到13堆牌(每一堆中有4张牌),7堆牌(有6堆是8张牌,还有一堆是4张牌),最后将得到52张的一堆牌。
分布排序(也被称作radix sort,即基数排序)的过程为:先将牌按点数分成13堆,然后将这13堆牌按点数顺序叠在一起;再将牌按花色分成4堆,然后将这4堆牌按花色顺序叠在一起,牌就排好序了。
在选用排序算法时,你还需要了解以下几个术语:
(1)自然的(natural)
如果某种排序算法对有序的数据排序速度较快(工作量变小),对无序的数据排序速度却较慢(工作变量大),我们就称这种排序算法是自然的。如果数据已接近有序,就需要考虑选用自然的排序算法。
(2)稳定的(stable)
如果某种排序算法能保持它认为相等的数据的前后顺序,我们就称这种排序算法是稳定的。
例如,现有以下名单:
Mary Jones
Mary Smith
Tom Jones
Susie Queue
如果用稳定的排序算法按姓对上述名单进行排序,那么在排好序后"Mary Jones”和"Tom Jones”将保持原来的Jr顺序,因为它们的姓是相同的。
稳定的排序算法可按主、次关键字对数据进行排序,例如按姓和名排序(换句话说,主要按姓排序,但对姓相同的数据还要按名排序)。在具体实现时,就是先按次关键字排序,再按主关键字排序。
(3)内部排序(internal sort)和外部排序(external sort)
待排数据全部在内存中的排序方法被称为内部排序,待排数据在磁盘、磁带和其它外存中的排序方法被称为外部排序。
查 找:
和排序算法一样,查找(searching)算法也是计算机科学中研究得最多的问题之一。查找算法和排序算法是有联系的,因为许多查找算法依赖于要查找的数据集的有序程度。基本的查找算法有以下4种:
·顺序查找(sequential searching)。
·比较查找(comparison searching)
·基数查找(radix searching)
·哈希查找(hashing)
下面仍然以一付乱序的牌为例来描述这些算法的工作过程。
顺序查找的过程为:从第一张开始查看每一张牌,直到找到要找的牌。
比较查找(也被称作binarysearching,即折半查找)要求牌已经排好序,其过程为:任意抽一张牌,如果这张牌正是要找的牌,则查找过程结束。如果抽出的这张牌比要找的牌大,则在它前面的牌中重复查找操作;反之,则在它后面的牌中重复查找操作,直到找到要找的牌。
基数查找的过程为:先将牌按点数分成13堆,或者按花色分成4堆。然后找出与要找的牌的点数或花色相同的那一堆牌,再在这堆牌中用任意一种查找算法找到要找的牌。
哈希查找的过程为:
(1)在桌面上留出可以放若干堆牌的空间,并构造一个函数,使其能根据点数和花色将牌映射到特定的堆中(这个函数被称为hashfunction,即哈希函数)。
(2)根据哈希函数将牌分成若干堆。
(3)根据哈希函数找到要找的牌所在的堆,然后在这一堆牌中找到要找的牌。
例如,可以构造这样一个哈希函数:
pile=rank+suit
其中,rank是表示牌的点数的一个数值;suit是表示牌的花色的一个数值;pile表示堆值,它将决定一张牌归入到哪一堆中。如果用1,2,……,13分别表示A,2,…….K,用0,1,2和3分别表示梅花、方块、红桃和黑桃,则pile的值将为1,2,……,16,这样就可以把一付牌分成16堆。
哈希查找虽然看上去有些离谱,但它确实是一种非常实用的查找算法。各种各样的程序,从压缩程序(如Stacker)到磁盘高速缓存程序(如SmartDrive),几乎都通过这种方法来提高查找速度,
排序或查找的性能:
有关排序和查找的一个主要问题就是速度。这个问题经常被人们忽视,因为与程序的其余部分相比,排序或查找所花费的时间几乎可以被忽略。然而,对大多数排序或查找应用来说,你不必一开始就花很多精力去编制一段算法程序,而应该先在现成的算法中选用一种最简单的(见3.1和3.4),当你发现所用的算法使程序运行很慢时,再换用一种更好的算法(请参见下文中的介绍)。
下面介绍一种判断排序或查找算法的速度的方法。
首先,引入一个算法的复杂度的概念,它指的是在各种情况(最好的、最差的和平均的)下排序或查找需要完成的操作次数,通过它可以比较不同算法的性能。
算法的复杂度与排序或查找所针对的数据集的数据量有关,因此,引入一个基于数据集数据量的表达式来表示算法的复杂度。
最快的算法的复杂度O(1),它表示算法的操作次数与数据量无关。复杂度O(N)(N表示数据集的数据量)表示算法的操作次数与数据量直接相关。复杂度O(logN)介于上述两者之间,它表示算法的操作次数与数据量的对数有关。复杂度为O(NlogN)(N乘以logN)的算法比复杂度为O(N)的算法要慢,而复杂度为O(N2)的算法更慢。
注意:如果两种算法的复杂度都是O(logN),那么logN的基数较大的算法的速度要快些,在本章的例子中,logN的基数均为10
9. 排序算法有多少种
排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。
排序就是把集合中的元素按照一定的次序排序在一起。一般来说有升序排列和降序排列2种排序,在算法中有8中基本排序:
(1)冒泡排序;
(2)选择排序;
(3)插入排序;
(4)希尔排序;
(5)归并排序;
(6)快速排序;
(7)基数排序;
(8)堆排序;
(9)计数排序;
(10)桶排序。
插入排序
插入排序算法是基于某序列已经有序排列的情况下,通过一次插入一个元素的方式按照原有排序方式增加元素。这种比较是从该有序序列的最末端开始执行,即要插入序列中的元素最先和有序序列中最大的元素比较,若其大于该最大元素,则可直接插入最大元素的后面即可,否则再向前一位比较查找直至找到应该插入的位置为止。插入排序的基本思想是,每次将1个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,寻找最适当的位置,直至全部记录插入完毕。执行过程中,若遇到和插入元素相等的位置,则将要插人的元素放在该相等元素的后面,因此插入该元素后并未改变原序列的前后顺序。我们认为插入排序也是一种稳定的排序方法。插入排序分直接插入排序、折半插入排序和希尔排序3类。
冒泡排序
冒泡排序算法是把较小的元素往前调或者把较大的元素往后调。这种方法主要是通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。冒泡排序的基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,重复进行上述计算,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程进行第2次、第3次排序,直至整个序列有序为止。排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。
选择排序
选择排序算法的基本思路是为每一个位置选择当前最小的元素。选择排序的基本思想是,基于直接选择排序和堆排序这两种基本的简单排序方法。首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可;以此类推,重复进行“最小元素”的选择,直至完成第(n-1)个位置的元素选择,则第n个位置就只剩唯一的最大元素,此时不需再进行选择。使用这种排序时,要注意其中一个不同于冒泡法的细节。举例说明:序列58539.我们知道第一遍选择第1个元素“5”会和元素“3”交换,那么原序列中的两个相同元素“5”之间的前后相对顺序就发生了改变。因此,我们说选择排序不是稳定的排序算法,它在计算过程中会破坏稳定性。
快速排序
快速排序的基本思想是:通过一趟排序算法把所需要排序的序列的元素分割成两大块,其中,一部分的元素都要小于或等于另外一部分的序列元素,然后仍根据该种方法对划分后的这两块序列的元素分别再次实行快速排序算法,排序实现的整个过程可以是递归的来进行调用,最终能够实现将所需排序的无序序列元素变为一个有序的序列。
归并排序
归并排序算法就是把序列递归划分成为一个个短序列,以其中只有1个元素的直接序列或者只有2个元素的序列作为短序列的递归出口,再将全部有序的短序列按照一定的规则进行排序为长序列。归并排序融合了分治策略,即将含有n个记录的初始序列中的每个记录均视为长度为1的子序列,再将这n个子序列两两合并得到n/2个长度为2(当凡为奇数时会出现长度为l的情况)的有序子序列;将上述步骤重复操作,直至得到1个长度为n的有序长序列。需要注意的是,在进行元素比较和交换时,若两个元素大小相等则不必刻意交换位置,因此该算法不会破坏序列的稳定性,即归并排序也是稳定的排序算法。
10. 各种排序算法实现和比较
1、 堆排序定义
n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):
(1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ )
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。
2、大根堆和小根堆
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
注意:
①堆中任一子树亦是堆。
②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。
3、堆排序特点
堆排序(HeapSort)是一树形选择排序。
堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。
4、堆排序与直接插入排序的区别
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
5、堆排序
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
① 初始化操作:将R[1..n]构造为初始堆;
② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
(3)堆排序的算法:
void HeapSort(SeqIAst R)
{ //对R[1..n]进行堆排序,不妨用R[0]做暂存单元
int i;
BuildHeap(R); //将R[1-n]建成初始堆
for(i=n;i1;i--){ //对当前无序区R[1..i]进行堆排序,共做n-1趟。
R[0]=R[1];R[1]=R[i];R[i]=R[0]; //将堆顶和堆中最后一个记录交换
Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质
} //endfor
} //HeapSort
(4) BuildHeap和Heapify函数的实现
因为构造初始堆必须使用到调整堆的操作,先讨论Heapify的实现。
① Heapify函数思想方法
每趟排序开始前R[l..i]是以R[1]为根的堆,在R[1]与R[i]交换后,新的无序区R[1..i-1]中只有R[1]的值发生了变化,故除R[1]可能违反堆性质外,其余任何结点为根的子树均是堆。因此,当被调整区间是R[low..high]时,只须调整以R[low]为根的树即可。
"筛选法"调整堆
R[low]的左、右子树(若存在)均已是堆,这两棵子树的根R[2low]和R[2low+1]分别是各自子树中关键字最大的结点。若R[low].key不小于这两个孩子结点的关键字,则R[low]未违反堆性质,以R[low]为根的树已是堆,无须调整;否则必须将R[low]和它的两个孩子结点中关键字较大者进行交换,即R[low]与R[large](R[large].key=max(R[2low].key,R[2low+1].key))交换。交换后又可能使结点R[large]违反堆性质,同样由于该结点的两棵子树(若存在)仍然是堆,故可重复上述的调整过程,对以R[large]为根的树进行调整。此过程直至当前被调整的结点已满足堆性质,或者该结点已是叶子为止。上述过程就象过筛子一样,把较小的关键字逐层筛下去,而将较大的关键字逐层选上来。因此,有人将此方法称为"筛选法"。
具体的算法
②BuildHeap的实现
要将初始文件R[l..n]调整为一个大根堆,就必须将它所对应的完全二叉树中以每一结点为根的子树都调整为堆。
显然只有一个结点的树是堆,而在完全二叉树中,所有序号 的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,我们只需依次将以序号为 , -1,…,1的结点作为根的子树都调整为堆即可。
具体算法。
5、大根堆排序实例
对于关键字序列(42,13,24,91,23,16,05,88),在建堆过程中完全二叉树及其存储结构的变化情况参见。
6、 算法分析
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1),
它是不稳定的排序方法。