1、插入排序
如图所示,从第1位开始不断地与前面的元素挨个比较,然后将较大的元素交换到后面,较小的元素交换到前面,直到所有元素排好序为止。
def insert_sort(lst): for i in range(1, len(lst)): x = lst[i] temp = i while temp > 0 and lst[temp-1] > x: lst[temp] = lst[temp-1] #只要前一个元素大于后一个元素,就将较大的元素交换到后面 temp -= 1 lst[temp] = x return lst if __name__=="__main__": lst = [4,1,6,8,34,9,2,5] print(insert_sort(lst))
算法的空间复杂度为O(1),时间复杂度为O(n2).
2、直接选择排序
每次都顺序扫描出未排序序列中最小的元素,然后将其添加在已排序序列的末尾。
def select_sort(lst): for i in range(len(lst)-1): #只需要比较len(lst)-1次,剩余的最后一个元素肯定是最大的 min_position = i # min_position记录最小元素的位置 for j in range(i+1, len(lst)): if lst[j] < lst[min_position]: #如果有更小的元素,则更新min_position min_position = j if i != min_position: #有可能位置i已经是最小元素的位置,所以此时就不需要交换了 lst[i], lst[min_position] = lst[min_position], lst[i] return lst if __name__=="__main__": lst = [4,1,6,8,34,9,2,5] print(select_sort(lst))
算法的空间复杂度为O(1),时间复杂度为O(n2).
直接选择排序的实际排序效率低于插入排序。所以直接选择排序很少被实际应用。
将原来已排序序列的后一个元素交换到最小元素的位置,有可能出现两个相同的元素做交换,这说明算法不稳定。
延伸:
上图中的排序过程中,每一遍检查都可以将最大的元素交换到最后面。这就像水中的气泡浮起,也是这种算法名字的由来。
当序列中最小元素在序列的最后时,冒泡排序需要n-1遍扫描。在一般情况下,并不需要这么多次。只要在某次扫描中没有发现逆序对,则说明排序已经完成了。
def bubble_sort(lst): for i in range(len(lst)-1): #最多需要比较len(lst)-1次 found = False #found变量初始为false,如果存在逆序对,则置为True,如果不存在,则为false,则会控制循环提前结束 for j in range(1, len(lst)-i): #第一次需扫描n个元素,第二次需扫描n-1个元素,依此类推 if lst[j-1] > lst[j]: #如果存在逆序对,则交换元素 lst[j-1], lst[j] = lst[j], lst[j-1] found = True if not found: break return lst if __name__=="__main__": lst = [30,13,25,16,47,26,19,10] print(bubble_sort(lst))
如果初始序列有序,则只需要扫描一次就结束了,时间复杂度是O(n)。
冒泡排序最坏的时间复杂度是O(n2),平均时间复杂度也是O(n2),最好情况下的时间复杂度是O(n)。其也是一种原位排序算法,空间复杂度是O(1)。
冒泡排序效率低于插入排序。一是因为反复交换相邻元素,累加起来代价较大;二是一些距离最终位置较远的元素会拖累整个算法。
此外,还有一种交错起泡的算法,可以将小元素快速地移向左方。从左向右扫描一次,再从右向左扫描一次,交替进行。
只需要 4遍就可以完成以下的排序工作。
4、快速排序
快速排序算法在实践中是平均速度最快的算法之一。
基本思想是:从序列中选一个元素作为‘标准’,将序列中剩余的元素与这个标准一一比较,小于‘标准’的元素放在其左边,大于‘标准’的元素放在其右边;然后在两个子序列中按照同样的方式递归地划分下去。
算法实现:
取序列中第一个元素作为‘标准’,记为R。小于R的元素在其左边,大于R的元素在其右边,中间是尚未检查的元素。此外,下标i和j分别指向未检查元素序列的第一个位置和最后一个位置(i初始时指向整个序列的第一个元素)。然后交替进行如下操作:
(1)从位置j开始向左逐个比较当前元素与R的大小,直至找到第一个小于R的元素,将其存入位置i。然后i加1,指向下一个需要检查的元素。
(2)从位置i开始向右逐个比较当前元素与R的大小,直至找到第一个大于R的元素,将其存入位置j。
重复以上两步,直到i与j相等,此时将R存入i=j的这个位置,一次划分完成。
def quick_sort(lst): record = qsort_rec(lst, 0, len(lst)-1) #i和j分别初始化为序列的第一个和最后一个位置 return record def qsort_rec(lst, l, r): if l >= r: #说明序列中没有元素或者只有一个元素 return i, j = l, r standard = lst[i] #标准元素为序列中最左边的元素 while i < j: #终止时i=j while i < j and lst[j] >= standard: #从右向左找到第一个小于标准的元素 j -= 1 if i < j: #将上面找到的元素放到位置i lst[i] = lst[j] i += 1 while i < j and lst[i] <= standard: #从左向右找到第一个大于标准的元素 i += 1 if i < j: #将上面找到的元素放到位置j lst[j] = lst[i] j -= 1 lst[i] = standard qsort_rec(lst, l, i-1) qsort_rec(lst, i+1, r) return lst if __name__=="__main__": lst = [30,13,25,16,47,26,19,10] print(quick_sort(lst))
如果每次划分都能讲序列分成两个基本相等的子序列,那么整个序列将被分为大约log n层;在其中一层中,元素的比较次数不会超过序列的长度n,所以快速排序的平均时间复杂度是O(log n)。
但是,如果每层划分得到的两个子序列中总有一个为空,另一个子序列中的元素个数只比本层划分前少一个,这样需要分为n-1层,每层的比较次数从n-1逐层减少到1,。此时是快速排序最坏的时间复杂度O(log n)。
其空间复杂度最坏时是O(n),但是可以通过不同的实现方式,提升至O(log n)。
5、归并排序
基本过程:
- 首先,将待排序序列中的n个元素看作n个有序子序列,每个子序列长度为1。
- 然后,将当前序列中的有序子序列两两归并,完成一遍后整个序列中的已排序序列个数减半,每个子序列长度翻倍。
- 对长度翻倍后的子序列继续两两归并,最后将得到一个长度为n的有序序列。
归并排序适合处理存储在外存中的大量数据。
下图是一个例子:第一遍将序列归并为一组长度为2的有序序列,最后的元素44没有归并对象,原样留到下一步;第二遍归并出3个长度为4的有序序列;第三遍只能归并出一个长度为8的有序序列,剩余的3个元素原样留到下一步;最后一步便已经得到了排序序列。
#实现一对有序序列的归并操作,将归并的结果存入另一个顺序表的相同位置。 def merge(lfrom, lto, low, mid, high): i, j, k = low, mid, low while i < mid and j < high:#每次都将两个子序列中最小的元素加入到lto中,但是总是会有某个序列的后面会剩下几个元素,需要下面的两个循环再将这些元素加到lto中 if lfrom[i] <= lfrom[j]: lto[k] = lfrom[i] i += 1 else: lto[k] = lfrom[j] j += 1 k += 1 while i < mid: #将第一个子序列的剩余元素加入到lto中 lto[k] = lfrom[i] i += 1 k += 1 while j < high: #将第二个子序列的剩余元素加入到lto中 lto[k] = lfrom[j] j += 1 k += 1 return lto #其中的某一遍归并操作,将表中的所有元素进行一遍归并 def merge_pass(lfrom, lto, list_len, sub_len): #list_len为整个序列的长度,sub_len为子序列的长度 i = 0 while i + 2*sub_len <= list_len: #处理序列中长度为sub_len的两个子序列 merge(lfrom, lto, i, i+sub_len, i+2*sub_len) i += 2*sub_len if i + sub_len < list_len: #此时剩下两个子序列,但只有第一个子序列的长度满足sub_len merge(lfrom, lto, i, i+sub_len, list_len) else: #否则,此时只剩下一个子序列,直接将这个子序列添加在lto后面 for j in range(i, list_len): lto[j] = lfrom[j] #主函数,分配不同长度的sub_len,调用归并函数 def merge_sort(lst): sub_len, list_len = 1, len(lst) temp_lst = [None] * list_len while sub_len < list_len: #不断将sub_len的长度翻倍 merge_pass(lst, temp_lst, list_len, sub_len) sub_len *= 2 merge_pass(temp_lst, lst, list_len, sub_len) sub_len *= 2 if __name__=="__main__": lst = [30,13,25,16,47,26,19] merge_sort(lst) print(lst)算法的时间复杂度为O(nlog n),空间复杂度为O(n)