排序算法的稳定性
排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法。
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。
(4, 1) (3, 7) (3, 1) (5, 6)
在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:
(3, 7) (3, 1) (4, 1) (5, 6) (维持次序)
(3, 1) (3, 7) (4, 1) (5, 6) (次序被改变)
不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个对象间之比较,(比如上面的比较中加入第二个标准:第二个键值的大小)就会被决定使用在原先数据次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。
排序算法的稳定性即是,如果只按照第一个数字排序的话,第一个数字相同而第二个数字不同的,第二个数字按照原有排序的就是稳定排序,不按照原有排序的就是不稳定排序。
冒泡排序及实现
单行比较一对相邻两个数字之间更大的数字,将更大的数字放在右边;再换下一行,将前一行大的那个数字再与下一个数字比较放在右边,所有行走一遍就可以排出一个最大的数字来放在最右边;排除一个数字后再所有行走一遍排除第二大的数字放在最大的数字的左边,一次类推,n个数经过 n-1 次可以完成从小到大排序。
冒泡排序是稳定性排序。
i = n-2 j = 1 i = n-3 j = 2 i+j = n-1 i = n-1-j
冒泡排序的实现

def bubble_sort(alist): n = len(alist) # 外层for循环,走n-1次将所有的元素进行排序 # range(n-1) 表示的是 0, n-2,其中有n-2+1 for j in range(n - 1): # 内存for循环,走一次就将一个最大的数从前往后排 # 假如有10个数,只要走到第9个数就可以了,9与9+1进行比较排序;但因为我们使用的是下标,所以n-1的下标为n-2,为range(0, n-1) count = 0 for i in range(0, n-1-j): if alist[i] > alist[i + 1]: alist[i], alist[i + 1] = alist[i + 1], alist[i] if count == 0: return def main(): li = [22, 11, 55, 44, 100, 99, 80] print(li) bubble_sort(li) print(li) if __name__ == '__main__': main()
选择排序算法及实现
选择排序算法可以理解为,将列表分为两部分,刚开始时左边一组为空,右边为所有元素;然后从右边选出一个最小的数字,放在最左边,然后左右两部分就是,左边一个数字,右边是其余的数字;然后再从右边选出一个最小的数字,放在左边第二位,以此类推,就可以完成排序了。始终从后面未排序的数组中选出一个最小的数字放在左边。
从右边选出一个最小的数字放在左边的过程可以理解为,初始时定义最小的min为下标为0的索引的数字,然后与0后面的索引数字进行比较,如果还有数字比索引0数字更小的就将min指向后面那个更小数字的索引,最后走了一遍后确定最小数字的索引min,然后将alist[0], alist[min] = alist[min], alist[0],这样就排好了第一个数字。然后再定义min为下标为1的数字,再和右边的进行比较,如果右边还有比索引1数字更小的数字,就将min指向后面更小数字的索引,最后确定最小数字索引,交换索引1数字和最小数字索引的位置,alist[1], alist[min] = alist[min], alist[i]。以此类推完成排序。
选择排序方法的最坏时间复杂度为 O(n^2),因为其算法问题,没有最优时间复杂度,也没有优化方法;
这里采用的是升序选择最小的方法进行排列,是稳定性排序;但如果你采用的是升序选择最大的方法,那么就是不稳定排序了;所以总体来说,我们认为选择排序是不稳定的。
选择排序的实现

def select_sort(alist): """选择排序""" n = len(alist) for j in range(0, n - 1): min_index = j # 内层循环,一次大循环从右边部分挑出一个最小数,将最小数依次放在左边 for i in range(j+1, n): if alist[min_index] > alist[i]: min_index = i alist[j], alist[min_index] = alist[min_index], alist[j] def main(): li = [22, 11, 55, 44, 100, 99, 80] print(li) select_sort(li) print(li) if __name__ == '__main__': main()
插入排序
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
插入算法可以理解为,还是将一个列表分为两部分,当第一次时,将列表第一个数字当做左边部分,其余做右边部分;第二次时,将右边第一个数字与左边的数字进行比较,如果更大就插入到左边第二位,更小就插入到左边第一位;第三次时,将右边第一个数字与左边部分从大到小比较,即首先与左边最右边的最大的数字比较,如果更大则插入到左边部分第三位,更小则插入到左边最大数的前一位,再与左边部分第二大的数字进行比较,如果更大则说明这个数字是左边部分第二大的数字,则不动,如果更小就插入到左边部分第二大数字的左边;以此类推即可完成排序;
插入排序就是依次从右边取出第一个数字,并将数字插入道左边部分合适的位置。
[22, 11, 55, 13, 44, 100, 99, 80] [11, 22, 55, 13, 44, 100, 99, 80] [11, 22, 55, 13, 44, 100, 99, 80] [11, 22, 13, 55, 44, 100, 99, 80] [11, 13, 22, 55, 44, 100, 99, 80] [11, 13, 22, 44, 55, 100, 99, 80] [11, 13, 22, 44, 55, 100, 99, 80] [11, 13, 22, 44, 55, 99, 100, 80] [11, 13, 22, 44, 55, 99, 80, 100, ] [11, 13, 22, 44, 55, 80, 99, 100, ]
插入排序的实现

