[Alg]排序算法之交换排序

[Alg]排序算法之交换排序 (写文章要有深度,直接刨它祖坟)#作者:屎壳郎 miaosg01@163.com日期:July 2021版次:初版简介: 交换排序是排序算法中的一大类,通过构成反序数据对的两两交换来消除反序,最终得到有秩序的排列。那么排列交换的本质是什么呢?说来话长,让我们回到远古时代,重温数据结构,从环形链表开始说起。0、 交换排序的本质环形链表的合并:假设现在我们有两个指针分别指向两个环形链表,要想把两个环形链表合并成一个的操作为:LINK(ptr1)↔LINK(ptr2
摘要由CSDN通过智能技术生成

[Alg]排序算法之交换排序


作者:屎壳郎 miaosg01@163.com

日期:July 2021

版次:初版


简介: 交换排序是排序算法中的一大类,通过构成反序数据对的两两交换来消除反序,最终得到有秩序的排列。那么排列交换的本质是什么呢?说来话长,让我们回到远古时代,重温数据结构,从环形链表开始说起。

0、 交换排序的本质

环形链表的合并:

假设现在我们有两个指针分别指向两个环形链表,要想把两个环形链表合并成一个的操作为: L I N K ( p t r 1 ) ↔ L I N K ( p t r 2 ) LINK(ptr1)\leftrightarrow LINK(ptr2) LINK(ptr1)LINK(ptr2),即交换这两个指针的链接地址。见下图:
在这里插入图片描述

环形链表的拆分:

那么反过来,我们想把一个环形链表拆分成两个环形链表,如何操作呢?同样的操作,只是两个指针要指向同一个环形链表内: L I N D ( p t r 1 ) ↔ L I N K ( p t r 2 ) LIND(ptr1)\leftrightarrow LINK(ptr2) LIND(ptr1)LINK(ptr2),即交换这两个指针的链接地址。区别就在于合并两个链表时,两个指针指向不同的环形链表;而拆分环形链表时,两个指针指向同一个链表。 见下图:
在这里插入图片描述

讲了这么多,环形链表的合并拆分跟交换排序又有什么关系?我要说数组就是一个或多个环形链表组成,你信吗?我们举个例子: ( 3   7   6   9   8   1   4   5   2 ) (3\,7\,6\,9\,8\,1\,4\,5\,2) (376981452) R 1 R_1 R1的值为3,表示 R 1 R_1 R1存储的指针指向 R 3 R_3 R3,即 R 1 → R 3 R_1\rightarrow R_3 R1R3,依此推算 R 1 → R 3 → R 6 → R 1 R_1\rightarrow R_3\rightarrow R_6\rightarrow R_1 R1R3R6R1构成了一个环形链表(红色表示);同理 ( R 5 , R 8 ) (R_5,R_8) (R5,R8)(蓝色表示)和 ( R 2 , R 7 , R 4 , R 9 ) (R_2,R_7,R_4,R_9) (R2,R7,R4,R9)(黑色表示)分别构成了两个环形链表。所以这个排列可表示为: ( ( 3   6   1 ) ( 2   7   4   9 ) ( 5   8 ) ) ((3\,6\,1)(2\,7\,4\,9)(5\,8)) ((361)(2749)(58))共三个环形链表构成的数组排列(在《排列与反序》中,应用这个思想解决了由反序表生成对应的排列问题,同时提供了数组转链表、链表转数组的算法)。见下图:
在这里插入图片描述

我们在不同的环中交换一次数据,环数减少1;在同环中交换一次数据环数增加1。我们交换排序的目标是把它排列为 ( 1   2   3   4   5   6   7   8   9 ) (1\,2\,3\,4\,5\,6\,7\,8\,9) (123456789),只包含一个元素、指向自身的环。还是以上面的例子为例,有3个环,我们的排序目标是9个元素要形成9个环,那么最少的交换次序 9 − 3 = 6 9-3=6 93=6,即最少要交换(同环交换)6次才能完成排序。理解了排列是由不同的环构成的,也就明白为什么第一类斯特林数满足 ∑ k [ n k ] = n ! \sum_k{n\brack k}=n! k[kn]=n!性质—— n n n个元素形成不同环的数量总和等于 n n n的排列数,其有一一的对应关系。(参考《排列与反序》,里面有详细的数组转链表、链表转数组的讲解,可加深对这方面知识的理解。)

1、 最短的排序程序,没有之一

