排序算法实现
前言
最近处理数据时常会用到排序算法,故有整理各种排序算法的想法。博客中有很多非常好的文章排序算法介绍及实现,但经常发现其实现代码并不能很好的运行,或多或少都会有bug,故写下以下文章总结一下各算法中需要注意的问题。
插入排序
直接插入排序
算法思想:将记录逐个插入到已存在的有序表中,从而得到一个新的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序。
示意图
图1 直接插入排序
算法实现(Python实现)
第一版
class InsertSort(object):
def __init__(self):
return
def sort(self, mlist): # mlist为待排序列表(数组)
for count in xrange(1, len(mlist)):
index = count - 1 # index待比较位置, index+1为待插入位置
ele = mlist[count] # 复制为哨兵x,即存储待排序元素
if ele > mlist[count - 1]:
continue # 若待插入大于序列最后一个元素,直接插入。
while(ele < mlist[index]):
mlist[index + 1] = mlist[index] # 若ele小于待插入位置的元素则后移
index = index - 1 # 继续向左比较直到找到比ele大的元素(索引index),插入index+1位置
mlist[index + 1] = ele # 插入到正确位置
if __name__ == '__main__':
myList = [3, 1, 5, 7, 2, 4, 9, 6]
mInsertSort = InsertSort()
mInsertSort.sort(myList)
print myList
以上代码描述了插入排序算法的基本动作,但仔细看会发现这段代码并不能很好的运行:在ele比较插入位置时,若比序列中第一个元素还要小,索引index继续左移很显然逻辑错误。
第二版
class InsertSort(object):
def __init__(self):
return
def sort(self, mlist):
print mlist
for count in xrange(1, len(mlist)):
index = count - 1 # index待比较位置, index+1为待插入位置
ele = mlist[count] # 复制为哨兵x,即存储待排序元素
if ele > mlist[count - 1]:
print(mlist, ele, count)
continue # 若第i个元素大于i-1元素,直接插入。
while(ele < mlist[index] and index >= 0):
mlist[index + 1] = mlist[index] # 若ele小于待插入位置的元素则后移
index = index - 1 # 继续向左比较
mlist[index + 1] = ele # 插入到正确位置
print(mlist, ele, count) # 打印每趟排序的结果
if __name__ == '__main__':
myList = [3, 1, 5, 7, 2, 4, 9, 6]
mInsertSort = InsertSort()
mInsertSort.sort(myList)
print myList
在while循环中加入index >= 0的限制index取值,当待插入值比序列第一个元素还要小则插入当前位置.
效率分析
直接插入算法中基本操作(可理解为最内层循环执行的语句)的频度与待排序序列长度N成平方关系,则时间复杂度为O(N^2)。很显然对于这种算法效率并不是很高,下文介绍另一种插入排序算法(希尔排序)能很好提高效率。
希尔排序
算法思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1( < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。希尔排序实质是分组插入方法。
示意图
图2 希尔排序
算法实现(Python实现)
class ShellSort(object):
def __init__(self):
return
def insertSort(self, mlist, div):
for count in xrange(div, len(mlist)): # 取分组序列
index = count - div # index待比较位置, index+1为待插入位置
ele = mlist[count] # 复制为哨兵x,即存储待排序元素
if ele > mlist[count - div]:
print(mlist, ele, count)
continue # 若第i个元素大于i-1元素,直接插入。
while(ele < mlist[index] and index >= 0):
mlist[index + div] = mlist[index] # 若ele小于待插入位置的元素则后移
index = index - div # 继续向左比较
mlist[index + div] = ele # 插入到正确位置
print(mlist, ele, count) # 打印每趟排序的结果
return
def shellSort(self, mlist):
for div in xrange(len(mlist), 0, -1):
self.insertSort(mlist, div)
return
if __name__ == '__main__':
myList = [3, 1, 5, 7, 2, 4, 9, 6]
mInsertSort = ShellSort()
mInsertSort.shellSort(myList)
print myList
另一种实现
import random
class ShellSort(object):
def __init__(self):
return
def insertSort(self, mlist, div):
for head in xrange(0, div):
for count in xrange(head, len(mlist), div): # 取分组序列
index = count - div # index待比较位置, index+div为待插入位置
ele = mlist[count] # 复制为哨兵x,即存储待排序元素
if ele > mlist[count - div]:
print(mlist, ele, count)
continue # 若第i个元素大于i-div元素,直接插入。
while(ele < mlist[index] and index >= 0):
mlist[index + div] = mlist[index] # 若ele小于待插入位置的元素则后移
index = index - div # 继续向左比较
mlist[index + div] = ele # 插入到正确位置
print(mlist, ele, count) # 打印每趟排序的结果
def shellSort(self, mlist):
for div in xrange((len(mlist) / 2) + 1, 0, -1):
self.insertSort(mlist, div)
return
if __name__ == '__main__':
myList = list()
for count in xrange(100):
myList.append(random.randint(1, 100))
mInsertSort = ShellSort()
mInsertSort.shellSort(myList)
print myList
以上两种代码实现思路相似,只是第二种更接近图片描述的先分组再排序。
效率分析
希尔排序基于插入排序算法, 在此算法基础之上增加了分组特性,提高了效率。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(n^(2/3)),希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择,但是比O(n^2 )复杂度的算法快得多。
希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。 原因是,当n值很大时数据项每一趟排序需要的个数很少,但数据项的距离很长。当n值减小时每一趟需要和动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。Shell算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。(摘自百度百科)
选择排序
直接选择排序
算法思想
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
示意图
图3 简单选择排序
算法实现(Python实现)
简单插入排序
class selectSort(object):
def __init__(self):
return
def sort(self, data):
"""选择排序 """
for sortCount in range(len(data)):
min = sortCount # 待排序序列最小值索引
for index in range(sortCount, len(data)): # 获取待排序序列最小值索引
if data[index] < data[min]:
min = index
data[sortCount], data[min] = data[min], data[sortCount]
print '第%s趟排序' %sortCount, data
if __name__ == '__main__':
data = [94, 13, 83, 23, 65, 63, 15, 27, 48, 39]
selectSort = selectSort()
selectSort.sort(data)
二元选择排序
class selectSort(object):
def __init__(self):
return
# 简单选择排序
def sort1(self, data):
"""选择排序 """
for sortCount in xrange(len(data)):
min = sortCount # 待排序序列最小值索引
for index in xrange(sortCount, len(data)): # 获取待排序序列最小值索引
if data[index] < data[min]:
min = index
data[sortCount], data[min] = data[min], data[sortCount]
print '第%s趟排序' %sortCount, data
# 二元选择排序
def sort(self, data):
for sortCount in xrange(len(data) / 2):
min = sortCount # 待排序序列最小值索引
max = len(data) - sortCount - 1 # 待排序序列最大值索引
for index in xrange(sortCount, len(data) - sortCount): # 获取待排序序列最小值索引
if data[index] < data[min]:
min = index
continue
if data[index] > data[max]:
max = index
# 这里不能直接交换,因为交换会改变数组值造成索引指向偏离预期的值
# 错误的做法
# data[sortCount], data[min] = data[min], data[sortCount]
# data[len(data) - sortCount - 1], data[max], = data[max], data[len(data) - sortCount - 1]
# 正确的做法
data[sortCount], data[min] = data[min], data[sortCount]
if max == sortCount:
max = min
data[len(data) - sortCount - 1], data[max], = data[max], data[len(data) - sortCount - 1]
print '第%s趟排序' %sortCount, data
if __name__ == '__main__':
data = [94, 13, 83, 23, 65, 63, 15, 27, 48, 39]
selectSort = selectSort()
selectSort.sort(data)
效率分析
直接选择排序和二元选择排序时间复杂度为O(N^2),下文介绍简单选择排序改进算法“堆排序”。
待更新