1 常见排序概念
排序稳定性:假设关键字ki = kj,且在排序前的序列中ri领先于rj,如果排序后ri仍领先于rj,则称所用的排序算法为稳定的。若有可能使排序后rj领先于ri,则排序算法是不稳定的。 内排序:在整个排序过程中,待排序的所有记录都被放置在内存中。 外排序:排序过程中由于排序的记录个数太多,不能同时放置在内存中,整个排序过程需要在内外存之间多次交换数据才能进行。 我们主要研究内排序,内排序排序算法性能受三方面影响 (1)时间性能。内排序主要进行两种操作,比较和移动。 (2)辅助空间。 (3)算法的复杂性 内排序主要分为四类: (1)选择排序类(简单选择排序、堆排序) (2)交换排序类(冒泡排序、快速排序) (3)插入排序类(直接插入排序、希尔排序) (4)归并排序类 如果待排序列总是基本有序,不应该考虑四种复杂的改进算法。 若待排序个数n较小,采用简单排序方法较为合适。四种复杂的改进算法适用于n较大的情况。 快速排序算法是20世纪十大算法之一,经过改进后可称之为性能最好的排序算法。
# define MAXSIZE 10
typedef struct {
int r[ MAXSIZE] ;
int length;
} SqList;
void swap ( SqList* L, int i, int j)
{
int temp = L-> r[ i] ;
L-> r[ i] = L-> r[ j] ;
L-> r[ j] = temp;
}
2 选择排序类
2.1 简单选择排序
基本思想是每一趟在n - i + 1个记录中选取关键字最小的记录作为有序序列的第i个记录。 最大的特点是移动数据次数相当少,就节约了相应的时间。 时间复杂度为O(n^2),但性能上优于冒泡排序。
void selectSort ( SqList* L)
{
int min = 0 ;
for ( int i = 0 ; i < L-> length; ++ i)
{
min = i;
for ( int j = i + 1 ; j < L-> length; ++ j)
{
if ( L-> [ min] > L-> [ j] )
min = j;
}
if ( i != min)
swap ( L, i, min) ;
}
}
2.2 堆排序
堆是具有下列性质的完全二叉树,每个结点的值都大于或等于其左右孩子结点的值称为大顶堆,每个结点的值都小于或等于其左右孩子结点的值称为小顶堆。 堆排序是对简单选择排序的一种改进,基本思想是将待排序的序列构造成一个大顶堆,将根结点与末尾元素交换,然后再将剩余的n-1个序列重新构造成一个堆,如此反复即可。 是一种不稳定的排序算法,时间复杂度为O(nlogn) 不适用于待排序元素较少的情况。
void heapSort ( SqList* L)
{
for ( int i = L-> length / 2 - 1 ; i >= 0 ; -- i)
heapAdjust ( L, i, L-> length) ;
for ( int i = L-> length - 1 ; i > 0 ; -- i)
{
swap ( L, 0 , i) ;
heapAdjust ( L, 0 , i - 1 ) ;
}
}
void heapAdjust ( SqList* L, int s, int m)
{
int temp = L-> r[ s] ;
for ( int j = 2 * s; j <= m; j *= 2 )
{
if ( j < m && L-> r[ j] < L-> r[ j + 1 ] )
++ j;
if ( temp >= L-> r[ j] )
break ;
L-> r[ s] = L-> r[ j] ;
s = j;
}
L-> r[ s] = temp;
}
3 交换排序类
3.1 冒泡排序
是一种交换排序,基本思想是两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止,比较多次交换多次 。 时间复杂度O(n^2)
void bubbleSort0 ( SqList* L)
{
for ( int i = 0 ; i < L-> length; ++ i)
{
for ( int j = i + 1 ; j < L-> length; ++ j)
{
if ( L-> r[ i] > L-> r[ j] )
swap ( L, i, j) ;
}
}
}
void bubbleSort1 ( SqList* L)
{
for ( int i = 0 ; i < L-> length; ++ i)
{
for ( int j = L-> length - 1 ; j > i; -- j)
{
if ( L-> r[ j] > L-> r[ j - 1 ] )
swap ( L, j, j - 1 ) ;
}
}
}
void bubbleSort2 ( SqList* L)
{
bool flag = true;
for ( int i = 0 ; i < L-> length && flag == true; ++ i)
{
flag = false;
for ( int j = L-> length - 1 ; j > i; -- j)
{
if ( L-> r[ j] > L-> r[ j - 1 ] )
{
flag = true;
swap ( L, j, j - 1 ) ;
}
}
}
}
3.2 快速排序
基本思想是通过一趟排序将待排记录分割成独立两部分,其中一部分记录的关键字均比另一部分记录的关键字小,可对这两部分记录继续进行排序,以达到整个序列有序的目的。 通过增大记录的比较和移动的距离,减少总的比较次数和移动交换次数。 是一种不稳定的排序算法,时间复杂度为O(nlogn) 数据量大时较为有效,数据量小时不如直接插入排序性能更好。
void quickSort ( SqList* L)
{
qSort1 ( L, 0 , L-> length - 1 ) ;
}
void qSort1 ( SqList* L, int low, int high)
{
if ( low < high)
{
int pivot = partition1 ( L, low, high) ;
qSort1 ( L, low, pivot - 1 ) ;
qSort1 ( L, pivot + 1 , high) ;
}
}
int partition1 ( SqList* L, int low, int high)
{
int pivotkey = L-> r[ low] ;
while ( low < high)
{
while ( low < high && L-> r[ high] >= pivotkey)
-- high;
swap ( L, low, high) ;
while ( low < high && L-> r[ low] <= pivotkey)
++ low;
swap ( L, low, high) ;
}
return low;
}
int partition2 ( SqList* L, int low, int high)
{
int m = low + ( high - low) / 2 ;
if ( L-> r[ low] > L-> r[ high] )
swap ( L, low, high) ;
if ( L-> r[ m] > L-> r[ high] )
swap ( L, m, high) ;
if ( L-> r[ m] > L-> r[ low] )
swap ( L, low, m) ;
int pivotkey = L-> r[ low] ;
while ( low < high)
{
while ( low < high && L-> r[ high] >= pivotkey)
-- high;
swap ( L, low, high) ;
while ( low < high && L-> r[ low] <= pivotkey)
++ low;
swap ( L, low, high) ;
}
return low;
}
int partition3 ( SqList* L, int low, int high)
{
int pivotkey = L-> r[ low] ;
int temp = pivotkey;
while ( low < high)
{
while ( low < high && L-> r[ high] >= pivotkey)
-- high;
L-> r[ low] = L-> r[ high] ;
while ( low < high && L-> r[ low] <= pivotkey)
++ low;
L-> r[ high] = L-> r[ low] ;
}
L-> r[ low] = temp;
return low;
}
# define MAX_LENGTH_INSERT_SORT 7
void qSort2 ( SqList* L, int low, int high)
{
if ( ( high - low) > MAX_LENGTH_INSERT_SORT)
{
int pivot = partition1 ( L, low, high) ;
qSort2 ( L, low, pivot - 1 ) ;
qSort2 ( L, pivot + 1 , high) ;
}
else
insertSort ( L) ;
}
void qSort3 ( SqList* L, int low, int high)
{
while ( low < high)
{
int pivot = partition1 ( L, low, high) ;
qSort3 ( L, low, pivot - 1 ) ;
low = pivot + 1 ;
}
}
4 插入排序类
4.1 直接插入排序
基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。 性能优于冒泡排序和简单选择排序,序列已基本有序或记录数较小时很有效。 时间复杂度为O(n^2)
void insertSort ( SqList* L)
{
int minValue = 0 ;
for ( int i = 1 ; i < L-> length; ++ i)
{
if ( L-> r[ i] < L-> r[ i - 1 ] )
{
minValue = L-> r[ i] ;
for ( int j = i - 1 ; L-> r[ j] > minValue; -- j)
L-> r[ j + 1 ] = L-> r[ j] ;
L-> r[ j] = minValue;
}
}
}
4.2 希尔排序
是对直接插入排序的改进算法,基本思想是采用跳跃分割的策略,将相距某个增量的记录组成一个子序列,在子序列内分别进行直接插入排序后得到基本有序的序列。 增量序列的选取对该算法的性能是十分关键的,增量序列的最后一个增量值必须为1。 是一种不稳定的排序算法,时间复杂度为O(n^3/2)。
void shellSort ( SqList* L)
{
int increment = L-> length;
do {
increment = increment / 3 + 1 ;
int minValue = 0 ;
for ( int i = increment + 1 ; i < L-> length; ++ i)
{
if ( L-> r[ i] < L-> r[ i - 1 - increment] )
{
minValue = L-> r[ i] ;
for ( int j = i - 1 - increment; L-> r[ j] > minValue && j > 0 ; j-= increment)
L-> r[ j + 1 + increment] = L-> r[ j] ;
L-> r[ j + increment] = minValue;
}
}
} while ( increment > 1 )
}
5 归并排序
2路归并排序的基本思想是,假设初始序列含有n个记录,可以看成为n个有序的子序列,每个子序列长度为1,然后两两归并,如此重复,直至得到一个长度为n的有序序列为止。 是一种稳定的排序算法,时间复杂度为O(nlogn) 空间复杂度为O(n + logn),比较占用内存
void mergeSort ( SqList* L)
{
mSort ( L-> r, L-> r, 0 , L-> length - 1 ) ;
}
void mSort ( int * SR, int * TR1, int s, int t)
{
int TR2[ MAXSIZE] ;
if ( s == t)
TR1[ s] = SR[ s] ;
else
{
int m = ( s + t) / 2 ;
mSort ( SR, TR2, s, m) ;
mSort ( SR, TR2, m + 1 , t) ;
merge ( TR2, TR1, s, m, t) ;
}
}
void merge ( int * SR, int * TR, int i, int m, int n)
{
for ( int j = m + 1 , int k = i; i <= m && j <= n; ++ k)
{
if ( SR[ i] < SR[ j] )
TR[ k] = SR[ i++ ] ;
else
TR[ k] = SR[ j++ ] ;
}
if ( i <= m)
{
for ( int l = 0 ; l <= m - i; ++ l)
TR[ k + l] = SR[ i + 1 ]
}
if ( j <= n)
{
for ( int l = 0 ; l <= n - j; ++ l)
TR[ k + l] = SR[ j + l] ;
}
}
上面的归并排序大量引用了递归,会造成空间和时间上的损耗,可将递归转化为迭代,进一步提高性能 空间复杂度为O(n)
void mergeSort ( SqList* L)
{
int * TR = ( int * ) malloc ( sizeof ( int ) * L-> length) ;
int k = 0 ;
while ( k < L-> length)
{
mergePass ( L-> r, TR, k, L-> length) ;
k = 2 * k;
mergePass ( TR, L-> r, k, L-> length) ;
k = 2 * k;
}
free ( TR) ;
}
void mergePass ( int * SR, int * TR, int s, int n)
{
int i = 0 ;
while ( i < n - 2 * s + 1 )
{
merge ( SR, TR, i, i + s - 1 , i + 2 * s - 1 ) ;
i = i + 2 * s;
}
if ( i < n - s + 1 )
merge ( SR, TR, i, i + s - 1 , n) ;
else
for ( int j = i; j <= n; ++ j)
TR[ j] = SR[ j] ;
}