我们通常所说的排序算法往往指的是内部排序算法,即数据记录在内存中进行排序。排序算法大体可分为两种:
a.比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
b.非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。
有一点我们很容易忽略的是排序算法的稳定性(腾讯校招2016笔试题曾考过)。
排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
例如,对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。
其次,说一下排序算法稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。
插入排序
插入排序是一种简单直观的排序算法。它的工作原理非常类似于我们抓扑克牌。对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
直接插入排序即顺序遍历排序序列,寻找插入点。在直接插入排序的基础上,改进算法采用二分查找的方式寻找插入点。
希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
# 插入排序——直接插入排序
def sort1(self,list):
for i in range(1,len(list)):
get = list[i] # 手里拿到的牌
base = i-1 # 与其前面的牌进行比较
while(base>=0 and list[base]>get):
list[base+1] = list[base]
base = base - 1
list[base+1] = get
return list
# 插入排序——二分插入排序
def sort2(self,list):
for i in range(1,len(list)):
get = list[i] # 手里拿到的牌
start = 0
end = i-1
while(start<=end): # 二分查找插入位置
mid = (start+end)/2
if list[mid] <= get:
start = mid+1
elif list[mid]>get:
end = mid-1
for j in range(i,start,-1): # 将插入位置以后的均后移一位
list[j] = list[j-1]
list[start] = get
return list
# 插入排序——希尔排序
def sort3(self,list): # 将数组按照等间距分成几个组,对小组进行组内的一个插入排序,不断缩小抽取的间距,最后其实为直接插入排序,间距为1
step = 2 # 间距初始值设置为2,接下来的间距会不断缩短一半
group = len(list)/step # 组数
while group>0:
for i in range(0,group):
for j in range(i+group,len(list),group): # 组内进行插入排序,
get = list[j] # 手里拿到的牌
base = j-group # 与其前面的牌进行比较,从后向前的方式进行比较
while base>=0 and list[base]>get:
list[base+group]=list[base]
base = base-group
list[base+group]=get
group=group/step # 组数由间距设定
return list
冒泡排序
冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。冒泡排序算法的运作如下:
- 比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
鸡尾酒排序,也叫定向冒泡排序,是冒泡排序的一种改进。此算法与冒泡排序的不同处在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能。
# 交换排序——冒泡排序
def sort5(self,list):
length = len(list)
while length>=0:
for i in range(0,length-1):
if list[i] > list[i+1]:
list[i], list[i+1] = list[i+1], list[i]
length = length - 1
return list
# 交换排序——冒泡排序的改进——鸡尾酒排序,
def sort6(self,list):
start = 0
end = len(list)-1
while(start < end):
for i in range(start,end):
if list[i] > list[i+1]:
list[i], list[i+1] = list[i+1], list[i]
end = end - 1
for j in range(end-1,start-1,-1):
if list[j] > list[j+1]:
list[j], list[j+1] = list[j+1], list[j]
start = start + 1
return list
归并排序
归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
# 归并排序
def sort8(self, list):
if len(list) <= 1: # 子序列
return list
mid = (len(list) / 2)
left = self.sort8(list[:mid]) # 递归的切片操作
right = self.sort8(list[mid:len(list)])
result = []
i,j=0,0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i = i + 1
else:
result.append(right[j])
j = j + 1
print result
result += left[i:]
result += right[j:]
return result
快速排序
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
- 从序列中挑出一个元素,作为"基准"(pivot).
- 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
- 对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了
# 交换排序——快速排序
def sort7(self,list,left,right):
if left >= right:
return
base = list[right]
flag = left
for i in range(left,right):
if list[i] <= base:
list[flag],list[i]=list[i],list[flag]
flag = flag + 1
list[flag],list[right] = list[right],list[flag]
self.sort7(list,left,flag-1)
self.sort7(list,flag+1,right)
return list
选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
# 选择排序——简单选择排序
def sort4(self,list):
for i in range(0,len(list)-1):
min = list[i]
for j in range(i+1,len(list)): # 从未排序的序列中找到最小值和最小值对应的标号
if list[j]<min:
min = list[j]
label = j
tmp = list[i]
list[i]=min
list[label]=tmp
return list