排序算法学习(一):冒泡排序

所谓排序即按照一定的规律将一组数据进行排列。排序算法的形式化定义如下:
    设存在一组无序序列$\{x_1,x_2,...,x_n\}$ ,并且存在一个函数$f(x)$,在经过一系列对该序列中的元素位置调整后,使得新得到的序列满足对于任意的$0 \le i \le j \le n$,均有$f(x_i) \le f(x_j)$(升序排序)或者$f(x_i) \ge f(x_j)$(降序排序)。针对不同的数据源的不同特性,人们设计出了不同的排序算法,以求对算法的时间复杂度或者空间复杂度进行优化。常见的排序算法有:冒泡排序,插入排序,快速排序,桶排序算法等。下面对各种排序算法的原理进行简述,并分析其优缺点。(以下在对排序算法的描述时,都假设将数据按照升序排序进行排列)。

冒泡排序

冒泡算法的步骤如下:比较相邻两个元素的大小,根据比较结果将两个元素按照从小到大的顺序排列,逐步向后移动,一直移动到末端。冒泡算法中的一轮过程如图1所示,每i轮的排序过程即是将第i个最大的元素移动到当前的数组中最大的位置。在每一轮的排序过程中,右边的已排序序列长度不断增长,最终使得整个序列都成为已排序序列。冒泡排序的完整过程如下图所示:
 

图1 冒泡排序的完整过程

冒泡排序算法的最基本实现

根据图1所描述的过程,冒泡排序算法的最基本的Python代码实现如下:


	def bubbleSort(data:List[int])->List[int]:
		length = len(data)
		for i in range(0, length):
			for j in range (0, length - 1):
				if data[j] > data[j+1]:
					tmp = data[j]
					data[j] = data[j+1]
					data[j+1] = tmp
		return data

对优化后的代码分析可知,一共需要进行$n$轮的排序,每一轮的排序中都需要进行$n-1$次的大小比较,因此该算法的算法复杂度为$O(n^2)$,空间复杂度为$1$

针对每轮比较次数的效率优化

通过对图1的观察,第1轮排序完成后,最右侧的1个元素是一个已完成排序的序列,在第2轮的排序过程中,最后1次的比较没有发生顺序的改变;第2轮排序完成后,最右侧的2个元素是一个已完成排序的序列,在第3轮的排序过程中,最后2次的比较没有发生顺序的改变。第$i$轮排序完成后,最右侧的$i$个元素是一个已完成排序的序列,在第$i+1$轮的排序过程中,最后$i$次的比较没有发生顺序的改变。根据这个特点,我们可以对每一轮的排序过程中需要比较的次数进行优化。代码如下:

	def bubbleSort(data:List[int])->List[int]:
			length = len(data)
			for i in range(0, length):
				for j in range (0, length - i - 1):
					if data[j] > data[j + 1]:
						tmp = data[j]
						data[j] = data[j+1]
						data[j+1] = tmp
			return data

在进行优化之后,可以发现在每一轮的排序过程中需要比较的元素的数量得到了优化,在第$i$轮中排序中进行了$i-1$次大小比较,因此在整个排序过程中,一共需要进行$\sum_{1}^{n}(i-1)$次比较运算,算法复杂度为$O(n^2)$,空间复杂度为$1$

针对算法终止条件的效率优化


另外,还可以对冒泡排序算法的终止条件进行优化。我们对图1进行观察,发现从第3轮开始,序列的顺序已经达到排序完成的状态,此后序列中元素的相对位置不再发生改变,因此我们可以根据此对排序的终止条件的判断进行优化。代码如下:

	def bubbleSort(data:List[int])->List[int]):
			length = len(data)
			changed = 0
			for i in range(0, length):
				for j in range (0, length - i - 1):
					if data[j] > data[j + 1]:
						tmp = data[j]
						data[j] = data[j+1]
						data[j+1] = tmp
						changed = 1
					if changed == 0:
						return data
			return data

在进行优化之后,可以发现排序算法可能提前终止排序状态。考虑最好的情况,序列的顺序本来就是排列好的,那么需要进行一轮排序来进行确认,一共需要进行$n-1$次排序,因此在最好的情况下算法的时间复杂度为$O(n)$,空间复杂度为$1$;在最差的情况下,一共需要进行$\sum_{1}^{n}(i-1)$次比较运算,算法复杂度为$O(n^2)$,空间复杂度为$1$

鸡尾酒排序

鸡尾酒排序又称为双向冒泡排序,是冒泡排序的一个变种算法,可以在某些情况下减少算法的比较次数。鸡尾酒排序算法的过程如图2所示:

图2 鸡尾酒排序算法的执行过程

对图2观察可知,利用鸡尾酒排序在第三轮的时候,序列就已经达到了排序完成的状态。比普通的冒泡排序算法快了一轮,下面分析为什么会比冒泡算法快一轮的原因。在冒泡算法过程中,元素单一的从一侧按照从小到大的顺序被移动到另一侧。但是如果假设有这样一种情况,即序列的左侧的大部分元素的相对顺序都是排好的,只有右侧部分元素的顺序不对,那么此时鸡尾酒排序相对于冒泡排序就会有极大的优势。极端一点的例子,如:序列$[2,3,4,5,1]$,如果按照冒泡排序来进行,那么需要进行4轮排序,如果按照鸡尾酒排序的算法来排序则只需要2轮排序。但是对于某些极端情况,如序列$[5,4,3,2,1]$,冒泡排序和鸡尾酒排序都需要进行4轮排序。

鸡尾酒排序算法的Python代码如下(以下代码已经针对每轮的比较次数和算法终止条件进行了优化):

	def cocktailsort(data:List[int])->List[int]):
			start = 0
			end = len(data)
			changed = 0
			while start != end:
				for i in range(start, len(data) - 1):
					if data[i] > data[i + 1]:
						tmp = data[i]
						data[i] = data[i + 1]
						data[i + 1] = tmp
						changed = 1
				print(data)
				if changed == 0:
					return data
				start = start + 1
				changed = 0
				for i in range(end - 1, 0, -1):
					if data[i] < data[i - 1]:
						tmp = data[i]
						data[i] = data[i - 1]
						data[i - 1] = tmp
						changed = 1
					print(data)
					if changed == 0:
						return data
				end = end - 1
			return data

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值