内部排序 (三):交换排序 Exchange Sorting (冒泡排序、快速排序)

作为数据结构的课程笔记,以便查阅。如有出错的地方,还请多多指正!

冒泡排序 Bubble Sort

排序过程

思路:

  • 比较1st2nd记录的关键字,若逆序则交换;然后比较2nd3rd记录的关键字;……直至比较n-1thnth记录为止。完成第一趟冒泡排序,则关键字最大的记录被放置在最后一个位置上
  • 对前n-1个记录进行第二趟冒泡排序,将关键字次大的记录放置在倒数第二个位置上
  • 重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止

在这里插入图片描述

算法实现

void Bubble_sort(SqList_t* list)
{
	for (int i = 0; i < list->len - 1; ++i)
	{
		int flag = 0;

		for (int j = 1; j < list->len - i; ++j)
		{
			if (list->rec[j].key > list->rec[j + 1].key)
			{
				list->rec[0] = list->rec[j];
				list->rec[j] = list->rec[j + 1];
				list->rec[j + 1] = list->rec[0];
				flag = 1;
			}
		}
		if (0 == flag)
		{
			break;
		}
	}
}

算法评价

适用于元素较少,或初始序列基本有序

T(n)

冒泡排序最多执行 n − 1 n-1 n1趟。第 i i i趟排序需要比较 n − i n-i ni

若待排记录为从小到大排列(正序)

  • 比较次数: n − 1 n-1 n1
  • 移动次数: 0 0 0

若待排记录为从大到小排列

  • 比较次数: ∑ i = 1 n − 1 ( n − i ) = n 2 − n 2 \sum_{i=1}^{n-1} (n-i) = \frac {n^2-n}{2} i=1n1(ni)=2n2n
  • 移动次数: 3 ∑ i = 1 n − 1 ( n − i ) = 3 ( n 2 − n ) 2 3\sum_{i=1}^{n-1} (n-i)= \frac {3(n^2-n)}{2} 3i=1n1(ni)=23(n2n)

∴ T ( n ) = O ( n 2 ) \therefore T(n)=O(n^2) T(n)=O(n2)

S(n)

S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

是否稳定

  • 稳定

快速排序 Quick Sort

排序过程

思路

  • 在待排序记录中任取一个记录 (通常为第一个记录) ,作为枢轴(pivot),将其它记录分为两个子序列:
    • 子序列 1 中,记录的关键值 < < < 枢轴的关键值
    • 子序列 2 中,记录的关键值 ≥ \geq 枢轴的关键值
  • 将枢轴放置在两个子序列之间 (即枢轴的最终位置),完成一趟快速排序
  • 分别递归地对两个子序列重复上述过程,直到序列有序
    在这里插入图片描述

一趟快排的具体实现过程

在这里插入图片描述

  • 设置标志lowhigh,初始指向待排序记录的首尾
  • high开始向前搜索,直到找到一个关键值<pivot的记录,将其放置在low所指位置,low后移
  • low开始向后搜索,直到找到一个关键值>=pivot的记录,将其放置在high所指位置,high前移
  • 重复上面两步,直到low==high,将pivot放置在该位置,一趟快速排序结束

一趟快排结束,接下来递归地对左右子序列进行快排即可:
在这里插入图片描述

算法实现

int Partiton(SqList_t* list, int low, int high)
{
	list->rec[0] = list->rec[low]; //选择枢轴

	while (low < high)
	{
		while (high > low && list->rec[high].key >= list->rec[0].key)
		{
			--high;
		}
		list->rec[low] = list->rec[high];

		while (high > low && list->rec[low].key <= list->rec[0].key)
		{
			++low;
		}
		list->rec[high] = list->rec[low];
	}
	list->rec[low] = list->rec[0];

	return low; //返回枢轴位置
}

void Quick_sort_reccurent(SqList_t* list, int low, int high)
{
	if (low < high)
	{
		int pivot_loc = Partiton(list, low, high);
		Quick_sort_reccurent(list, low, pivot_loc - 1);
		Quick_sort_reccurent(list, pivot_loc + 1, high);
	}
}

void Quick_sort(SqList_t* list)
{
	Quick_sort_reccurent(list, 1, list->len);
}

算法评价

T ( n ) T(n) T(n)

最坏情况 1:初始序列为升序,每次总选到最小元素作枢轴

  • 需要 n − 1 n-1 n1 趟快排,每趟比较 n − i n-i ni
  • 比较次数: ∑ i = 1 n − 1 ( n − i ) = n 2 − n 2 \sum_{i=1}^{n-1} (n-i) = \frac {n^2-n}{2} i=1n1(ni)=2n2n
    ∴ T ( n ) = O ( n 2 ) \therefore T(n)=O(n^2) T(n)=O(n2)在这里插入图片描述

