排序算法总结附代码(冒泡、选择、插入、希尔、归并、快排)

最近,看数据结构,系统的学了一遍各种排序算法,抽点空总结出来。

排序算法总结附代码(冒泡、选择、插入、希尔、归并、快排)

堆排序和基数排序,后面再总结一下。。。。

 

冒泡排序:  从前向后扫描,依次比较相邻两个元素,若前者大,则交换位置。第一趟下来,最大的元素必在最后的位置。

                 (本质问题:有多少个逆序对,就需要交换多少次,每次只能消除一个逆序对)

        特点:最坏情况 O(N^2),优点是可以对链表数据排序, 稳定(不会交换数值相同元素的顺序)。

        代码:

void Bubble_sort ( ElementType A[], int N) 	//冒泡排序 
{
	int i,p;
	int flag;    //flag 用于判断,如果某一次的冒泡过程中,已排好序,则跳出排序 
	for( p=N-1; p>0; p--) {
		flag = 0;			
		for( i=0; i<p; i++)
			if( A[i] > A[i+1] ) {
				Swap(&A[i] , &A[i+1]);
				flag = 1;		//发生了交换 
			}
		if (flag == 0) break;	//未发生交换,说明已排好序; 
	}
}

 

 

选择排序:每次扫描未排好序的元素,找出最小的元素,放在已排好序的后面。速度的瓶颈在于查找最小元素。

         特点:最坏情况 O(N^2),不稳定

         代码:

int ScanForMin(int list[], int i, int n) //查找最小数的位置下标 
{
	int Min_position = i;
	int j;
	for ( j=i; j<n; j++){
		if( list[j] < list[Min_position] )
			Min_position = j;
	}
	return Min_position;
}

void Selection_Sort( ElementType A[], int N) //选择排序算法,每次在未排序的数列中选择一个最小
                                             //的,粘在已排好序的数组后面 
{
	int i;
	int MinPosition;
	for ( i=0; i<N; i ++) {
		MinPosition = ScanForMin( A, i, N );  //算法速度的瓶颈,在于查找 
		Swap( &A[i], &A[MinPosition] );	      //交换步骤O(N) 
	}
}

插入排序:模拟抓牌的过程,抓起一张牌,与前面的进行比较,插入到前面已排好序相应的位置(最后一次插入之前,可能所有的 元素都未在正确位置)

                (本质问题同冒泡排序一样:有多少个逆序对,就需要交换多少次,每次只能消除一个逆序对)

       特点最坏情况 O(N^2), 稳定。

       代码:

void Insertion_sort ( ElementType A[], int N) //插入排序 
{
	int i,p,Tmp;
	for( p=1; p<N; p++){
		Tmp = A[p];	//摸下一张牌 ,然后与之前的牌比较 
		for( i=p; i>0 && A[i-1]>Tmp; i--) 
			A[i] = A[i-1];	//移出空位 
		A[i] = Tmp;		//新牌落位 
	}
 } 

 

希尔排序:在插入排序的基础上,每次选择要插入的数据有一定间隔,然后间隔从大到小,最后一趟间距为1,就是原插入排序

      (根本目标,在于每次交换尽可能消除更多的逆序对,因为间隔交换数据,势必会减少很多逆序对 ,核心是希尔增序序列)

      特点:最坏情况 thet (N^(3/2 )),但不稳定

      代码

void Shell_sort ( ElementType A[] , int N) //希尔排序  (不稳定) 
{
	int i,p,Tmp,D;
	for( D=N/2; D>0; D/=2) { //希尔增量序列 ,这里可以定义不同的增量序列 
				//Hibbard增量序列,Dk= 2^k -1 ,最坏情况 thet(N^(3/2 ))
		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;
		}
	}
 } 
 
//void Shell_sort( 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;
//         }
//}

