几种排序的实现与对比/C语言
下面是我整理的几种排序。这个是我的学习笔记。
1.冒泡排序 2.插入排序 3.选择排序
4.快速排序 5.堆排序 6.归并排序
7.希尔排序
平均效率 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 | |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n)~O(n) | 不稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
希尔排序 | O(n log n) ~ O(n²) | O(n¹•³) | O(n²) | O(1) | 不稳定 |
1.冒泡排序
冒泡就是将数字向后一位比如果大就向后移一位。C语言实现如下。一个嵌套的for循环,内循环总是从0下标开始,将最大的数浮到length-1-i处,所以外循环了length-1次。可以得出他的平均时间复杂度为O(n²) 。
//冒泡,排升序
void sort(int nums[],int length){
int temp;
for(int i=0;i<length-1;i++){
for(int j=0;j<length-1-i;j++){
if(nums[j]>nums[j+1]){//比较相邻元素,将大的向后移动一位
temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
}
记住这张图基本就知道冒泡的原理了,以后再也不怕冒泡了。
2.插入排序
插入排序原理也比较直接,就是构建一个有序链的过程。让有序列从无到有,一点点长大。可以把插入序列记为把数据插入的一个有序序列中。
示图:
3.选择排序
选择排序就是将最小的数字移到前面。一个嵌套循环,内循环起始在i+1截止到 lenght-1 。选择排序外循环一次,只用交换一次数据。内循环只需记住最小数的下标就可以了。
他的最优和最坏时间浮渣度都是O(n²)。
//选择排序
void sort(int nums[],int length){
int temp,minIndex;
for(int i=0;i<length-1;i++){
minIndex = i;//其实设置初始最小的
for(int j=i+1;j<length;j++)
if(nums[j]<nums[minIndex])//比较[minIndex]元素,小的记住下标
minIndex=j;
if(i != minIndex){//将最小数,换到i处
temp=nums[minIndex];
nums[minIndex]=nums[i];
nums[i]=temp;
}
}
}
示图:
4.快速排序
1,分而治之,通过主元做子集划分。
2,主元的选取影响着排序的稳定
/* 快速排序 */
ElementType Median3( ElementType A[], int Left, int Right )
{
int Center = (Left+Right) / 2;
if ( A[Left] > A[Center] )
Swap( &A[Left], &A[Center] );
if ( A[Left] > A[Right] )
Swap( &A[Left], &A[Right] );
if ( A[Center] > A[Right] )
Swap( &A[Center], &A[Right] );
/* 此时A[Left] <= A[Center] <= A[Right] */
Swap( &A[Center], &A[Right-1] ); /* 将基准Pivot藏到右边*/
/* 只需要考虑A[Left+1] … A[Right-2] */
return A[Right-1]; /* 返回基准Pivot */
}
void Qsort( ElementType A[], int Left, int Right )
{ /* 核心递归函数 */
int Pivot, Cutoff, Low, High;
if ( Cutoff <= Right-Left ) { /* 如果序列元素充分多,进入快排 */
Pivot = Median3( A, Left, Right ); /* 选基准 */
Low = Left; High = Right-1;
while (1) { /*将序列中比基准小的移到基准左边,大的移到右边*/
while ( A[++Low] < Pivot ) ;
while ( A[--High] > Pivot ) ;
if ( Low < High ) Swap( &A[Low], &A[High] );
else break;
}
Swap( &A[Low], &A[Right-1] ); /* 将基准换到正确的位置 */
Qsort( A, Left, Low-1 ); /* 递归解决左边 */
Qsort( A, Low+1, Right ); /* 递归解决右边 */
}
else InsertionSort( A+Left, Right-Left+1 ); /* 元素太少,用简单排序 */
}
void QuickSort( ElementType A[], int N )
{ /* 统一接口 */
Qsort( A, 0, N-1 );
}
示图:图不太清晰,但比较直观,选取的主元是53.
性质:最好情况:T=O(NlogN)最坏情况:T=O(N²)
5.堆排序
从原理上来说是对选择排序的一种改进。但是学过数据结构后面的堆之后,堆排序不难。就是通过最大(最小)推每次找到最大值(最小值),然后不断弹出最大值(最小值)得到有序序列。
C语言实现
void Swap( ElementType *a, ElementType *b )
{
ElementType t = *a; *a = *b; *b = t;
}
void PercDown( ElementType A[], int p, int N )
{ /* 改编代码4.24的PercDown( MaxHeap H, int p ) */
/* 将N个元素的数组中以A[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = A[p]; /* 取出根结点存放的值 */
for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
Child = Parent * 2 + 1;
if( (Child!=N-1) && (A[Child]<A[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= A[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
A[Parent] = A[Child];
}
A[Parent] = X;
}
void HeapSort( ElementType A[], int N )
{ /* 堆排序 */
int i;
for ( i=N/2-1; i>=0; i-- )/* 建立最大堆 */
PercDown( A, i, N );
for ( i=N-1; i>0; i-- ) {
/* 删除最大堆顶 */
Swap( &A[0], &A[i] ); /* 见代码7.1 */
PercDown( A, 0, i );
}
}
性质:T(N)=O(NlogN),但它要额外一个O(N)的在这里插入代码片
空间,并且复制元素需要时间。
6.归并排序
核心:有序子序列的合并
/* 归并排序 - 递归实现 */
/* L = 左边起始位置, R = 右边起始位置, RightEnd = 右边终点位置*/
void Merge( ElementType A[], ElementType TmpA[], int L, int R, int RightEnd )
{ /* 将有序的A[L]~A[R-1]和A[R]~A[RightEnd]归并成一个有序序列 */
int LeftEnd, NumElements, Tmp;
int i;
LeftEnd = R - 1; /* 左边终点位置 */
Tmp = L; /* 有序序列的起始位置 */
NumElements = RightEnd - L + 1;
while( L <= LeftEnd && R <= RightEnd ) {
if ( A[L] <= A[R] )
TmpA[Tmp++] = A[L++]; /* 将左边元素复制到TmpA */
else
TmpA[Tmp++] = A[R++]; /* 将右边元素复制到TmpA */
}
while( L <= LeftEnd )
TmpA[Tmp++] = A[L++]; /* 直接复制左边剩下的 */
while( R <= RightEnd )
TmpA[Tmp++] = A[R++]; /* 直接复制右边剩下的 */
for( i = 0; i < NumElements; i++, RightEnd -- )
A[RightEnd] = TmpA[RightEnd]; /* 将有序的TmpA[]复制回A[] */
}
void Msort( ElementType A[], ElementType TmpA[], int L, int RightEnd )
{ /* 核心递归排序函数 */
int Center;
if ( L < RightEnd ) {
Center = (L+RightEnd) / 2;
Msort( A, TmpA, L, Center ); /* 递归解决左边 */
Msort( A, TmpA, Center+1, RightEnd ); /* 递归解决右边 */
Merge( A, TmpA, L, Center+1, RightEnd ); /* 合并两段有序序列 */
}
}
void MergeSort( ElementType A[], int N )
{ /* 归并排序 */
ElementType *TmpA;
TmpA = (ElementType *)malloc(N*sizeof(ElementType));
if ( TmpA != NULL ) {
Msort( A, TmpA, 0, N-1 );
free( TmpA );
}
else printf( "空间不足" );
}
/* 归并排序 - 循环实现 */
/* 这里Merge函数在递归版本中给出 */
/* length = 当前有序子列的长度*/
void Merge_pass( ElementType A[], ElementType TmpA[], int N, int length )
{ /* 两两归并相邻有序子列 */
int i, j;
for ( i=0; i <= N-2*length; i += 2*length )
Merge( A, TmpA, i, i+length, i+2*length-1 );
if ( i+length < N ) /* 归并最后2个子列*/
Merge( A, TmpA, i, i+length, N-1);
else /* 最后只剩1个子列*/
for ( j = i; j < N; j++ ) TmpA[j] = A[j];
}
void Merge_Sort( ElementType A[], int N )
{
int length;
ElementType *TmpA;
length = 1; /* 初始化子序列长度*/
TmpA = malloc( N * sizeof( ElementType ) );
if ( TmpA != NULL ) {
while( length < N ) {
Merge_pass( A, TmpA, N, length );
length *= 2;
Merge_pass( TmpA, A, N, length );
length *= 2;
}
free( TmpA );
}
else printf( "空间不足" );
}
示图:
7.希尔排序(shell)
关键点:步长。
主要思想:
1, 先做D间隔的排序
2,然后考虑下一个x间隔排序
3.····
首先定义一个增量序列Dм-₁>Dм-₂>k…>D₁=1
然后对每个Dk进行Dk间隔的排序
C语言实现
void ShellSort( ElementType A[], int N )
{ /* 希尔排序 - 用Sedgewick增量序列 */
int Si, D, P, i;
ElementType Tmp;
/* 这里只列出一小部分增量 */
int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
for ( Si=0; Sedgewick[Si]>=N; Si++ )
; /* 初始的增量Sedgewick[Si]不能超过待排序列长度 */
for ( D=Sedgewick[Si]; D>0; D=Sedgewick[++Si] )
for ( P=D; P<N; P++ ) { /* 插入排序*/
Tmp = A[P];
for ( i=P; i>=D && A[i-D]>Tmp; i-=D )
A[i] = A[i-D];
A[i] = Tmp;
}
}
性质:在经行下一步的Dk间隔的排序后,原来的已经排好的仍然有序。
在最坏的情况下T=O(N²)
1,Hibbard增量序列。
Dk=2^k-1(相邻的元素是互质的)
最坏的情况:T=O(N^(3/2))
2,Sedgewick增量序列。
{1,5,19,109}