算法+数据结构总结系列(二)

排序

排序: 将一组“无序”的记录序列调整为“有序”的记录序列。
列表排序:将无序列表变为有序列表。
输入:列表
输出:有序列表
两种基本方式:升序与降序
python内置排序函数:sort()

常见的排序算法:
排序 Low B 三人组: 冒泡排序;选择排序;插入排序
排序 Niu B 三人组: 快速排序;堆排序; 归并排序
其他排序:希尔排序; 计数排序; 基数排序

冒泡排序(Bubble Sort)

步骤:

  1. 列表每两个相邻的数,如果前面比后面大,则交换这两个数;
  2. 一趟排序完成后, 则无序区减少一个数,有序区增加一个数;
  3. 代码关键点:趟数、无序区范围。

栗子:
7, 5, 4, 6, 3, 8, 2, 9, 1

第一趟:
7比5大,交换7和5: 5, 7, 4, 6, 3, 8, 2, 9, 1;
7比4大,交换7和4: 5, 4, 7, 6, 3, 8, 2, 9, 1;
7比6大,交换7和6: 5, 4, 6, 7, 3, 8, 2, 9, 1;
7比3大,交换7和3: 5, 4, 6, 3, 7, 8, 2, 9, 1;
7比8小,不交换: 5, 4, 6, 3, 7, 8, 2, 9, 1;
8比2大,交换8和2: 5, 4, 6, 3, 7, 2, 8, 9, 1;
8比9小,不交换: 5, 4, 6, 3, 7, 2, 8, 9, 1;
9比1大,交换9和1: 5, 4, 6, 3, 7, 2, 8, 1, 9;

那么最大的数 9 “冒泡”到了最前面,这就完成了第一趟。9作为有序区的第一个数,其余继续组成无序区

5, 4, 6, 3, 7, 2, 8, 1, 9

在第二趟中,在无序区重复相同的操作。

第二趟:
5比4大,交换5和4: 4, 5, 6, 3, 7, 2, 8, 1, 9;
5比6小,不交换: 4, 5, 6, 3, 7, 2, 8, 1, 9;
6比3大,交换6和3: 4, 5, 3, 6, 7, 2, 8, 1, 9;
6比7小,不交换: 4, 5, 3, 6, 7, 2, 8, 1, 9;
7比2大,交换7和2: 4, 5, 3, 6, 2, 7, 8, 1, 9;
7比8大,不交换: 4, 5, 3, 6, 2, 7, 8, 1, 9;
8比1小,交换8和1: 4, 5, 3, 6, 2, 7, 1, 8, 9;

无序区最大的数8 “冒泡”到了最前面, 这就完成了第二趟。8,9 作为有序区的前两个数, 其余继续组成无序区

4, 5, 3, 6, 2, 7, 1, 8, 9

在接下来的几趟中, 在剩余无序区重复相同操作

栗子的总结:
假设一无序列表长度为n,那么在冒泡排序中:

  1. 每一趟无序区长度减一有序区长度加一最后一趟无序区只有一个元素,无需进行,因此一共进行n-1趟。
  2. 第i趟中,无序区元素指标从1到n-i+1遍历 (若是列表指标则为从0到n-i)
  3. 第i趟中, 比较的两个数元素指标j,j+1从1到n-i遍历 (若是列表指标则为从0到n-i-1)。
  4. 冒泡排序只需原地进行排序,无需开辟新内存。
def bubble_sort(li):
	for i in range(len(li) - 1): #第i趟
		for j in range(len(li) - i - 1):
			if li[j] > li[j+1]:
				li[j], li[j+1] = li[j+1], li[j]
			

时间复杂度:O(n2)

冒泡排序的改进:

原理:
在某一趟过程中,若没有发生交换操作,那么该列表已经排好,无需进行后续的趟数。

栗子:
对于列表[9,8,7,1,2,3,4,5,6](更极端的情况[1,2,3,4,5,6,7,8,9]),在第三趟后就已经不再发生交换,即第三趟后就已经排好, 第三趟后结束即可。

