交换排序算法

引言

定义

排序 :将一组无需的记录序列调整为有序的记录序列
排序算法:一种能将一串记录序列按照某种特定的方式进行调整的一种方法

格式

void X_sort(ElementType A[], int N)
  • X : 排序算法名
  • N :正整数,需要排序的元素个数
  • 稳定性: 任意两个相等的数据,排序前后的相对位置不发生改变。

没有一种排序算法在任何情况下是最优的,必须根据实际情况选择最优的算法解决问题。

交换排序

冒泡排序

算法思想

对于n个待排序的元素,算法共进行n-1次循环。每次循环完成,最大的元素归位,排在最后面。
在第k次循环时,将第n+1-k大的元素排好。具体实现如下:
从前往后依次比较相邻的两个元素(1~n-k),如果前一个元素比后一个元素大,则交换两个元素的位置。这样,就得到了这n+1-k个元素中最大的元素。

优化:当这一趟排序没有任何元素交换时,说明序列已经全部有序,不需要进行下一趟排序,可直接结束。
增加一个flag标记:检查这一趟排序有没有元素需要交换。

算法模拟

给出一组待排序的数:44 12 59 36 62 43

排序前441259366243
第一趟排序124436594362
第二趟排序123644435962
第三趟排序123643445962
第四趟排序123643445962
第五趟排序123643445962

举例:
第一趟排序:

比较是否交换序列
44 > 1212 44 59 36 62 43
44 < 5912 44 59 36 62 43
59 > 3612 44 36 59 62 43
59 < 6212 44 36 59 62 43
62 > 4312 44 36 59 43 62
算法实现
/* 交换两个元素 */
void swap(ElementType * a, ElementType * b)
{
	ElementType t = *a;
	*a = *b;
	*b = t;
}

/* 冒泡排序 */
void Bubble_sort(ElementType A[], int N)
{
	bool flag;
	for( int i = N-1; i > 0; i--) /* 进行N-1趟排序 */
	{
		flag = true; /* 每趟排序前设置flag,标记这趟排序是否发生了交换*/
		for( int j = 0; j < i; j++) /* 对0~i个元素进行排序 */
		{
			if(A[j] > A[j+1]) /* 如果前一个元素大于后一个元素,交换 */
			{
				swap( &A[j],&A[j+1]);
				flag = false;
			}
	    }
	    if(flag == true) /* 如此趟排序没有任何元素交换,退出排序 */
	    {
	    	break;
	    }
	 }
时间复杂度分析
  • 最坏情况:
    当序列是逆序排列时,每次比较都要进行交换。
    此时,共进行(n-1) + (n-2) + … + 2 + 1 = n(n-1)/2 次。
    时间复杂度为O(n2

  • 最好情况:
    当序列已经排好时,只需第一次比较n-1次,flag标记为true,退出循环。
    时间复杂度为O(n)

稳定性分析

是稳定的。
仅当前一个元素大于后一个元素时,才会进行交换。两个元素相等时,相对位置不会发生改变。

优缺点
  • 优点: 当需要排序的元素存储在链表中时,也能很好地实现排序(因为只用比较两个相邻的元素)。
  • 缺点:时间复杂度太高。



快速排序

算法思想(分而治之)

从未排序的元素中选出一个“主元”。以该“主元”作为基准,将比“主元”小的元素放在“主元”的左边,比“主元”大的元素放在“主元”的右边,得到两个子序列。分别对两个子序列递归地执行此过程,直至只剩一个元素。

1) 选择一个主元,与最后的元素进行交换
2)设置指针low和high分别指向第一个元素和倒数第二个元素
3)low从左至右扫描,找到一个大于等于主元的元素停止;
      high从右至左扫描,找到一个小于等于主元的元素停止;
      交换这两个元素的位置。
4)当low >= high时,停止扫描,将A[low]与主元交换。
5)递归地分别对主元左边的子序列、主元右边的子序列进行排序。

如果有元素正好等于主元?
1. 停下来交换
考虑所有元素都相等的情况,则每遇到一个元素都要停下来交换。好处是形成n/2和n/2的划分,时间复杂度为O(nlogn),坏处是进行了很多次没有意义的交换。
2. 忽视,继续移动指针
同样考虑所有元素都相等的情况,则low从左至右一直扫描到主元,形成一个n-1和1的划分,好处是不用进行多余的交换,坏处是这将导致时间复杂度为O(n2)

如果待排序的规模比较小时,由于递归,快速排序的效果不如插入排序。
解决方法:在递归过程中检查当前子问题的规模,当其小于某个阈值时,不再继续递归,直接调用插入排序。

主元的选取
一种比较好的选取方法,取第一个元素(A[low])、最后一个元素(A[high])和中间的元素(A[low+high]/2)中的中值。

算法模拟

给出一组待排序的数:44 12 59 36 62 43
第一趟排序:
A[0] = 44, A[5] = 43,A[0+5/2] = 59
A[5] < A[0] < A[2],主元选取A[0] = 44。
将主元A[0]与A[5]交换。

排序前431259366244
lowhigh基准
第一次交换431259366244
lowhigh基准
第一次交换结果431236596244
lowhigh基准
交换A[low]和主元431236596244
low、high基准
交换A[low]和主元结果431236446259
low、high
算法实现
/* 选取基准 */
ElemntType Median3(ElementTypr A[], int left, int right)
{
	int center = (left+right)/2;
	/* 对A[left],A[center],A[right] 排序*/
	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] ,只需要对A[left+1]~A[right-1]进行排序 */
	
	/*以A[center]作为基准 */
	Swap(&A[center],&A[right - 1]);
	return A[right - 1];
}

/*对A[left] ~ A[right] 元素进行快排*/
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;/*对A[lef+1]~A[right-2]个元素进行快排*/
		while(1)
		{
			while(A[++low] < pilot); /*找到大于等于pilot的元素*/
			while(A[--high] > pilot);/*找到小于等于pilot的元素*/
			if(low < high)
			{
				Swap(&A[low],&A[high]);/*交换两个元素的位置*/
			}
			else
				break; /*当low == right时,退出*/
		 }
		 Swap(&A[low],&A[right-1]);
		 Qsort(A,left,low-1);/*对基准左边序列排序*/
		 Qsort(A,low+1,high);/*对基准右边序列排序*/
	 }
	 else/*元素过少,选择插入排序*/
	 {
	 	Insertion_sort(A+left,right-left+1);
	 }
}

/*统一接口*/
void Quik_sort(ElementType A[],int N)
{
	Qsort(A,0,N-1);
}
时间复杂度分析
  • 最坏情况:
    当每次划分都近似于1和N-1时,有(N-1) + (N-2) + … +2 +1 = N(N-1)/2
    时间复杂度为O(n2)
    -最好情况:
    每次划分都将原序列分成两个等长的子序列,每一递归层次上为O(n),递归深度为O(log2n)。
    时间复杂度为O(nlog2n)
稳定性分析

不稳定的。
对于比主元小的元素,它可能被交换到和它等值的元素以前;
对于比主元大的元素,它可能被交换到和它等值的元素以后。
导致相对位置发生改变。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值