排序算法实现

排序算法实现

前言

最近处理数据时常会用到排序算法,故有整理各种排序算法的想法。博客中有很多非常好的文章排序算法介绍及实现,但经常发现其实现代码并不能很好的运行,或多或少都会有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)


简单选择排序,每趟循环定位待排序序列最小值索引。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可

二元选择排序

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),下文介绍简单选择排序改进算法“堆排序”。

待更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值