最坏情况2:初始序列为降序,每次总选到最大元素作枢轴

  • 需要 n − 1 n-1 n1趟快排,每趟比较 n − i n-i ni次,移动1次
  • 比较次数: ∑ i = 1 n − 1 ( n − i ) = n 2 − n 2 \sum_{i=1}^{n-1} (n-i) = \frac {n^2-n}{2} i=1n1(ni)=2n2n
  • 移动次数: n − 1 n-1 n1
    ∴ T ( n ) = O ( n 2 ) \therefore T(n)=O(n^2) T(n)=O(n2)

最好情况:每次总是选到中间值作枢轴

递归算法的时间复杂度分析
设问题规模为 n n n,利用分治法得到 a a a个规模为 n / b n/b n/b的问题,每次递归带来的额外计算为 c ∗ ( n d ) c*(n^d) c(nd)
即: T ( n ) = a T ( n / b ) + c ( n d ) T(n)=aT(n/b)+c(n^d) T(n)=aT(n/b)+c(nd)
则:

  • a = b d , T ( n ) = O ( n d l o g 2 n ) a=b^d,T(n)=O(n^d log_2n) a=bdT(n)=O(ndlog2n)
  • a < b d , T ( n ) = O ( n d ) a<b^d,T(n)=O(n^d) a<bdT(n)=O(nd)
  • a > b d , T ( n ) = O ( n l o g b a ) a>b^d,T(n)=O(n^{log_ba}) a>bdT(n)=O(nlogba)

最好情况下, a = 2 , b = 2 , d = 1 a=2,b=2,d=1 a=2,b=2,d=1,有 a = b d a=b^d a=bd
∴ T ( n ) = O ( n d l o g 2 n ) = O ( n l o g 2 n ) \therefore T(n)=O(n^d log_2n)=O(n log_2n) T(n)=O(ndlog2n)=O(nlog2n)
在这里插入图片描述

