几种排序的实现与对比/C语言

几种排序的实现与对比/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}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值