冒泡排序:每一轮每次比较相邻元素的大小,将较大者往前移动(升序排列)或者往后移动(降序排列),
如此循环,直到所有元素排好序。举例:将序列8,12,13,4,7,2升序排列,第一轮对于第一个元素来说,
首先比较和第二个元素大小,发现比后者小,故而不动;再比较第2个元素和第3个元素,还是不动;比较第3和元素和第4个元素,大于后者,交换位置;
序列变为8,12,4,13,7,2;第一轮结束,序列变为8,12,4,7,2,13;如此循环,直到所有元素排好序。该方法的复杂度为O(n^2)。
"""
#-*- coding:utf-8 -*-
@Description:十大常用排序算法实现
@author xuehui
@time:2018-3-29
"""
def bubbleSort(seq):
if len(seq) <= 0:
return None
for i in range(len(seq)):
for j in range(len(seq)-1):
if seq[j] > seq[j+1]:
temp = seq[j]
seq[j] = seq[j+1]
seq[j+1] = temp
return seq
选择排序:每一轮每次比较第一个元素和其他位置元素的大小,将较大者移到序列末尾(升序排列)动或者移到序列开头(降序排列),
如此循环,直到所有元素排好序。选择排序可以看成是冒泡排序的优化;
相较于冒泡排序,选择排序每一轮都将最小或者最大的元素移到了最后;
而冒泡仅仅是交换相邻元素位置,每一轮都是从第一个元素开始;
举例:将序列8,12,13,4,7,2升序排列,对于第一个元素来说,
首先比较和第二个元素大小,发现比后者小,故而不动;再比较第1个元素和第3个元素,还是不动;比较第1和元素和第4个元素,大于后者,交换位置;
序列变为4,12,13,8,7,2;第一轮结束,序列变为2,12,13,7,8,4;如此循环,直到所有元素排好序。该方法的复杂度为O(n^2)。
def selectSort(seq):
if len(seq) <= 0:
return None
for i in range(len(seq)-1):
for j in range(i+1, len(seq)):
if seq[i] > seq[j]:
temp = seq[j]
seq[j] = seq[i]
seq[i] = temp
return seq
插入排序:不同于冒泡排序和选择排序通过比较和交换位置,插入排序是在保证,找到当前元素的合适位置,将元素直接插入,其后边的元素位置集体后移从而实现排序。
需要注意的是在进行插入排序时往往假设第一个元素的位置正确,其次在进行元素的插入时必须保证该元素前面的元素是有序的。
举例:9,3,5,8,1;第一轮对于元素3来说,比第一个元素小,将3插入,9后移;序列为:3,9,5,8,1;再查找第3个元素的合适位置,序列变为:3,5,9,8,1;如此序列最后变为:
1,3,5,8,9。该方法的计算复杂度为O(n^2)
def insertSort(seq):
if len(seq) <= 0:
return None
for i in range(1, len(seq)):
target = seq[i]
j = i
while j > 0 and target <= seq[j-1]:
seq[j] = seq[j - 1]
j -= 1
seq[j] = target
return seq
快速排序:快速排序实际当中同数量级(O(nlogn))的排序方法中平均性能最好的。其主要思想为选定一个基准(最后一个或者第一个),将所有大于基准的数据放在
右边,所有小于等于基准的放在左边,一次遍历之后基准位置挪到数组中间,整个数组可以分为两部分:小于等于基准部分和大于基准部分。
而后再分别对进行重复的分组操作,直到数组不能再切分为止。
该算法的复杂度为O(nlogn)。
def quickSort(seq,l,r):
if l >= r:
return seq
base = seq[l]
i = l
j = r
while i < j:
while i < j and seq[j] >= base:
j -= 1
seq[i] = seq[j]
while i < j and seq[i] <= base:
i += 1
seq[j] = seq[i]
seq[i] = base
quickSort(seq,l,i-1)
quickSort(seq,i+1,r)
return seq
希尔排序:该算法是插入排序的一种改进方法。插入排序对快排好序的数据进行操作时,其时间复杂度接近于O(n),但实际情况远远不止于此;
此外插入排序每次只能够移动一位数据。希尔排序通过将操作的数据分成几个区域,每次将一个数据向前移动一大步而不是插入排序那样每次移动一步。
具体来说,每次对数据进行分组放在一个表中,然后对每列数据进行插入排序;重复这过程,不过每次分组的长度越来越小(相当于每次排序的列越来越长),最后整个表就只有一列了。
假设有这样一组数[13 22 7 34 21 44 17 25 11 9],如果我们以分组长度为3开始进行排序,我们可以通过将这列表放在有3列的表中来更好地描述算法,这样他们就应该看起来是这样:
13 22 7
34 21 44
17 25 11
9
然后我们对每列进行排序:
9 21 7
13 22 11
17 25 44
34
如此直到结束
该算法的复杂度跟每一次步长(分组长度)有关,具体的可以参看维基百科上的该算法时间复杂度与步长的关系。
def shellSort(seq):
length = len(seq)
step = length // 2
while step > 0:
"""对每一列的的数据进行插入排序,排完以后更改步长"""
for i in range(step, length):
temp = seq[i]
j = i
while j >= step and seq[j-step] >= temp:
seq[j] = seq[j-step]
j -= step
seq[j] = temp
step = step // 2
return seq
归并排序:和快速排序一样归并排序也利用了分治的思想;其步骤可分为两部分,第一部分就是对数组进行分组(一般是二分)然后排序;第二部分就是合并已经排好序的数组;
对数组的分组并排序可以用递归实现,当数组只含有一个元素的时候可以认为数组是有序的,此时可以将数组进行合并,这意味着在算法实现的时候可以不断地进行递归划分直到数组中元素为1,而后进行合并;
其中有序数组的合并操作为:两个数组从头开始比较,取数组中元素较小的作为合并后数组的第一个元素,对应的分数组中索引后移,再与另一个分数组的元素比较,谁小取谁,直到一个数组为空,如此完成了数组的合并。
经过上述两步就完成了数组的归并排序。该算法的平均时间复杂度为O(nlogn)
def mergeSort(seq):
if len(seq) <= 1:
return seq
ind = len(seq)//2 #分组
leftArray = mergeSort(seq[:ind])
rightArray = mergeSort(seq[ind:])
return mergeArray(leftArray, rightArray) #合并
def mergeArray(leftArray, rightArray):
indLeft, indRight = 0, 0
mergedArray = []
while indLeft < len(leftArray) and indRight < len(rightArray):
if leftArray[indLeft] < rightArray[indRight]:
mergedArray.append(leftArray[indLeft])
indLeft += 1
else:
mergedArray.append(rightArray[indRight])
indRight += 1
"""当两个数组长度不一致的时候会出现某一数组元素比较完,另外一组元素还有剩余这种情况下直接把剩余元素放入就行"""
mergedArray += leftArray[indLeft:]
mergedArray += rightArray[indRight:]
return mergedArray
计数排序:计数排序是非比较的排序方法。其主要思想为:对于一个给定的整数数组,其当把数组内的每一个元素作为某一个数组的索引的时候,
由于索引是有序的,所以我们能够知道当前小于作为索引的整数的个数从而能够找到该整数在有序数组中的位置。对于重复的数字而言只要在第一个的基础上往后延展就行。
计数排序的步骤:
1.计算计数数组的长度(maximum-minimum+1)
2.统计待排序数组中每个整数的频次,并将(num-minum+1)作为计数数组的索引,将频次作为该索引对应的值
4.更新计数数组中的值,即将小于当前整数的个数count统计出来,从而确定当前整数的位置为count+1
3.将根据计数数组中的频次和索引取出整数放入输入数组中完成排序
技术排序是,其时间复杂度和空间复杂度为线性O(n+k),其中k为计数数组的长度
def countSort(seq):
maximum = max(seq)
minimum = min(seq)
countArray = [0]*(maximum-minimum+1)
for i in range(len(seq)):
countArray[seq[i]-minimum] = countArray[seq[i]-minimum] + 1 #统计数组中每个整数的频次
for j in range(1, len(countArray)): #将计数数组中的数据替换原待排序数组中的整数
countArray[j] = countArray[j] + countArray[j-1]
for k in range(len(seq)):
seq[countArray[seq[k]-minimum]-1] = seq[k]
countArray[seq[k]-minimum] -= 1
return seq
桶排序:桶排序的主要思想就是将待排序的数据按区间进行划分(均匀划分),放到M个桶里边,然后对每个桶中的数据进行排序,最后再进行合并。
计数排序其实是桶排的一种,相当于每个桶里边只有一个元素,然后进行排序。
桶排的时间复杂度为O(N+M*(N/M)log(N/M)),可知当M=N时,其时间复杂度为线性的。
def bucketSort(seq):
maximum = max(seq)
minimum = min(seq)
res = []
countArray = [0]*(maximum-minimum+1)
for i in range(len(seq)):
countArray[seq[i]-minimum] = countArray[seq[i]-minimum] + 1
for j in range(len(countArray)):
if countArray[j] != 0:
res += [j+minimum]*countArray[j]
return res
基数排序:基数排序是一种非比较型的排序方法,这里所说的非比较型个人理解为相较于其他排序方法该方法为对待排序数组中的每个数据进行直接比较。
该方法首先将待排数组中的所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序(桶排序)。当从最低位
排到最高位的时候整个数组就排好序了。需要注意的是基数排数不能够对有负数的情况进行排序。
其计算复杂度为O(kn),k为数位的长度
import math
def radixSort(seq):
k = int(math.ceil(math.log(max(seq)+1, 10)))
for i in range(1, k+1):
bucket = [[] for i in range(10)] # 不能用 [[]]*radix,否则相当于开了radix个完全相同的list对象
for val in seq:
bucket[val % (10 ** i) // (10 ** (i - 1))].append(val) # 獲得整數第K位數字 (從低到高)
del seq[:]
for each in bucket:
seq.extend(each) # 桶合并
return seq
堆排序:,堆排序是所有排序算法里边个人觉得最难的一种了,由于牵涉到二叉树的操作,对于初学者来说有些不好实现。
前面的几种算法都比较好理解,写起来比较顺手。恰好二叉树这块学的比较差,所以堆排被放到了最后来写。
堆排序利用了数据结构中的二叉堆来进行排序。二叉堆指的是完全二叉树或者近似二叉树,具有如下性质:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。
当用数组来表示堆的时候,父节点和左右子节点存在如下关系:任意一位置i上元素,其左儿子为2i+1上,右儿子在2i+2上;子节点i的父节点在位置floor((i-1)/2);
堆排序的整个流程为:
(1):将数组array[1…n]构建成为最大堆(升序)或者最小堆(降序)
(2):将数组中最后一个元素a[n]和第一个元素a[1]互换,使得最后一个元素成为最大值或者最小值
(3):将a[1…n-1]重新调整为最大堆或者最小堆,互换第一个元素和最后一个元素,以此类推,知道整个堆中只有一个元素。
堆排序是本质上还是属于选择排序,其最好、最坏、及平均时间复杂度都是O(nlogn).
def heapAdjust(seq, start, end):
fatherNode = start
sonNode = 2 * fatherNode + 1
while (sonNode<=end):
if sonNode+1 <= end and seq[sonNode] < seq[sonNode+1]:
sonNode += 1
if seq[sonNode] > seq[fatherNode]:
seq[fatherNode], seq[sonNode] = seq[sonNode], seq[fatherNode]
fatherNode = sonNode
sonNode = 2 * fatherNode + 1
else:
break
def heapSort(seq):
length = len(seq)
#构建堆
for i in range(length//2-1, -1, -1):
heapAdjust(seq, i, length-1)
#交换第一个元素和最后一个元素后调整堆
for i in range(length-1,0,-1):
seq[0], seq[i] = seq[i], seq[0]
heapAdjust(seq, 0, i-1)
return seq
#测试
seq = map(int, raw_input().split())
print bubbleSort(seq)
print selectSort(seq)
l = 0
r = len(seq) - 1
print quickSort(seq,l,r)
print insertSort(seq)
print shellSort(seq)
print mergeSort(seq)
print countSort(seq)
print bucketSort(seq)
print radixSort(seq)
print heapSort(seq)