归并排序:将序列不停的分割下去,直到2个,直接比较交换位置(然后,再两两合并。有两种算法,一种是递归算法,一种是非递归算法。

特点:平均复杂度 NlogN , 稳定,最坏复杂度也是NlogN,而且还是稳定的 ,算法千好万好,唯独需要开个内存空间, 来回倒元素 ,一般不用于内排序,多用于外排序 (内存装不下全部的数据,需要借用外部存储空间进行排序)

代码:

///////////*****递归的归并排序****/////////////////
 
/* L = 左边起始位置,R = 右边起始位置, RightEnd= 右边重点位置 */
void Merge ( ElementType A[], ElementType TmpA[],		//归并函数 
			int L, int R, int RightEnd )
{
	int LeftEnd = R - 1;
	int Tmp = L;
	int NumElements = RightEnd - L + 1;
	while ( L <= LeftEnd && R <= RightEnd ) {
		if( A[L] <= A[R] ) 	TmpA[Tmp++] = A[L++];
		else				TmpA[Tmp++] = A[R++];
	} 
	while ( L <= LeftEnd )
		TmpA[Tmp++] = A[L++];
	while (R <= RightEnd )
		TmpA[Tmp++] = A[R++];
	int i;
	for ( i=0; i< NumElements; i++, RightEnd--)
		A[RightEnd] = TmpA[RightEnd];
}

///////递归排序函数,递归到最小单元:一单元一个数,相邻归并成两个数,一次类推 
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 Merge_sort ( ElementType A[], int N)
{
	ElementType *TmpA;
	TmpA = malloc( N * sizeof( ElementType ) );
	if ( TmpA != NULL ) {
		MSort( A, TmpA, 0, N-1);
		free ( TmpA );
	}
	else printf("ERROR 空间不足"); 
}






///////////*****非递归的归并排序****/////////////////
void Merge1 ( ElementType A[], ElementType TmpA[],		//归并函数 
			int L, int R, int RightEnd )
{
	int LeftEnd = R - 1;
	int Tmp = L;
	int NumElements = RightEnd - L + 1;
	while ( L <= LeftEnd && R <= RightEnd ) {
		if( A[L] <= A[R] ) 	TmpA[Tmp++] = A[L++];
		else				TmpA[Tmp++] = A[R++];
	} 
	while ( L <= LeftEnd )
		TmpA[Tmp++] = A[L++];
	while (R <= RightEnd )
		TmpA[Tmp++] = A[R++];
	/////与Merge唯一不同的地方就是 不需要将TmpA 倒回至 A; 
}
///////非递归排序算法
void Merge_pass( ElementType A[], ElementType TmpA[], int N, int length)
{
	int i,j;
	for ( i=0; i<= N-2*length; i+= 2*length )
		Merge1( A, TmpA, i, i+length, i+2*length-1 );
	if ( i+length < N )
		Merge1( A, TmpA, i, i+length, N-1);
	else	
		for ( j=i; j<N; j++ ) TmpA[j] = A[j];
 } 
 

////////排序接口函数/////// 
void Merge1_sort ( ElementType A[], int N )
{
	int length = 1;
	ElementType *TmpA;
	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 ); //为了保证排好序的数据一定在A中 
			length *=2;
		}
		free ( TmpA );
	}
	else printf("ERROR 空间不足"); 
}

快速排序:首先随机选择一个主元pivot,从两边向中间扫描,满足条件(前面大于pivot,后面的小于pivot)时,互换位置,每扫描一次,主元的位置就固定好了,左边都是小于自己的元素,右边都是大于自己的元素 ,然后左右两边分别递归调用这个函数,直到各个小序列剩2个元素,根据大小互换位置(或者当序列长度小于某个值cutoff时,对这个序列采用插入排序即可)

特点:目前应用较广泛的算法,平均时间NlogN,因为递归,需要空间logN,速度快,核心在于如何确定主元pivot,但不稳定。选主元的一种方法:选三个元素:第一个,中间的,最后一个,将三个数的中位数作为主元,并放在序列最后位置 

代码:

void Insertion_sort ( ElementType A[], int N) //插入排序 
{
	int i,p,Tmp;
	for( p=1; p<N; p++){
		Tmp = A[p];		//摸下一张牌 ,然后与之前的牌比较 
		for( i=p; i>0 && A[i-1]>Tmp; i--) 
			A[i] = A[i-1];		//移出空位 
		A[i] = Tmp;				//新牌落位 
	}
 } 
 
////选主元的一种方法:选三个元素:第一个,中间的,最后一个,将三个数的中位数作为主元,并放在序列
///最后位置 
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,后面的小于pivot)时,互换位置 
void Qsort( ElementType A[], int Left, int Right )
{
	int Cutoff = 2;	//阈值,快排递归中,当序列长度小于cutoff时,采用简单排序(插入) 
	int pivot,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
		Insertion_sort( A+Left, Right-Left+1 );
}

void QuickSort( ElementType A[], int N)
{
	Qsort( A, 0, N-1);
}