对于大规模已经是有序片段的列表来说,此改进还是很显著的。

策略:
在每一趟设置一个标记位,发生交换时标记位为True,否则为False,根据标记位来判断是否要进行后续的趟数。

def bubble_sort(li):
	for i in range(len(li) - 1): #第i趟
		exchange = False
		for j in range(len(li) - i - 1):
			if li[j] > li[j+1]:
				li[j], li[j+1] = li[j+1], li[j]
				exchange = True
		if  not exchange:
			return
			

选择排序(Select Sort)

步骤:

  1. 遍历一遍列表,找到最小的元素;
  2. 再次遍历剩余列表,找到第二小的元素;
  3. 依次进行下去…
  4. 依次记录这些找到的元素,得到有序列表。

问题:对于第4步,如何记录这些元素?
回答:一种策略是每次将最小元素记录在新列表,并在旧列表中删除…

基于如上的策略,代码如下:

def simple_select_sort(li):
	li_new = []
	for i in range(len(li)):
		min_val = min(li)
		li_new.append(min_val)
		li.remove(min_val)
	return li_new
	

注意:上面代码有几个致命的缺点:

  1. 生成了两个列表,多占用了内存。对比冒泡排序,不是原地排序,内存多占用一倍。
  2. 代码中函数min(),remove()都不是O(1)的操作,都是O(n)的操作,从而整个代码块是O(n2)的时间复杂度

问题:如何克服?
回答:记录最小元素指标,和无序区第一个元素交换,实现原地排序。这与冒泡排序有相似之处:在无序区每一趟找出最小元素的指标。

def select_sort(li):
	for i in range(len(li) - 1):
		min_index = i
		for j in range(i + 1, len(li)):
			if li[j] < li[min_index]:
				min_index = j
		li[i], li[min_index] = li[min_index], li[i]

时间复杂度:O(n2),但占用内存会比较少。

插入排序(Insert Sort)

步骤

  1. 初始时手里(有序区)只有一张牌;
  2. 每次(从无序区)摸一张牌,插入到手里已有牌的正确位置。

栗子:

5, 7, 4, 6, 3, 1, 2, 9, 8

有序区为5,摸出7,插入5右边5, 7, 4, 6, 3, 1, 2, 9, 8;
有序区为5,7,摸出4,插入5左边4, 5, 7, 6, 3, 1, 2, 9, 8;
有序区为4,5,7,摸出6,插入5右边4, 5, 6, 7, 3, 1, 2, 9, 8;
有序区为4,5,6,7,摸出3,插入4左边3,4, 5, 6 ,7, 1, 2, 9, 8;
有序区为3,4,5,6,7,摸出1,插入3左边1,3,4, 5, 6 ,7, 2, 9, 8;
有序区为1,3,4,5,6,7,摸出2,插入1左边1,2,3,4, 5, 6 ,7, 9, 8;
有序区为1,2,3,4,5,6,7,摸出9,插入7右边1,2,3,4, 5, 6 ,7, 9, 8;
有序区为1,2,3,4,5,6,7,9,摸出8,插入7右边1,2,3,4, 5, 6 ,7, 8,9

栗子总结
假设一无序列表长度为n,那么在插入排序中:

  1. 一共进行n-1次摸牌,从而有n-1趟
  2. 第i趟中,新摸出的牌仅在有序区进行比较插入,范围是元素指标从1到i(若为列表指标则从0到i-1)。
  3. 插入过程中涉及到有序区列表元素的移动
def insert_sort(li):
	for i in range(1, len(li)): #表示摸到的牌的下标
		tag = li[i]
		j = i-1     # 表示手里最后一张牌的下标
		while j > -1 and li[j] > tag: 
			li[j+1] = li[j] #向前移动
			j -= 1
		li[j+1] = tag

时间复杂度:O(n2)

本次内容即为Low B三人组中的三种排序方法,其时间复杂度均为O(n2),相对来说都是比较慢的,下一篇中将带来较快的排序方法—— Niu B 三人组

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值