最基本的算法就是与相邻的数据两两比较、交换,这是冒泡排序的基本思路。与直接插入排序类似,两两的比较交换数据是非常低效的,因其数据需要的平均净移动量为 1 3 N {1\over3}N 31N,见《[Alg]排序算法之插入排序》。当然,万事万物都有其两面性,牺牲了性能,得到的好处是最简洁的代码,下面是最短的排序程序描述:

	   int i;
begin: i = N; //设置从头开始遍历
	   for( ; i > 0; --i){
	    	if(R[i] < R[i-1]){
		    	R[i] <-> R[i-1];//交换数据
			    goto begin;
		    } 
	   }

这个算法原理非常之简单,从头开始遍历数据记录并与其相邻的记录比较,发现一个反序后即对进行数据交换,然后从头开始重新遍历,直到消除所有的反序,至此if分支不成立,不执行条件语句中的goto跳转,然后从循环退出。这是一个比冒泡排序还苯的算法,大约平均执行 1 4 N 2 {1\over4}N^2 41N2(见拆分数,反序的均值)次循环(一个反序对应一次循环)时间复杂度 O ( N 3 ) O(N^3) O(N3)。这个算法除了能增加一点我们的见识,貌似没有其它作用了(在快排中,选头、中、尾三个数据时用到了,这个排序很简洁,比写一大堆的if语句要好)。

回过头来接着聊冒泡排序,冒泡排序相邻的数据两两比较,成反序则交换。一趟遍历后,大数据上浮;我们可以在下一趟遍历时,让小数据下沉。这点改进稍稍能提高点性能,但与直接插入比起来,虽然都很垃圾,但冒泡排序更垃圾,其代码量相当于直接插入排序的两倍。好像除了冒泡排序妇孺皆知、广为传诵的名字,也没有其它让人欣赏的东西了。

2、Batcher’s 并行排序算法

我们从《[Alg]排序算法之插入排序》的分析中得知,要提高算法的效率,就要增大比较和交换的跨度。下面提到的算法和shell排序算法类似,增加比较交换跨度。虽然其原理相似,但其具体的实现手段却相去甚远。这种增大跨度的方式很新奇,亦非常的错综复杂,不容易理解。我们先列出算法的实现,再配以例子逐条解释。可能也只有这个方法能把算法过程说的明白一点,我实在想不出其它更好的方式了。

算法M:(Batcher’s 并行算法)

设记录 R 0 , R 1 , … , R N − 1 R_0,R_1,\ldots, R_{N-1} R0,R1,,RN1,其关键词为: K 0 , K 1 , … , K N − 1 K_0,K_1,\ldots,K_{N-1} K0,K1,,KN1,假设 N − 1 > 2 N-1>2 N1>2

  • M1.[初始化 p p p.] 置 p ← 2 t − 1 p\gets2^{t-1} p2t1,在这里 t = ⌈ lg ⁡ N ⌉ t=\lceil\lg N\rceil t=lgN,即 t t t取满足条件 2 t ≥ N 2^t\geq N 2tN的最小值。
  • M2.[初始化 q , r , d q,r,d q,r,d.] 置 q ← 2 t − 1 q\gets2^{t-1} q2t1 r ← 0 r\gets0 r0 d ← p d\gets p dp
  • M3.[循环 i i i.] For 0 ≤ i < N − d 0\leq i<N-d 0i<Nd and i ∧ p = r i\wedge p=r ip=r,执行M4,然后转到M5。
  • M4.[比较交换] 如果 K i + 1 > K i + d + 1 K_{i+1}>K_{i+d+1} Ki+1>Ki+d+1,交换 R i + 1 ↔ R i + d + 1 R_{i+1}\leftrightarrow R_{i+d+1} Ri+1Ri+d+1
  • M5.[循环 q q q.] 如果 q ≠ p q\neq p q=p,置 d ← q − p d\gets q-p dqp q ← q / 2 q\gets q/2 qq/2 r ← p r\gets p rp,并返回M3。
  • M6.[循环 p p p.] 置 p ← p / 2 p\gets p/2 pp/2,如果 p > 0 p>0 p>0,返回M2;否则结束。

先设置一个例子,设记录 R R R其值为 ( R 0 , R 1 , R 2 , R 3 , R 4 , R 5 , R 6 , R 7 ) (R_0,R_1,R_2,R_3,R_4,R_5,R_6,R_7) (R0,R1,R2,R3,R

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值