这两天参加笔试题发现排序的东西还是蛮多的,借此机会总结一下~
排序算法的稳定性: 排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。即如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。
def quick_sort(nums, start, end):
index = partition(nums, start, end)
if index - 1 > start:
quick_sort(nums, start, index - 1)
if index + 1 < end:
quick_sort(nums, index + 1, end)
def partition(nums, start, end):
small= start - 1
for index in range(start, end):
if nums[index] < nums[end]:
small+= 1
if small!= index:
swap(nums, small, index)
small+= 1
swap(nums, small, end)
return small
l 快速排序采用了一种分治的策略(Divide-and-Conquer Method)。该方法的基本思想是:1)先从数列中取出一个数作为基准数;2)分区过程:将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;3)再对左右区间重复第二步,直到各区间只有一个数。
l 快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需k-1次关键字的比较。
l 最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。时间复杂度为O(n*n)
l 在最好情况下,每次划分所取的基准都是当前无序区的"中值"记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:O(nlgn)
l 尽管快速排序的最坏时间为O(n2),但就平均性能而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名。它的平均时间复杂度为O(nlgn)。
l 快速排序是不稳定排序,也是原地排序;
def merge_sort(nums, start, end): if start < end: mid = int((end - start) >> 1) + start merge_sort(nums, start, mid) merge_sort(nums, mid + 1, end) merge(nums, start, mid, end) def merge(nums, start, mid, end): left_length = mid - start + 1 right_length = end - mid left_array = [0] * left_length right_array = [0] * right_length for i in range(start, mid + 1): left_array[i - start] = nums[i] for i in range(mid + 1, end + 1): right_array[i - mid - 1] = nums[i] i = j = 0 k = start while i < left_length and j < right_length: if left_array[i] <= right_array[j]: nums[k] = left_array[i] i += 1 else: nums[k] = right_array[j] j += 1 k += 1 if i < left_length: for i in range(i, left_length): nums[k] = left_array[i] i += 1 k += 1 if j < right_length: for j in range(j, right_length): nums[k] = right_array[j] j += 1 k += 1
l 归并排序(Mergesort):采用分治法(Divideand Conquer)。时间复杂度为O(nlogn)这是该算法中最好、最坏和平均的时间性能,空间复杂度为 O(n)。
l 比较操作的次数介于(nlogn)/ 2和nlogn -n + 1。
l 赋值操作的次数是(2nlogn)。
l 归并排序比较占用内存,但却效率高且稳定的算法。
def select_sort(nums): for i in range(0, len(nums) - 1): min_index = i for j in range(i + 1, len(nums)): if nums[j] < nums[min_index]: min_index = j if min_index != i: swap(nums, min_index, i)
选择排序是固定位置,找元素,时间复杂度O(n2),不稳定。
def bubble_sort(nums): for i in range(len(nums) - 1, 0, -1): for j in range(0, i): if nums[j] > nums[j + 1]: swap(nums, j, j + 1)
冒泡排序将待排序的元素看作是竖着排列的“气泡”,较小(大)的元素比较轻(重),从而要往上浮(下沉)。时间复杂度O(n2),稳定。
def insert_sort(nums): for i in range(1, len(nums)): for j in range(i - 1, -1, -1): if nums[j] < nums[j + 1]: break else: swap(nums, j, j + 1)
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。时间复杂度O(n2),稳定。
堆排序是一种树形选择排序,在排序过程中,将A[n]看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。不稳定,时间复杂度O(nlgn)
思想:在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。
不稳定,平均时间复杂度O(nlgn)