def insert_sort(alist): """插入排序""" n = len(alist) # 外层循环,决定从右边的无序列表中去除多少个元素执行这样的过程 for j in range(1, n): # i = [1, 2, 3, ... , n-1] i = j # 内层循环,循环一次,就将右边的一个数字插入到左边部分正确的位置中 while i > 0: if alist[i] < alist[i - 1]: alist[i - 1], alist[i] = alist[i], alist[i - 1] i -= 1 else: break def main(): li = [22, 11, 55, 13, 44, 100, 99, 80] print(li) insert_sort(li) print(li) if __name__ == '__main__': main() # 运行结果: # [22, 11, 55, 13, 44, 100, 99, 80] # [11, 13, 22, 44, 55, 80, 99, 100]
时间复杂度
最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
最坏时间复杂度:O(n2)
稳定性:稳定
希尔排序
希尔排序过程
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10
然后我们对每列进行排序:
10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45
排序之后变为:
10 14 13 25 23 33 27 25 59 39 65 73 45 94 82 94
最后以1步长进行排序(此时就是简单的插入排序了)
希尔排序的实现

def shell_sort(alist): n = len(alist) # 初始步长 gap = n // 2 while gap > 0: # 按步长进行插入排序 for i in range(gap, n): j = i # 插入排序 while j >= gap and alist[j - gap] > alist[j]: alist[j - gap], alist[j] = alist[j], alist[j - gap] j -= gap # 得到新的步长 gap = gap // 2 li = [22, 11, 55, 13, 44, 100, 99, 80] print(li) shell_sort(li) print(li)
时间复杂度
最优时间复杂度:根据步长序列的不同而不同
最坏时间复杂度:O(n2)
稳定想:不稳定
快速排序
快速排序的实现

def quick_sort(alist, start, end): """快速排序""" # 递归的退出条件 if start >= end: return # 设定起始元素为要寻找位置的基准元素 mid = alist[start] # low为序列左边的由左向右移动的游标 low = start # high为序列右边的由右向左移动的游标 high = end while low < high: # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动 while low < high and alist[high] >= mid: high -= 1 # 将high指向的元素放到low的位置上 alist[low] = alist[high] # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动 while low < high and alist[low] < mid: low += 1 # 将low指向的元素放到high的位置上 alist[high] = alist[low] # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置 # 将基准元素放到该位置 alist[low] = mid # 对基准元素左边的子序列进行快速排序 quick_sort(alist, start, low - 1) # 对基准元素右边的子序列进行快速排序 quick_sort(alist, low + 1, end) li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) quick_sort(li, 0, len(li) - 1) print(li)
时间复杂度
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(n2)
稳定性:不稳定
归并排序
归并排序的实现

def merge_sort(alist): """归并排序""" n = len(alist) if n <= 1: return alist mid = n//2 # left 采用归并排序后形成的有序的新的列表 left_li = merge_sort(alist[:mid]) # right 采用归并排序后形成的有序的新的列表 right_li = merge_sort(alist[mid:]) # 将两个有序的子序列合并为一个新的整体 # merge(left, right) left_pointer, right_pointer = 0, 0 result = [] while left_pointer < len(left_li) and right_pointer < len(right_li): if left_li[left_pointer] <= right_li[right_pointer]: result.append(left_li[left_pointer]) left_pointer += 1 else: result.append(right_li[right_pointer]) right_pointer += 1 result += left_li[left_pointer:] result += right_li[right_pointer:] return result if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) sorted_li = merge_sort(li) print(li) print(sorted_li)
时间复杂度
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
稳定性:稳定
二分查找
二分查找条件:
- 1.查找的数据必须是经过排序的;
- 2.数据必须是相邻的;
- 3.必须可以索引;
所以二分查找只能作用在有序的顺序表中;
二分查找的实现

def binary_search(alist, item): """二分查找,递归""" n = len(alist) if n > 0: mid = n // 2 if alist[mid] == item: return True elif item < alist[mid]: return binary_search(alist[:mid], item) else: return binary_search(alist[mid + 1:], item) return False def binary_search_2(alist, item): """二分查找, 非递归""" n = len(alist) first = 0 last = n - 1 while first <= last: mid = (first + last) // 2 if alist[mid] == item: return True elif item < alist[mid]: last = mid - 1 else: first = mid + 1 return False if __name__ == "__main__": li = [17, 20, 26, 31, 44, 54, 55, 77, 93] print(binary_search(li, 55)) print(binary_search(li, 100)) print(binary_search_2(li, 55)) print(binary_search_2(li, 100))
时间复杂度
最优时间复杂度:O(1)
最坏时间复杂度:O(logn)