深度解析:排序策略解析与应用案例

更新:11-11 神话故事 我要投稿 纠错 投诉

很多朋友对于深度解析:排序策略解析与应用案例和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!

向已排序的有序列表中插入一条记录,得到一个新的有序列表,记录数加1。即:先将序列的第一条记录视为有序子序列,然后逐条插入第二条记录,直到整个序列是有序的。

要点:设置哨兵,用于临时存储和判断数组边界。

直接插入排序示例:

如果遇到与插入元素相等的元素,那么插入元素会将要插入的元素放在相等元素的后面。因此,相等元素的顺序没有改变。原来的无序序列的顺序就是排序后的顺序,所以插入排序是稳定的。

算法的实现:

voidprint(inta[],intn,inti){

库特

for(intj=0;j8;j++){

库特

}

库特

}

voidInsertSort(inta[],intn)

{

为(inti=1;我

if(a[i]a[i-1]){//如果第i个元素大于第i-1个元素,则直接插入。如果小于,则移动已排序的列表并插入。

intj=i-1;

intx=a[i];//复制为哨兵,存放要排序的元素

a[i]=a[i-1];//逐个移动一个元素

while(xa[j]){//在有序列表中查找插入位置

a[j+1]=a[j];

j--;//将元素向后移动

}

a[j+1]=x; //插入到正确的位置

}

print(a,n,i);//打印每次排序的结果

}

}

intmain(){

inta[8]={3,1,5,7,2,4,9,6};

插入排序(a,8);

打印(a,8,8);

}

效率:

时间复杂度:O(n^2)。

其他插入排序包括二元插入排序和2路插入排序。

