排序
排序,作为计算编程中最普遍也最重要的算法之一,在各类程序软件中应用非常广泛,是实现计算机高效计算的基础。
-
排序的目的
计算机作为管理数据储存信息的重要载体,为了实现其高效处理、检索、删插数据的目的,数据有序排列是一个重要方式。要通过排序算法对已有的数据进行排序,便是实现这个目标的主要途径。
-
排序的种类
常用的排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序等等,为了更好的展示排序的种类及性能,将常见的排序算法做了汇总,见下表:
排序算法 | 平均时间复制度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | IN | YES |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | IN | NO |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | IN | YES |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | OUT | YES |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(logn) | IN | NO |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | IN | NO |
希尔排序 | O(nlogn) | O(nlogn) | O(nlog2n) | O(1) | IN | NO |
- 时间复杂度:算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级,如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。例如一次简单循环的时间复杂度为O(n);
- 空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,包括程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面。算法空间复杂度的计算公式记作:S(n)= O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。例如一位数组a[n]的空间复杂度为O(n);
- 排序方式:IN不占用额外内存,OUT占有额外内存;
- 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序是否相同;
-
排序实现
1)冒泡排序
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
步骤:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
def bubbleSort(lyst):
for i in range(len(lyst)-1): #比较次数
for j in range(0,len(lyst)-i): #判断上浮下标
if lyst[j]>lyst[j+1]:
lyst[j], lyst[j+1] = lyst[j+1], lyst[j]
return lyst
2)选择排序
选择排序(selection sort)是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。
步骤:
-
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
-
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
-
重复第二步,直到所有元素均排序完毕。
def selectionSort(lyst):
for i in range(len(lyst)-1):
min, index = lyst[i], i #记录最小值及最小值下标
for j in range(i+1,len(lyst)):
if min > lyst[j]:
min, index = lyst[j], j
lyst[i], lyst[index] = lyst[index], lyst[i]
return lyst
3)插入排序
插入排序(Insertion sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
步骤:
-
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
-
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
def insertSort(lyst):
for i in range(len(lyst)):
preIndex = i-1 #记录当前值的前一下标
current = lyst[i] #记录当前值
while preIndex >= 0 and lyst[preIndex] > current:
lyst[preIndex+1] = lyst[preIndex]
preIndex -= 1
lyst[preIndex+1] = current
return lyst
4)归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
-
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
-
自下而上的迭代;
步骤:
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 3 直到某一指针达到序列尾;
-
将另一序列剩下的所有元素直接复制到合并序列尾。
def mergeSort(lyst): #分
length = len(lyst)
if length < 2:
return lyst
middle = int(length / 2)
left = lyst[0:middle]
right = lyst[middle:length]
return merge(mergeSort(left), mergeSort(right))
def merge(left, right): #治
result = []
while left and right: #依次将左右中较小值放结果列表中
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
while left:
result.append(left.pop(0))
while right:
result.append(right.pop(0))
return result