快排平均时间复杂度计算
  • T p a s s ( n ) = c n T_{pass}(n)=cn Tpass(n)=cn 为对 n n n 个记录进行一趟快排所需时间
    T ( n ) = T p a s s ( n ) + T ( k − 1 ) + T ( n − k ) T(n)=T_{pass}(n)+T(k-1)+T(n-k) T(n)=Tpass(n)+T(k1)+T(nk)因为待排记录是随机排列的,则在一趟排序之后, k k k 1 1 1 ~ n n n 之间任何一值的概率相同
    ∴ T a v g ( n ) = c n + 1 n ∑ k = 1 n ( T a v g ( k − 1 ) + T a v g ( n − k ) ) = c n + 2 n ∑ i = 0 n − 1 T a v g ( i ) ∴ ∑ i = 0 n − 1 T a v g ( i ) = n 2 T a v g ( n ) − c n 2 2                  ( 1 ) 式 ∵ ∑ i = 0 n − 1 T a v g ( i ) = ∑ i = 0 n − 2 T a v g ( i ) + T a v g ( n − 1 ) = n − 1 2 T a v g ( n − 1 ) − c ( n − 1 ) 2 2 + T a v g ( n − 1 )                  ( 2 ) 式 由 ( 1 ) 式 = ( 2 ) 式 可 得 : T a v g ( n ) = n + 1 n T a v g ( n − 1 ) + c ( 2 n − 1 ) n ≈ n + 1 n T a v g ( n − 1 ) + 2 c       ( 省 略 了 一 个 比 常 数 项 还 小 的 数 ) ∴ T a v g ( n ) n + 1 = T a v g ( n − 1 ) n + 2 c n + 1 现 在 可 以 进 行 叠 缩 : T a v g ( n − 1 ) n = T a v g ( n − 2 ) n − 1 + 2 c n T a v g ( n − 2 ) n − 1 = T a v g ( n − 3 ) n − 2 + 2 c n − 1 ⋮ T a v g ( 2 ) 3 = T a v g ( 1 ) 2 + 2 c 3 将 上 面 n − 1 个 式 子 相 加 : T a v g ( n ) n + 1 = T a v g ( 1 ) 2 + 2 c ( ∑ i = 3 n + 1 1 i ) ≈ T a v g ( 1 ) 2 + 2 c ( ln ⁡ ( n − 1 ) + γ − 3 2 ) ∴ T a v g ( n ) ≈ 2 c ( n + 1 ) ln ⁡ ( n − 1 ) + d = O ( n log ⁡ n ) \begin{aligned} \therefore T_{avg}(n)&=cn+\frac{1}{n}\sum_{k=1}^n(T_{avg}(k-1)+T_{avg}(n-k)) \\&= cn+\frac{2}{n}\sum_{i=0}^{n-1}T_{avg}(i) \\ \therefore \sum_{i=0}^{n-1}T_{avg}(i) &= \frac{n}{2}T _{avg}(n) - \frac{cn^2}{2} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (1)式 \\\because \sum_{i=0}^{n-1}T_{avg}(i) &= \sum_{i=0}^{n-2}T_{avg}(i) + T_{avg}(n-1) \\&= \frac{n-1}{2}T _{avg}(n-1) - \frac{c(n-1)^2}{2} + T_{avg}(n-1) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (2)式 \\由(1)式=(2)&式可得:\\ T_{avg}(n)&= \frac{n+1}{n}T _{avg}(n-1) + \frac{c(2n-1)}{n}\approx\frac{n+1}{n}T _{avg}(n-1) + 2c\ \ \ \ \ (省略了一个比常数项还小的数) \\\therefore \frac{T_{avg}(n)}{n+1}&=\frac{T_{avg}(n-1)}{n}+\frac{2c}{n+1}\\ 现在可以进行&叠缩:\\ \frac{T_{avg}(n-1)}{n}&=\frac{T_{avg}(n-2)}{n-1}+\frac{2c}{n} \\ \frac{T_{avg}(n-2)}{n-1}&=\frac{T_{avg}(n-3)}{n-2}+\frac{2c}{n-1}\\ \vdots \\ \frac{T_{avg}(2)}{3}&=\frac{T_{avg}(1)}{2}+\frac{2c}{3}\\ 将上面n-1个&式子相加:\\ \frac{T_{avg}(n)}{n+1}&=\frac{T_{avg}(1)}{2}+2c(\sum_{i=3}^{n+1}\frac{1}{i})\\ &\approx\frac{T_{avg}(1)}{2}+2c(\ln(n-1)+\gamma-\frac{3}{2}) \\\therefore T_{avg}(n)&\approx 2c(n+1)\ln(n-1)+d=O(n\log n) \end{aligned} Tavg(n)i=0n1Tavg(i)i=0n1Tavg(i)(1)=(2)Tavg(n)n+1Tavg(n)nTavg(n1)n1Tavg(n2)3Tavg(2)n1n+1Tavg(n)Tavg(n)=cn+n1k=1n(Tavg(k1)+Tavg(nk))=cn+n2i=0n1Tavg(i)=2nTavg(n)2cn2                (1)=i=0n2Tavg(i)+Tavg(n1)=2n1Tavg(n1)2c(n1)2+Tavg(n1)                (2)=nn+1Tavg(n1)+nc(2n1)nn+1Tavg(n1)+2c     ()=nTavg(n1)+n+12c=n1Tavg(n2)+n2c=n2Tavg(n3)+n12c=2Tavg(1)+32c=2Tavg(1)+2c(i=3n+1i1)2Tavg(1)+2c(ln(n1)+γ23)2c(n+1)ln(n1)+d=O(nlogn)其中 γ ≈ 0.577 \gamma\approx0.577 γ0.577 为欧拉常数,详见调和级数
  • 快速排序的平均时间为 T a v g ( n ) = O ( n log ⁡ n ) T_{avg}(n)=O(n\log n) Tavg(n)=O(nlogn)在所有同数量级 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的排序方法中,它的常数因子最小。但是初始序列基本有序时,快速排序将退化为 O ( n 2 ) O(n^2) O(n2)
改进
  • 改进:比较 R[low],R[high],R[(low+high)/2],取三个关键字中取中值的记录为枢轴,将其与 R[low] 互换。算法实现不变. 然而,即使这样改进之后,也不能使快排在待排序列基本有序时达到 O ( n ) O(n) O(n) 复杂度,可进一步修改为“一次划分”算法来改善平均性能:
    • low 加 1 和 high 减 1 的同时进行冒泡操作,即在相邻两个记录处于逆序时互换
    • 如果 low 从低端向中间移动过程中没有进行过交换,则不需要对低端子表进行排序
    • 如果 high 从高端向中间移动过程中没有进行过交换,则不需要对高端子表进行排序

S ( n ) S(n) S(n)

  • 递归函数需要额外的栈空间
    • 最坏情况:初始序列为升/降序
      S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n)
    • 最好情况:每次总是选到中间值作枢轴,栈的最大深度为 ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n\rfloor+1 log2n+1
      S ( n ) = O ( log ⁡ 2 n ) S(n)=O(\log_2n) S(n)=O(log2n)

是否稳定

  • 不稳定
    • 例如:3 2 2* -> 2* 3 3

总结

  • 就平均时间而言,快速排序算法被认为是内部排序方法中最好的一种
  • 对于小规模数据,不适合选用快速排序
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值