2.插入排序——希尔排序(Shell`s Sort)

希尔排序法是由D.L. 提出的。 Shell于1959年提出,是对直接排序的很大改进。希尔排序也称为减少增量排序。

基本思想:

首先,将整个待排序记录序列分成若干子序列进行直接插入排序。当整个序列中的记录“基本有序”时,则直接对所有记录进行插入排序。

操作方法:

选择增量序列t1,t2,tk,其中titj,tk=1;

根据增量序列数k对序列进行k次排序;

在每次排序过程中,将待排序列根据对应的增量ti分成若干个长度为m的子序列,对每个子表进行直接插入排序。只有当增量因子为1时,整个序列才被当作一个表来处理,表的长度就是整个序列的长度。

希尔排序示例:

算法实现:

我们简单处理增量序列:增量序列d={n/2,n/4, n/8.1}n 为要排序的数字个数

即:首先将一组待排序记录按照一定的增量d(n/2,n为待排序数的个数)划分为若干组子序列,每组记录的下标相差d.对于每组中的所有记录元素通过直接插入进行排序,然后按更小的增量(d/2)进行分组,并在每组内再次排序。继续减少增量,直到为1,最后使用直接插入排序完成排序。

voidprint(inta[],intn,inti){

库特

for(intj=0;j8;j++){

库特

}

库特

}

/**

*直接插入排序的一般形式

*@paramintdk 减少增量。如果是直接插入排序,dk=1

*/

voidShellInsertSort(inta[],intn,intdk)

{

为(inti=dk;i

if(a[i]a[i-dk]){//如果第i个元素大于第i-1个元素,则直接插入。如果小于,则移动已排序的列表并插入。

intj=i-dk;

intx=a[i];//复制为哨兵,存放要排序的元素

a[i]=a[i-dk];//先向后移动一个元素

while(xa[j]){//在有序列表中查找插入位置

a[j+dk]=a[j];

j-=dk;//向后移动元素

}

a[j+dk]=x; //插入到正确的位置

}

打印(a,n,i);

}

}

/**

*首先按照增量d进行希尔排序(n/2,n为要排序的数字个数)

*/

voidshellSort(inta[],intn){

intdk=n/2;

而(dk=1){

ShellInsertSort(a,n,dk);

dk=dk/2;

}

}

intmain(){

inta[8]={3,1,5,7,2,4,9,6};

//ShellInsertSort(a,8,1);//直接插入排序

shellSort(a,8);//希尔插入排序

打印(a,8,8);

}

希尔排序时效性分析比较困难。键码比较的次数和记录的移动的次数取决于增量因子序列d的选择。在某些情况下,可以准确估计键码比较次数和记录的走法次数。目前还没有人给出一种选择最佳增量因子序列的方法。增量因子序列可以通过多种方式获取,包括奇数和素数。但需要注意的是,增量因子之间除1外没有其他公共因子,最后的增量因子必须为1。希尔排序方法是一种不稳定的排序方法。

3. 选择排序——简单选择排序

基本思想:

在一组待排序的数字中,选择最小(或最大)的数字,与第一个位置的数字交换;然后找到剩余数字中最小(或最大)的数字,并将其与第二位置的数字交换。交换,以此类推,直到第n-1个元素(倒数第二个数字)和第n个元素(最后一个数字)进行比较。

简单选择排序的示例:

操作方法:

第一遍,找到n条记录中关键码最小的记录,与第一条记录交换;

第二遍,从第二条记录开始的n-1条记录中选择关键码最小的记录,与第二条记录交换;

等等.

对于第i遍,从第i条记录开始的n-i+1条记录中选择具有最小密钥码的记录并与第i条记录交换。

直到整个序列按key排序。

算法实现:

voidprint(inta[],intn,inti){

库特斯

for(intj=0;j8;j++){

库特

}

库特

}

/**

*数组的最小值

*@returnint 数组键值

*/

intSelectMinKey(inta[],intn,inti)

{

intk=i;

for(intj=i+1;jn;++j){

如果(a[k]a[j])k=j;

}

返回k;

}

/**

*选择排序

*/

voidselectSort(inta[],intn){

intkey,tmp;

for(inti=0;in;++i){

key=SelectMinKey(a,n,i);//选择最小的元素

如果(键!=i){

tmp=a[i];a[i]=a[key];a[key]=tmp;//最小元素与第i个元素互换

}

打印(a,n,i);

}

}

intmain(){

inta[8]={3,1,5,7,2,4,9,6};

cout"初始值:";

for(intj=0;j8;j++){

库特

}

库特

选择排序(a,8);

打印(a,8,8);

}

简单选择排序的改进—— 二元选择排序

在简单选择排序中,每次循环只能确定排序后一个元素的位置。我们可以考虑改进每次循环对两个元素(当前最大和最小记录)的定位,从而减少排序所需的循环次数。改进后,排序n个数据最多只需要[n/2]次循环。具体实现如下:

voidSelectSort(intr[],intn){

inti,j,最小值,最大值,tmp;

for(i=1;i=n/2;i++){

//进行不超过n/2次的选择排序操作

min=i;max=i;//分别记录最大最小关键字记录位置

for(j=i+1;j=n-i;j++){

if(r[j]r[最大值]){

最大=j;继续;

}

如果(r[j]r[分钟]){

最小值=j;

}

}

//这个交换操作也可以具体情况具体讨论,提高效率。

tmp=r[i-1];r[i-1]=r[min];r[min]=tmp;

tmp=r[n-i];r[n-i]=r[max];r[max]=tmp;

}

}

4.选择排序——堆排序

堆排序是一种树选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:n个元素的序列(k1,k2,kn),当且仅当

它被称为堆。从堆的定义可以看出,堆顶元素(即第一个元素)一定是最小项(小顶堆)。

如果将堆存储为一维数组,则堆对应于一棵完全二叉树,所有非叶子节点的值不大于(或不小于)其子节点的值,根节点(堆顶元素)的值最小(或最大)。喜欢:

(a) 大顶堆序列:(96,83,27,38,11,09)

(b) 小顶堆序列:(12, 36, 24, 85, 47, 30, 53, 91)

最初,将待排序的n个数的序列视为一个顺序存储的二叉树(一维数组存储二叉树),调整它们的存储顺序,使其成为一个堆,输出堆顶元素即可得到n 个元素。堆中最小(或最大)的元素,则堆的根节点数最小(或最大)。然后重新调整前(n-1)个元素组成堆,输出堆顶元素,得到n个元素中第二小(或第二大)的元素。依此类推,直到出现一个只有两个节点的堆,将它们交换,最后得到n个节点的有序序列。将此过程称为堆排序。

因此,实现堆排序需要解决两个问题:

1.如何构建n个要排序的数字到堆中;

2、输出堆顶元素后,如何调整剩余的n-1个元素,使其成为新的堆。

我们先讨论第二个问题:输出堆顶元素后,用剩余的n-1个元素重建堆的调整过程。

如何调整小顶堆:

1)有一个有m个元素的堆。输出堆顶元素后,还剩下m-1个元素。堆底的元素被发送到堆顶((最后一个元素与堆顶交换),堆被销毁。唯一的原因是根节点不满足属性堆的。

2)将根节点与左右子树中较小的元素交换。

3)如果与左子树交换:如果左子树堆被破坏,即左子树的根节点不满足堆的属性,则重复方法(2)。

4)如果与右子树交换,如果右子树堆被破坏,即右子树的根节点不满足堆的性质。然后重复方法(2)。

5)继续对不满足堆属性的子树进行上述交换操作,直到叶子节点和堆构建完毕。

这种从根节点到叶节点的调整过程称为筛选。如图所示:

我们来讨论一下最初构建一个包含n 个元素的堆的过程。

堆建法:初始序列建堆的过程是一个反复筛选的过程。

1)对于有n个节点的完全二叉树,最后一个节点是第[n/2]个节点的子树。

2)从第[n/2]个节点为根的子树开始筛选,子树变成堆。

3)然后,按顺序向前过滤以每个节点为根的子树,并将它们形成堆,直到根节点。

如图所示,构建堆的初始过程:无序序列:(49,38,65,97,76,13,27,49)

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶和堆最后一个元素之间交换位置。所以堆排序由两个函数组成。一是构建堆的穿透函数,二是重复调用穿透函数实现排序的函数。

voidprint(inta[],intn){

为(intj=0;j

库特

}

库特

}

/**

*已知H[s.m]除H[s]外均满足堆的定义

*调整H[s]使其成为一个大的顶堆。即将过滤以第s个节点为根的子树,

*@paramH是要调整的堆数组

*@params为要调整的数组元素的位置

*@paramlength是数组的长度

*/

voidHeapAdjust(intH[],ints,intlength)

{

inttmp=H[s];

intchild=2*s+1;//左子节点的位置。 (i+1为当前调整节点右子节点的位置)

while(子长度){

如果(孩子+1

++孩子;

}

如果(H[s]

H[s]=H[child];//然后将较大的子节点上移,替换其父节点

s=child;//重置s,即下一个要调整的节点的位置

子=2*s+1;

}else{//如果当前要调整的节点大于其左右子节点,则无需调整,直接退出

休息;

}

H[s]=tmp;//将当前要调整的节点放到比它大的子节点位置。

}

打印(高度,长度);

}

/**

*初始堆调整

* 将H[0.length-1] 构建成堆

*调整后第一个元素为序列中最小的元素

*/

voidBuildingHeap(intH[],intlength)

{

//最后一个有子节点的位置i=(length-1)/2

for(inti=(长度-1)/2;i=0;--i)

HeapAdjust(H,i,长度);

}

/**

*堆排序算法

*/

voidHeapSort(intH[],intlength)

{

//初始堆

BuildingHeap(H,长度);

//调整从最后一个元素开始的顺序

for(inti=长度-1;i0;--i)

{

//交换堆顶元素H[0]和堆最后一个元素

inttemp=H[i];H[i]=H[0];H[0]=温度;

//每次交换堆顶元素和堆最后一个元素后,必须对堆进行调整

堆调整(H,0,i);

}

}

intmain(){

intH[10]={3,1,5,7,2,4,9,6,10,8};

cout"初始值:";

打印(H,10);

堆排序(H,10);

//选择排序(a,8);

cout"结果:";

打印(H,10);

}

分析:

设树深度为k,k=[log2n]+1。从根到叶子过滤时,元素比较次数最多为2(k-1)次,记录最多交换k次。因此,堆建好后,排序过程中的筛选次数不超过以下公式:

构建堆时的比较次数不超过4n,因此堆排序最坏情况的时间复杂度为:O(nlogn)。

5.交换排序——冒泡排序(Bubble Sort)

基本思想:

在一组待排序的数字中,对范围内所有尚未排序的数字,从上到下依次比较和调整相邻的两个数字,使较大的数字向下沉,较大的数字向下沉。小的向上升起。即:每当比较两个相邻的数字,发现它们的排序与排序要求相反时,就将它们交换。

冒泡排序示例:

算法的实现:

voidbubbleSort(inta[],intn){

for(inti=0;in-1;++i){

for(intj=0;jn-i-1;++j){

如果(a[j]a[j+1])

{

inttmp=a[j];a[j]=a[j+1];a[j+1]=tmp;

}

}

}

}

冒泡排序算法的改进

冒泡排序常见的改进方法是添加符号变量交换,用于标记某个排序过程中是否存在数据交换。如果在某个排序过程中没有发生数据交换,则说明数据已按要求排列。 OK,排序可以立即结束,避免不必要的比较过程。本文提供了以下两种改进算法:

1. 设置一个地标变量pos 来记录每次排序过程中最后一次交换的位置。由于pos 位置之后的记录已就位交换,因此在下一次排序过程中仅扫描pos 位置。

改进后的算法如下:

voidBubble_1(intr[],intn){

inti=n-1;//初始时,最终位置保持不变

而(i0){

intpos=0;//每行开始时,无记录交换

for(intj=0;ji;j++)

如果(r[j]r[j+1]){

pos=j;//记录兑换位置

inttmp=r[j];r[j]=r[j+1];r[j+1]=tmp;

}

i=pos;//为下一步排序做准备

}

}

2.传统冒泡排序中,每次排序操作只能找到一个最大值或最小值。我们考虑采用在每次排序操作中进行正向冒泡和反向冒泡的方法,一次性获得两个最终值(最大的一个)。和最小的),从而将排序次数减少了近一半。

改进后的算法实现为:

voidBubble_2(intr[],intn){

intlow=0;

inthigh=n-1;//设置变量的初始值

inttmp,j;

而(低高){

for(j=low;jhigh;++j)//正向冒泡,找到最大的

如果(r[j]r[j+1]){

tmp=r[j];r[j]=r[j+1];r[j+1]=tmp;

}

--high;//修改high值,向前移动一位

for(j=high;jlow;--j)//反转气泡找出最小的

如果(r[j]

tmp=r[j];r[j]=r[j-1];r[j-1]=tmp;

}

++low;//修改low值,向后移动一位

}

}

6.交换排序——快速排序

基本思想:

1)选择一个基本元素,通常是第一个元素或最后一个元素,

2)通过一轮排序,将待排序的记录分为两个独立的部分,并且其中一部分记录的元素值小于参考元素值。另一部分记录大于基值的元素值。

3)此时,参考元素排序后已处于正确位置。

4)然后继续用同样的方式对两部分记录进行排序,直到整个序列有序。

快速排序示例:

(a) 一次排序过程:

(b) 排序全过程

算法的实现:

递归实现:

voidprint(inta[],intn){

为(intj=0;j

库特

}

库特

}

voidswap(int*a,int*b)

{

inttmp=*a;

*a=*b;

*b=tmp;

}

intpartition(inta[],intlow,inthigh)

{

intprivotKey=a[low];//基本元素

while(lowhigh){//从表格两端向中间交替扫描

while(lowhigha[high]=privotKey)--high;//从high指向的位置向前搜索,一直到low+1的位置。将小于底端的元素交换到下端

交换(a[低],a[高]);

while(lowhigha[low]=privotKey)++low;

交换(a[低],a[高]);

}

打印(a,10);

回报低;

}

voidquickSort(inta[],intlow,inthigh){

如果(低高){

intprivotLoc=partition(a,low,high);//将表分为两部分

fastSort(a,low,privotLoc-1);//递归排序low子表

fastSort(a,privotLoc+1,high);//递归排序high子表

}

}

intmain(){

inta[10]={3,1,5,7,2,4,9,6,10,8};

cout"初始值:";

打印(a,10);

快速排序(a,0,9);

cout"结果:";

打印(a,10);

}

分析:

快速排序通常被认为是同数量级排序方法中平均性能最好的(O(nlog2n))。但如果初始序列是按键排序或者基本排序的话,快速排序就会退化为冒泡排序。为了改进它,通常采用“三合一法”来选择基准记录,即将以两个端点为中心的三个记录键和排序区间的中点调整为支点记录。快速排序是一种不稳定的排序方法。

快速排序改进

在该改进算法中,仅对长度大于k的子序列递归调用快速排序,使原始序列基本有序,然后使用插入排序算法对整个基本有序序列进行排序。实践证明,改进算法的时间复杂度有所降低,且当k取8左右时,改进算法性能最佳。算法思想如下:

voidprint(inta[],intn){

为(intj=0;j

库特

}

库特

}

voidswap(int*a,int*b)

{

inttmp=*a;

*a=*b;

*b=tmp;

}

intpartition(inta[],intlow,inthigh)

{

intprivotKey=a[low];//基本元素

while(lowhigh){//从表格两端向中间交替扫描

while(lowhigha[high]=privotKey)--high;//从high指向的位置向前搜索,一直到low+1的位置。将小于底端的元素交换到下端

交换(a[低],a[高]);

while(lowhigha[low]=privotKey)++low;

交换(a[低],a[高]);

}

打印(a,10);

回报低;

}

voidqsort_improve(intr[],intlow,inthigh,intk){

if(high-lowk){//长度大于k时递归,k为指定数字

intpivot=partition(r,low,high);//调用的Partition算法不变

qsort_improve(r,低,pivot-1,k);

qsort_improve(r,枢轴+1,高,k);

}

}

voidquickSort(intr[],intn,intk){

qsort_improve(r,0,n,k);//首先调用改进算法Qsort,使其基本有序

//重用插入排序对基本有序序列进行排序

for(inti=1;i=n;i++){

inttmp=r[i];

intj=i-1;

而(tmpr [j]){

r[j+1]=r[j];j=j-1;

}

r[j+1]=tmp;

}

}

intmain(){

inta[10]={3,1,5,7,2,4,9,6,10,8};

cout"初始值:";

打印(a,10);

快速排序(a,9,4);

cout"结果:";

打印(a,10);

}

7. 归并排序

基本思想:

归并排序方法就是将两个(或两个

上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 归并排序示例: 合并方法: 设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m。 j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标 若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束 //选取r[i]和r[j]较小的存入辅助数组rf 如果r[i] 否则,rf[k]=r[j]; j++; k++; 转⑵ //将尚未处理完的子表中元素存入rf 如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空 如果j<=n ,  将r[j…n] 存入rf[k…n] //后一子表非空 合并结束。 //将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n] voidMerge(ElemType *r,ElemType *rf,inti,intm,intn) { intj,k; for(j=m+1,k=i; i<=m && j <=n ; ++k){ if(r[j] < r[i]) rf[k] = r[j++]; elserf[k] = r[i++]; } while(i <= m)  rf[k++] = r[i++]; while(j <= n)  rf[k++] = r[j++]; } 归并的迭代算法 1 个元素的表总是有序的。所以对n 个元素的待排序列,每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表长度均为2。再进行两两合并,直到生成n 个元素按关键码有序的表。 voidprint(inta[],intn){ for(intj= 0; j cout< } cout< } //将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n] voidMerge(ElemType *r,ElemType *rf,inti,intm,intn) { intj,k; for(j=m+1,k=i; i<=m && j <=n ; ++k){ if(r[j] < r[i]) rf[k] = r[j++]; elserf[k] = r[i++]; } while(i <= m)  rf[k++] = r[i++]; while(j <= n)  rf[k++] = r[j++]; print(rf,n+1); } voidMergeSort(ElemType *r, ElemType *rf,intlenght) { intlen = 1; ElemType *q = r ; ElemType *tmp ; while(len < lenght) { ints = len; len = 2 * s ; inti = 0; while(i+ len  Merge(q, rf,  i, i+ s-1, i+ len-1 );//对等长的两个子表合并 i = i+ len; } if(i + s < lenght){ Merge(q, rf,  i, i+ s -1, lenght -1);//对不等长的两个子表合并 } tmp = q; q = rf; rf = tmp;//交换q,rf,以保证下一趟归并时,仍从q 归并到rf } } intmain(){ inta[10] = {3,1,5,7,2,4,9,6,10,8}; intb[10]; MergeSort(a, b, 10); print(b,10); cout<<"结果:"; print(a,10); } 两路归并的递归算法 voidMSort(ElemType *r, ElemType *rf,ints,intt) { ElemType *rf2; if(s==t) r[s] = rf[s]; else { intm=(s+t)/2;/*平分*p 表*/ MSort(r, rf2, s, m);/*递归地将p[s…m]归并为有序的p2[s…m]*/ MSort(r, rf2, m+1, t);/*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/ Merge(rf2, rf, s, m+1,t);/*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/ } } voidMergeSort_recursive(ElemType *r, ElemType *rf,intn) {/*对顺序表*p 作归并排序*/ MSort(r, rf,0, n-1); } 8. 桶排序/基数排序(Radix Sort) 说基数排序之前,我们先说桶排序: 基本思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。 简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。 例如要对大小为[1..1000]范围内的n个整数A[1..n]排序 首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储   (10..20]的整数,……集合B[i]存储(   (i-1)*10,   i*10]的整数,i   =   1,2,..100。总共有  100个桶。 然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。  再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任  何排序法都可以。 最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这  样就得到所有数字排好序的一个序列了。 假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果 对每个桶中的数字采用快速排序,那么整个算法的复杂度是 O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   -   nlogm) 从上式看出,当m接近n的时候,桶排序复杂度接近O(n) 当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的  ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。 前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是: 1)首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。 2)其次待排序的元素都要在一定的范围内等等。 桶式排序是一种分配排序。分配排序的特定是不需要进行关键码的比较,但前提是要知道待排序列的一些具体情况。 分配排序的基本思想:说白了就是进行多次的桶式排序。 基数排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。 实例: 扑克牌中52 张牌,可按花色和面值分成两个字段,其大小关系为: 花色:梅花< 方块< 红心< 黑心 面值:2< 3< 4< 5< 6< 7< 8< 9< 10< J< Q< K< A 若对扑克牌按花色、面值进行升序排序,得到如下序列: 即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花色情况下,大小关系才由面值的大小确定。这就是多关键码排序。 为得到排序结果,我们讨论两种排序方法。 方法1:先对花色排序,将其分为4 个组,即梅花组、方块组、红心组、黑心组。再对每个组分别按面值进行排序,最后,将4 个组连接起来即可。 方法2:先按13 个面值给出13 个编号组(2 号,3 号,...,A 号),将牌按面值依次放入对应的编号组,分成13 堆。再按花色给出4 个编号组(梅花、方块、红心、黑心),将2号组中牌取出分别放入对应花色组,再将3 号组中牌取出分别放入对应花色组,……,这样,4 个花色组中均按面值有序,然后,将4 个花色组依次连接起来即可。 设n 个元素的待排序列包含d 个关键码{k1,k2,…,kd},则称序列对关键码{k1,k2,…,kd}有序是指:对于序列中任两个记录r[i]和r[j](1≤i≤j≤n)都满足下列有序关系: 其中k1 称为最主位关键码,kd 称为最次位关键码     。 两种多关键码排序方法: 多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种方法: 最高位优先(Most Significant Digit first)法,简称MSD 法: 1)先按k1 排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等。 2)再对各组按k2 排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后。 3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。 最低位优先(Least Significant Digit first)法,简称LSD 法: 1) 先从kd 开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。 2) 最后将各个子序列连接起来,便可得到一个有序的序列, 扑克牌按花色、面值排序中介绍的方法二即是LSD 法。 基于LSD方法的链式基数排序的基本思想 “多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。 基数排序: 是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。 算法实现: Void RadixSort(Node L[],length,maxradix) { intm,n,k,lsp; k=1;m=1; inttemp[10][length-1]; Empty(temp);//清空临时空间 while(k { for(inti=0;i { if(L[i] Temp[0][n]=L[i]; else Lsp=(L[i]/m)%10;//确定关键字 Temp[lsp][n]=L[i]; n++; } CollectElement(L,Temp);//收集 n=0; m=m*10; k++; } } 总结 各种排序的稳定性,时间复杂度和空间复杂度总结: 我们比较时间复杂度函数的情况: 时间复杂度函数O(n)的增长情况 所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。 时间复杂度来说: (1)平方阶(O(n2))排序 各类简单排序:直接插入、直接选择和冒泡排序;(2)线性对数阶(O(nlog2n))排序快速排序、堆排序和归并排序;(3)O(n1+§))排序,§是介于0和1之间的常数。 希尔排序(4)线性阶(O(n))排序 基数排序,此外还有桶、箱排序。 说明: 当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n); 而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2); 原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。 稳定性: 排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。 稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较; 稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序 不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序 选择排序算法准则: 每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。 选择排序算法的依据 影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点: 1.待排序的记录数目n的大小; 2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小; 3.关键字的结构及其分布情况; 4.对排序稳定性的要求。 设待排序元素的个数为n. 1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短; 堆排序:  如果内存空间允许且要求稳定性的, 归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。 2)当n较大,内存空间允许,且要求稳定性 =》归并排序 3)当n较小,可采用直接插入或直接选择排序。 直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。 直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序 5)一般不使用或不直接使用传统的冒泡排序。 6)基数排序 它是一种稳定的排序算法,但有一定的局限性: 1、关键字可分解。

OK,关于深度解析:排序策略解析与应用案例和的内容到此结束了,希望对大家有所帮助。

用户评论

经典的对白

这标题让人好奇是哪种排序排名?

    有7位网友表示赞同!

一样剩余

是按数字顺序排还是其他方式?

    有8位网友表示赞同!

╭摇划花蜜的午后

29 有什么关联呢?

    有17位网友表示赞同!

Edinburgh°南空

这个标题有点神秘啊。

    有15位网友表示赞同!

墨染殇雪

想知道具体的排序标准,才能更有感触。

    有16位网友表示赞同!

命里缺他

希望是按友好度排序!

    有20位网友表示赞同!

予之欢颜

说不定是排行榜啊,谁排第一呢?

    有14位网友表示赞同!

枫无痕

29 这个数字听起来很特别哦。

    有6位网友表示赞同!

逾期不候

期待看到这个排序的结果!

    有20位网友表示赞同!

孤岛晴空

不知道是哪些东西在被排序呢?

    有9位网友表示赞同!

寂莫

是不是什么比赛的排名?

    有13位网友表示赞同!

神经兮兮°

给我看看29是什么样子吧!

    有9位网友表示赞同!

你很爱吃凉皮

好像听过很多关于排名的说法,这又是怎么样的呢?

    有8位网友表示赞同!

凉凉凉”凉但是人心

我很想了解这种排序背后的规则。

    有9位网友表示赞同!

ˉ夨落旳尐孩。

标题也太简洁了吧!

    有13位网友表示赞同!

孤自凉丶

希望能详细介绍一下这个排序的方法啊!

    有7位网友表示赞同!

夏至离别

29 这个数字让我联想到...

    有18位网友表示赞同!

来瓶年的冰泉

也许是对时间的一种排序?

    有11位网友表示赞同!

你瞒我瞒

如果可以按自己喜欢的顺序排序就更好了!

    有5位网友表示赞同!

虚伪了的真心

期待进一步了解“排序 29”!

    有11位网友表示赞同!

代价是折磨╳

标题太有吸引力了,让我忍不住想了解更多!

    有8位网友表示赞同!

【深度解析:排序策略解析与应用案例】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:《青年教师成长之路》感悟篇(一) 下一篇:《秋乃茉莉作品:《恐怖宠物店》系列第五百一十章 新篇》