一,排序的基本概念
(1).排序:
将一组数据元素序列重新排列,使得数据元素序列按某个数据项(关键字)有序。
排序依据:是依据数据元素的关键字。
若关键字是主关键字(关键字值不重复),这无论采用何种排序方法,排出的结果都是唯一的;
若关键字是次关键字(关键字值可以重复),则排出的结果可能不唯一。
一般情况下,
假设含n个记录的序列为{ R1, R2,...Rn}
其相应的关键字序列为{ K1, K2, ...Kn}
这些关键字相互之间可以进行比较,即在它们之间存在着这样一个关系:Kp1≤Kp2≤...≤Kpn
按此固有关系将上式记录序列重新排列为{Rp1,Rp2, ....,Rpn}的操作称作排序。
(2).稳定排序和不稳定排序
对于任意的数据元素序列,若排序前后所有相同关键字的相对位置都不变,则称该排序方法称为稳定的排序方法。
若存在一组数据序列,在排序前后,相同关键字的相对位置发生了变化,则称该排序方法称为不稳定的排序方法。
例如,对于关键字序列3,2, 3, 4,若某种排序方法排序后变为2,3,3,4,则该排序方法是不稳定的。
(3).内部排序和外部排序
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;
反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
(4).内部排序的方法
内部排序的过程是个逐步扩 大记录的有序序列长度的过程。
二,简单插入排序
基本思想:将无序子序列中的一个或几个记录"插入”到有序子序列中,从而增加有序子序列的长度。
不同的定位方法导致不同的插入算法。
直接插入排序(基于顺序查找定位)
排序过程:整个排序过程为n- 1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序
代码实现:
typedef struct { int key;//关键字 float info;//其他信息 }JD; void straisort(D r[],int n)//对长度为n的序列排序 { int ij; for(i=2;i<=n;i++) { r[0]=r[i]; j=i-1; while(r[0].key<r[jJ.key) { r[j+1]=r[j]; j-; } r[j+1]=r[0]; } }
折半插入排序(基于折半查找定位)
希尔排序(基于逐趟缩小增量)
用直接插人排序,数据序列越接近有序,比较次数越少。
简单插入排序的本质?
比较和交换
序列中逆序的个数决定交换次数
平均逆序数量为C(n,2)/2 ,所以T(n)= 0(n2)
简单插入排序复杂度由什么决定? 逆序个数
如何改进简单插入排序复杂度?
●分组,比如C(n,2)/2> 2C((n/2),2)/2
●3,2,1有3组逆序对(3,1) (3,2) (2,1)需要交换3次。但相隔较远的
3,1交换一次后1,2,3就没有逆序对了。
●基本有序的插入排序算法复杂度接近O(n)
三,希尔排序(缩小增量法)
1,基本思想:
分割成若干个较小的子文件,对各个子文件分别进行直接插入排序,当文件达到基本有序时,再对整个文件进行一-次直接插入排序。
对待排记录序列先作“宏观”调整,再作“微观”调整。
"宏观"调整,指的是,"跳跃式”的插入排序。
2,排序过程:
首先将记录序列分成若干子序列,然后分别对每个子序列进行直接插入排序,最后待基本有序时,再进行一次直接插入排序
例如:将n个记录分成d个子序列:
{ R[1],R[1+d], R[1+2d], .. R[1+kd]}
{ R[2], R[2+d], R[2+2d], ... R[2+kd]}
{ R[d], R[2d], R[3d], .... R[kd], R[(k+1)d] }
其中,d称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后一趟排序减
为1。
void shellsort(JD r[],int n,intd[],int T) { int i.j,k; JD X; k=0; //循环每 一趟进行分组,组内进行简单插入排序 while(k<T) { for(i=d[k]+1;i<=n;i++) //i为未排序记录的位置 { x=r[i]; j=i-d[k]//j为本组i前面的记录位置 while((j>0)&&(x.key<r[j].key)) //组内简单插入排序 { r[j+d[k]]=r[j]; j=jd[k]; } r[j+d[k]]=x; } k++; }
3,排序特点:
●子序列的构成不是简单的”逐段分割”,而是将相隔某个增量的记录组成一个子序列
●希尔排序可提高排序速度,因为分组后n值减小,n2更小,而T(n)=O(n2),所以T(n)从总体上看是减小了关键字较小的记录跳跃式前移,在进行最后一趟增量为1的插入排序时,序列已基本有序
●增量序列取法
无除1以外的公因子
最后一个增量值必须为1
希尔排序算法本身很简单,但复杂度分析很复杂.他适合于中等数据量大小的排序(成千上万的
数据量)
4,性能分析:
四,选择排序
1,基本思想:
从无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
假设排序过程中,待排记录序列的状态为:
2,排序过程:
●首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与
第一个记录交换
●再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与
第二个记录交换
●重复上述操作,共进行n-1趟排序后,排序结束
3,代码实现:
void smp_ selesort(JD r[], int n){ int i,j,k; JD x; for (i = 1;i<n;i++){ k=i; for (j =i+ 1;j <= n;j++) if (r[j]. key<r[k].key) k = j; if (i != k){ x = r[i]; r[i] = r[k]; r[k] = x; } } }
4,算法评价:
稳定性分析:
堆排序:
void Heap Sort ( ElementType A[],int N ) { BuildHeap(A) ; /* O(N) */ for ( i=0; i<N; i++ ) TmpA[i] = DeleteMin(A) ; /* O(1ogN) */ for ( i=0; i<N; i++ ) /* O(N) */ A[i] = TmpA[i] ; }
void Heap Sort ( ElementType A[],int N ) { for ( i=N/2-1; i>=0; i-- ) /* BuildHeap */ PercDown( A,i, N ) ; for ( i=N-1; i>0; i-- ) { Swap( &A[0] ,&A[i] ) ; /* DeleteMax */ PercDown( A,0,i ) ; } }
五,归并排序
1,合并两个有序表
归并——将两个或两个以上的有序表组合成一个新的有序表,叫归并排序
2-路归并排序
排序过程
设初始序列含有n个记录,则可看成n个有序的子序列,每个子序列长度为1
两两合并,得到n/2个长度为2或1的有序子序列
再两两合并,...如此重复,直至得到一个长度为n的有序序列为止
迭代算法:
将序列的每一个数据看成一个长度为1的有序表,然后,将相邻两组进行归并得到长度为2的有序表(一 趟归并)再对相邻两组长度为2的有序表进行下一趟归并得到长度为4的有序表,这样一直进行下去,直到整个表归并成有序表。如果某一趟归并过程中,单出一个表,该表轮空,等待下一趟归并。
递归思想
将无序序列划分成大概均等的2个子序列,然后用同样的方法对2个子序列进行归并排序得到2个有序的子序列,再用合并2个有序表的方法合并这2个子序列,得到n个元素的有序序列。
void MSort( ElementType A[ ] ElementType TmpArray[ ] ,int Left, int Right ) { int Center; if( Left< Right ) { /* 待排序的数据在数组的下标位置*/ Center=(Left十Right)/2; MSort( A, TmpArray, Left, Center ); /*T( N / 2) */ MSort( A, TmpArray, Center十1, Right ); /*T(N/2)*/ Merge( A, TmpArray, Left, Center + 1, Right ); /* O( N ) */ } } void Mergesort( ElementType A[ ],int N ) { ElementType *TmpArray; /* need O(N) extra space */ TmpArray = malloc( N* sizeof( ElementType ) ); if ( TmpArray != NULL) { MSort( A, TmpArray, 0,N-1 ); free( TmpArray ); } else FatalError( "No space for tmp array!!" ); }
/* Lpos = start of left half, Rpos = start of right half */ void Merge( ElementType A[ ] ElementType TmpArray[ ],int Lpos, int Rpos, int RightEnd ) { int i, LeftEnd, NumElements, TmpPost LeftEnd = Rpos- 14; TmpPos = Lpos; NumElements = RightEnd- Lpos十1; while( Lpos <= LeftEnd && Rpos <= RightEnd ) /* main loop */ if(A[Lpos]<=A[Rpos]) TmpArray[ TmpPos++ ]三A[ Lpos++ ]; else TmpArray[ TmpPos++ ] 3 A[ Rpos++ ]; while( Lpos <= LeftEnd ) /* Copy rest of first half */ TmpArray[ TmpPos++ ] a A[ Lpos++ ]; while( Rpos <= RightEnd ) /* Copy rest of second half * / TmpArray[ TmpPos++ ] = A[ Rpos++ ]; for( ie 0; i < NumElements; i+ +, RightEnd-- ) /* Copy TmpArray back */ { A[ RightEnd ] = TmpArray[ RightEnd ]; printf(%od " A[RightEnd]); } }
2,算法评价:
时间复杂度:每-趟归并的时间复杂度为O(n),总共需要归并log2n趟,因而,总的时间复杂度为O(nlog2n)。
空间复杂度: 2-路归并排序过程中, 需要一个与表等长的存储单
元数组空间,因此,空间复杂度为O(n)。
六,交换排序
通过"交换"无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
冒泡排序算法评价:
冒泡排序的改进:
快速排序算法评价:
算法特点:
●快速排序算法是不稳定的
对待排序序列49 49' 38 65,
快速排序结果为: 38 49' 49 65
●快速排序的性能跟初始序列中关键字的排列和选取的枢纽有关
●当初始序列按关键字有序(正序或逆序)时,性能最差,蜕化为冒泡
排序,时间复杂度为O(n2)
●常用“三者取中”法来选取划分记录,即取首记录r[s].key.尾记录r[t].key和中间记录r[(S+ t)/2].key三者的中间值为划分记录。
●快速排序算法的平均时间复杂度为O(nlogn)
七,基数排序
多关键字排序:
设序列{R1, R..... R},关键字有d位,
关键字(K0, k1.... Kd-1)有序是指:
对于序列中任意两个数据R;和R;(1≤i<j≤n)
都满足: (K:, K....K.-1)<. (K:0, K....K.-1)
其中:
K°称为"最主”位关键字,Kd-1称为 "最次” 位关键字
多关键字排序方法分为:
高位优先多关键字排序
低位优先多关键字排序
高位优先多关键字排序
先对K进行排序,并按K0的不同值将记录序列分成若干子序列之后,分别对K1进行排序,....依次类推,直至对最次位关键字Kd-1排序完成为止。
低位优先多关键字排序
首先按关键字Kd- 1进行排序,
然后按关键字Kd-2进行排序,。。。。依次类推,直至最后对最主位关键字K排序完成为止。
两种排序方法实现上有什么区别?哪种方法更好?为什么?