1. 常用的数据排序算法有哪些,各有什么特点举例结合一种排序算法并应用数组进行数据排序。
排序简介
排序是数据处理中经常使用的一种重要运算,在计算机及其应用系统中,花费在排序上的时间在系统运行时间中占有很大比重;并且排序本身对推动算法分析的发展也起很大作用。目前已有上百种排序方法,但尚未有一个最理想的尽如人意的方法,本章介绍常用的如下排序方法,并对它们进行分析和比较。
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)。
2. C语言排序
排序:基本排序算法
程序员可以使用5种:
路插入排序(insertionsort.)
路交换排序(exchangesOrt)
路选择排序(selectionsort)路排序(归并)合并
路分配排序(distributionsort)
为了生动地解释每个排序算法的工作原理,让我们来看看如何在桌子上,随机使用这些方法该卡支付排序。排序根据色卡只(顺序梅花,方块,红心和黑色心脏),而且还排序由点(从2到A)。
插入排序过程:从一堆卡片的顶部开始拿牌,每人持有一张卡,根据卡的原则纳入排序正确的位置的手中。在桌子上拿着卡后,递给排序的卡。
交换排序过程为:
(1)获得两张牌到手上。如果卡到左边在卡的背面右侧,以换取这两张牌的位置。
(2)然后把一张卡,如果有必要比较一下最右边的两张牌,以换取这两张牌的位置。
(3)重复步骤(2),直到所有的牌都拿到手。
(4)如果位置不再有任何需要交换的两张牌的手,就说明这个品牌已经被排序,否则,出牌桌上的双手,重复(1)至(4)步骤,直到他们的手来分类的。
选择排序过程:找到最小的卡牌在桌子上,并拿在手中,重复这个过程,直到所有的牌都在你手中。
归并排序过程:上表中的卡片被划分为52的反应器,每个反应器到卡上。因为每堆牌是有序的(记住,这个时候每个堆叠只有一个卡),所以如果卡分成两堆相邻桩,每样的堆卡,你可以得到26堆已排序的卡,然后每堆有两张卡。重复此合并操作,你可以把堆有13张牌(有四张牌中每根桩),7堆叠卡(有六个反应堆八张牌,有一堆的四张牌),最后将获得52桩卡。
分配排序(也称为基数排序,即基数排序)过程是:首先由卡13点到堆中,并根据卡叠在一起的顺序堆叠这13点,然后通过许可西装4分堆,那么这四张牌由花色顺序叠堆在它排序的卡。
在选择排序算法,你还需要了解以下条款:
(1)自然(自然)
如果某种有序的数据排序算法的比较快(工作变小),排序为无序的速度数据较慢(工作变大),我们称这种排序算法是很自然的。如果数据已接近有序,就需要考虑使用自然排序算法。
(2)稳定(稳定)
如果一个排序算法可以保持审议之前和之后的序列数据相等,我们称这种排序算法是稳定的。
例如,下面的现有列表:
玛丽·琼斯
玛丽·史密斯
汤姆·琼斯
苏西队列
如果上面的列表中一个稳定的排序算法由姓氏排序,那么“玛丽·琼斯”和“汤姆·琼斯”JR将维持原有的秩序,因为他们的姓是一样的后整理。
稳定的排序算法可以是一级,二级关键字的数据,如名字和姓氏(排序排序换句话说,按姓氏的主要排序,但在相同的姓氏,而且按名称排序的数据)。在具体实施时,按下第二个键进行排序,然后键排序。
(3)内部排序(内部排序)和外部排序的所有排序方法被称为内部排序,将排在磁盘,磁带的数据在内存中的数据(外部排序)
鳞次栉比,及其他事情存款排序方法被称为外部排序。
查找:
和排序算法,找到一个(搜索)算法也是研究最多的计算机科学问题。查找算法和排序算法是链接的,因为许多搜索算法依赖于数据集找到有序度。基本的搜索算法有以下四种:
路顺序查找(顺序检索)。
路查找比较(比较搜索)
路查找底数(基数搜索)
路哈希查找(哈希)
下面是照样出,以支付卡为例,描述这些算法的工作过程。
顺序查找过程如下:从第一个开始,看看每个卡,直到你找到找卡。
比较搜索(也称为binarysearching,即二进制搜索)的发牌条件已排序,其过程是:任意抽一张牌,如果卡找卡,然后找到该进程的结束。如果绘制的不是寻找更大的卡此卡,该卡在它前面反复查找操作,相反,品牌的背后是反复的查找操作,直到你找到找卡。 13分第一个卡插入堆,或按颜色分为四个桩:
基地查找过程。然后找到并找到点或卡那一堆相同花色的牌,然后在这堆使用的搜索算法中的任何一个寻找寻找卡牌。
哈希查找过程是:
(1)留在桌上可以放几堆卡空间,构造一个函数,根据花色和等级,以便它可以被映射到一个特定的品牌桩(该函数被调用散列函数,即哈希函数)。
(2)根据散列功能卡片分成若干堆。
(3)寻找到桩寻找基于散列函数在卡片上,然后找到望着这堆牌中的卡。
例如,您可以构建这样一个散列函数:
一堆=等级+西装
其中,职级是一个数字表示卡点;西装是一个数字表示牌的花色;说一堆一堆的价值,它决定了一堆卡片掉落。如果1,2,......,13表示一个,2,...... K,1,2,和3,分别与梅花,方块,红心和黑桃,然后堆放值将是1,2,...,16,这样就可以把一副牌分成16堆。
哈希查找,虽然看起来有点可笑,但它确实是一个非常有用的搜索算法。各种程序,从压缩程序(如堆高机)的磁盘缓存程序(如的SmartDrive),几乎所有的这种方式来提高搜索速度,或发现性能
排序:
>其中一个主要问题是排序和搜索的速度。这个问题往往被忽视,与程序的其他部分相比,还是觉得它需要排序的时间几乎可以忽略不计。然而,对于大多数应用程序,排序或查找,你不必花费大量的精力开始准备一些算法,但应可在选择最简单的算法之一(见3.1和3.4),当你发现算法当程序有更好的算法运行速度非常慢,而且回来(见下面的说明)使用。
这里有一个排序或搜索算法,以确定方法的速度。
一,引进的一种算法,它是指在所有情况下(最好和最差的平均值)查找需要进行排序或完成作业的数量,从而可以比较不同性能的复杂性的概念的算法。复杂性和数据进行排序或查找数据集针对
算法的量,因此,表达数据集的量引入基于所述算法的复杂度的表示。 最快的算法复杂度,它代表操作的数量无关,与算法的数据量。 O(N)的复杂度(N表示数据集的数量)指示操作的数目直接相关的数据的量的算法。为O(logN)的复杂性两者之间,其指示操作的数量和相关的算法的对数的数据量。复杂度为O(nlogn)的(N乘以logN)的算法比复杂度为O(N)的算法是缓慢的,而且复杂度为O(N2)算法慢。
注意:如果两种算法的复杂度为O(logN)的,则算法的logN的基数较大的速度要快于本章中的例子,logN的基数是10
3. c++的程序
其实排序,在C++的泛型算法里有一个 sort() ,直接利用它就不用自己写代码,又简洁,又省力。
我把1 2的代码入在一个main()里
#include <iostream>
#include <cstdlib> //随机数产生头文件
#include <ctime> //包含time()的头文件
#include <vector>
#include <algorithm> //泛型算法头文件
using std::cout;
using std::endl;
using std::cin;
using std::vector;
int main(void)
{
/* 1小题部分 */
int rezult = 1;
int temp = 0;
cout << "please input the number of the end:" << endl;
cin >> temp; //交互部分,输入阶乘上限
while(temp)
{
rezult *= temp--;
}
cout << "rezult:" << rezult << endl << '\n'; // 结果
/* 2小题部分 */
int num = 0;
int rd = 0;
vector<int> TR;
srand(time(0));
cout << "input the number :" << endl;
cin >> num; //输入需要产生随机数的个数,10则输入10
while(num != 0)
{
rd = rand() % 101;
if(rd % 2 != 0 )
TR.push_back(rd);
else
continue;
--num;
}
vector<int>::iterator first = TR.begin();
sort(first, TR.end()); //利用泛型算法对数排序
while(first != TR.end())
{
cout << *first << " "; // 输出排序后的数
++first;
}
cout << endl;
return 0;
}
4. 用C语言实现冒泡排序对数排序后利用折中算法查找输入
#include<bits/stdc++.h>
usingnamespacestd;
intn,a[233],T;
intmain(){
scanf("%d",&n);
for(inti=1;i<=n;i++)scanf("%d",&a[i]);
for(inti=1;i<n;i++)
for(intj=n;j>i;j--)
if(a[j]<a[i])
swap(a[i],a[j]);
for(inti=1;i<=n;i++)printf("%d",a[i]);printf(" ");
scanf("%d",&T);
intl=1,r=n;
while(l<r){
intmid=(l+r+1)>>1;
if(a[mid]<=T)l=mid;elser=mid-1;
}
if(a[l]==T)printf("%d ",l);elseprintf("-1 ");
}
5. 时间为O(nlg n)的排序算法 如快速排序 堆排序 nlg是什么意思。好象是lgn。 什么意思
准确来说,是log(2,n),即以2为底取n的对数.
该时间复杂度的产生是由于算法中使用了二分法.二分法的其中一个显着的标志就是使得渐进复杂度变为2底对数级别.
直观来说,对于1000个数的排序,效率为O(n)的排序(假设有)将花费1000"单位"的时间,那么O(n²)的排序将花费10^6"单位"的时间.而O(nlogn)的排序将花费 1000*log(1000) ≈ 10000 "单位"的时间.
这里可以看出其效率的显着优势,
而通过函数有关特征可以得知,对数函数是增长的越来越慢的,这就使得O(nlogn)的排序可以在越大的工作量中和平方级排序拉大差距.
6. 常用的排序算法都有哪些
排序算法 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
分类
在计算机科学所使用的排序算法通常被分类为:
计算的复杂度(最差、平均、和最好表现),依据串行(list)的大小(n)。一般而言,好的表现是O。(n log n),且坏的行为是Ω(n2)。对于一个排序理想的表现是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要Ω(n log n)。
记忆体使用量(以及其他电脑资源的使用)
稳定度:稳定排序算法会依照相等的关键(换言之就是值)维持纪录的相对次序。也就是一个排序算法是稳定的,就是当有两个有相等关键的纪录R和S,且在原本的串行中R出现在S之前,在排序过的串行中R也将会是在S之前。
一般的方法:插入、交换、选择、合并等等。交换排序包含冒泡排序(bubble sort)和快速排序(quicksort)。选择排序包含shaker排序和堆排序(heapsort)。
当相等的元素是无法分辨的,比如像是整数,稳定度并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。
(4, 1) (3, 1) (3, 7) (5, 6)
在这个状况下,有可能产生两种不同的结果,一个是依照相等的键值维持相对的次序,而另外一个则没有:
(3, 1) (3, 7) (4, 1) (5, 6) (维持次序)
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地时作为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个物件间之比较,就会被决定使用在原先资料次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。
排列算法列表
在这个表格中,n是要被排序的纪录数量以及k是不同键值的数量。
稳定的
冒泡排序(bubble sort) — O(n2)
鸡尾酒排序 (Cocktail sort, 双向的冒泡排序) — O(n2)
插入排序 (insertion sort)— O(n2)
桶排序 (bucket sort)— O(n); 需要 O(k) 额外 记忆体
计数排序 (counting sort) — O(n+k); 需要 O(n+k) 额外 记忆体
归并排序 (merge sort)— O(n log n); 需要 O(n) 额外记忆体
原地归并排序 — O(n2)
二叉树排序 (Binary tree sort) — O(n log n); 需要 O(n) 额外记忆体
鸽巢排序 (Pigeonhole sort) — O(n+k); 需要 O(k) 额外记忆体
基数排序 (radix sort)— O(n·k); 需要 O(n) 额外记忆体
Gnome sort — O(n2)
Library sort — O(n log n) with high probability, 需要 (1+ε)n 额外记忆体
不稳定
选择排序 (selection sort)— O(n2)
希尔排序 (shell sort)— O(n log n) 如果使用最佳的现在版本
Comb sort — O(n log n)
堆排序 (heapsort)— O(n log n)
Smoothsort — O(n log n)
快速排序 (quicksort)— O(n log n) 期望时间, O(n2) 最坏情况; 对于大的、乱数串行一般相信是最快的已知排序
Introsort — O(n log n)
Patience sorting — O(n log n + k) 最外情况时间, 需要 额外的 O(n + k) 空间, 也需要找到最长的递增子序列(longest increasing subsequence)
不实用的排序算法
Bogo排序 — O(n × n!) 期望时间, 无穷的最坏情况。
Stupid sort — O(n3); 递回版本需要 O(n2) 额外记忆体
Bead sort — O(n) or O(√n), 但需要特别的硬体
Pancake sorting — O(n), 但需要特别的硬体
排序的算法
排序的算法有很多,对空间的要求及其时间效率也不尽相同。下面列出了一些常见的排序算法。这里面插入排序和冒泡排序又被称作简单排序,他们对空间的要求不高,但是时间效率却不稳定;而后面三种排序相对于简单排序对空间的要求稍高一点,但时间效率却能稳定在很高的水平。基数排序是针对关键字在一个较小范围内的排序算法。
插入排序
冒泡排序
选择排序
快速排序
堆排序
归并排序
基数排序
希尔排序
插入排序
插入排序是这样实现的:
首先新建一个空列表,用于保存已排序的有序数列(我们称之为"有序列表")。
从原数列中取出一个数,将其插入"有序列表"中,使其仍旧保持有序状态。
重复2号步骤,直至原数列为空。
插入排序的平均时间复杂度为平方级的,效率不高,但是容易实现。它借助了"逐步扩大成果"的思想,使有序列表的长度逐渐增加,直至其长度等于原列表的长度。
冒泡排序
冒泡排序是这样实现的:
首先将所有待排序的数字放入工作列表中。
从列表的第一个数字到倒数第二个数字,逐个检查:若某一位上的数字大于他的下一位,则将它与它的下一位交换。
重复2号步骤,直至再也不能交换。
冒泡排序的平均时间复杂度与插入排序相同,也是平方级的,但也是非常容易实现的算法。
选择排序
选择排序是这样实现的:
设数组内存放了n个待排数字,数组下标从1开始,到n结束。
i=1
从数组的第i个元素开始到第n个元素,寻找最小的元素。
将上一步找到的最小元素和第i位元素交换。
如果i=n-1算法结束,否则回到第3步
选择排序的平均时间复杂度也是O(n²)的。
快速排序
现在开始,我们要接触高效排序算法了。实践证明,快速排序是所有排序算法中最高效的一种。它采用了分治的思想:先保证列表的前半部分都小于后半部分,然后分别对前半部分和后半部分排序,这样整个列表就有序了。这是一种先进的思想,也是它高效的原因。因为在排序算法中,算法的高效与否与列表中数字间的比较次数有直接的关系,而"保证列表的前半部分都小于后半部分"就使得前半部分的任何一个数从此以后都不再跟后半部分的数进行比较了,大大减少了数字间不必要的比较。但查找数据得另当别论了。
堆排序
堆排序与前面的算法都不同,它是这样的:
首先新建一个空列表,作用与插入排序中的"有序列表"相同。
找到数列中最大的数字,将其加在"有序列表"的末尾,并将其从原数列中删除。
重复2号步骤,直至原数列为空。
堆排序的平均时间复杂度为nlogn,效率高(因为有堆这种数据结构以及它奇妙的特征,使得"找到数列中最大的数字"这样的操作只需要O(1)的时间复杂度,维护需要logn的时间复杂度),但是实现相对复杂(可以说是这里7种算法中比较难实现的)。
看起来似乎堆排序与插入排序有些相像,但他们其实是本质不同的算法。至少,他们的时间复杂度差了一个数量级,一个是平方级的,一个是对数级的。
平均时间复杂度
插入排序 O(n2)
冒泡排序 O(n2)
选择排序 O(n2)
快速排序 O(n log n)
堆排序 O(n log n)
归并排序 O(n log n)
基数排序 O(n)
希尔排序 O(n1.25)
冒泡排序
654
比如说这个,我想让它从小到大排序,怎么做呢?
第一步:6跟5比,发现比它大,则交换。564
第二步:5跟4比,发现比它大,则交换。465
第三步:6跟5比,发现比它大,则交换。456
7. 内部排序算法比较
按平均时间将排序分为四类:
(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);
}
8. 数据排序的一般方法有什么
数据排序方法
好的排序方法可以有效提高排序速度,提高排序效果。
在计算机领域主要使用数据排序方法根据占用内存的方式不同分为2大类:内部排序方法与外部排序方法。
内部排序方法
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。
内排序的方法有许多种,按所用策略不同,可归纳为五类:插入排序、选择排序、交换排序、归并排序和基数排序。
其中,插入排序主要包括直接插入排序和希尔排序两种;选择排序主要包括直接选择排序和堆排序;交换排序主要包括气(冒)泡排序和快速排序。
外部排序方法
外部排序基本上由两个相互独立的阶段组成。首先,按可用内存大小,将外存上含n个记录的文件分成若干长度为k的子文件或段(segment),依次读入内存并利用有效的内部排序方法对它们进行排序,并将排序后得到的有序子文件重新写入外存。通常称这些有序子文件为归并段或顺串;然后,对这些归并段进行逐趟归并,使归并段(有序子文件)逐渐由小到大,直至得到整个有序文件为止。
9. 几种排序方法
这两天复习了一下排序方面的知识,现将目前比较常见的整理一下。 选择排序选择排序的思想是首先先找到序列中最大元素并将它与序列中最后一个元素交换,然后找下一个最大元素并与倒数第二个元素交换,依次类推。此排序很简单,这不做多说,代码实现如下:View Code插入排序算法流程:1. 从第一个元素开始,该元素可以认为已经被排序 2. 取出下一个元素,在已经排序的元素序列中从后向前扫描 3. 如果该元素(已排序)大于新元素,将该元素移到下一位置 4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 5. 将新元素插入到下一位置中 6. 重复步骤2View Code冒泡排序依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。 View Code合并排序在介绍合并排序之前,首先介绍下递归设计的技术,称为分治法。分治法的核心思想是:当问题比较小时,直接解决。当问题比较大时,将问题分为两个较小的子问题,每个子问题约为原来的一半。使用递归调用解决每个子问题。递归调用结束后,常常需要额外的处理,将较小的问题的结果合并,得到较大的问题的答案。 合并排序算法在接近数组中间的位置划分数组,然后使用递归运算对两个一半元素构成的数组进行排序,最后将两个子数组进行合并,形成一个新的已排好序的数组。 代码如下:View Code快速排序快速排序与合并排序有着很多相似性。将要排序的数组分成两个子数组,通过两次递归调用分别对两个数组进行排序,再将已经排好序的两个数组合并成一个独立的有序数组。但是,将数组一分为二的做法比合并排序中使用的简单方法复杂的多。它需要将所有小于或者等于基准元素的元素放置到基准元素前面的位置,将大于基准的元素放置到基准后面的位置。 View Code堆排序View Code大概常用的几种排序就这几种,希望大家多多指正。