各种排序算法介绍与python实现

插入排序

     遍历数组,遍历到i时,a0,a1...ai-1是已经排好序的,取出ai,从ai-1开始向前和每个比较大小,如果小于,则将此位置元素向后移动,继续先前比较,如果不小于,则放到正在比较的元素之后。可见相等元素比较是,原来靠后的还是拍在后边,所以插入排序是稳定的。

     当待排序的数据基本有序时,插入排序的效率比较高,只需要进行很少的数据移动。

if list != None:
        if len(list) == 1:
            pass
        else:
            for i in range(1,len(list)): 
                temp = list[i]
                for j in range(i):
                    if list[j]>list[i]:
                        for k in range(i,j,-1):#
                            list[k]= list[k-1]
                            list[j] = temp


选择排序

 

     遍历数组,遍历到i时,a0,a1...ai-1是已经排好序的,然后从i到n选择出最小的,记录下位置,如果不是第i个,则和第i个元素交换。此时第i个元素可能会排到相等元素之后,造成排序的不稳定。

if list != None:
    for i in range(len(list)):
          min = i
          for j in range(i+1,len(list)):
                if list[min] > list[j]:
                    min = j
          if min != i:
                list[min],list[i] = list[i],list[min]


冒泡排序

 

     冒泡排序的名字很形象,实际实现是相邻两节点进行比较,大的向后移一个,经过第一轮两两比较和移动,最大的元素移动到了最后,第二轮次大的位于倒数第二个,依次进行。这是最基本的冒泡排序,还可以进行一些优化。

def bubble(list):
    for i in range(len(list)):
        for j in range(0,len(list)-1-i):
            if list[j] > list[j+1]:
                list[j],list[j+1]=list[j+1],list[j]

if __name__ == '__main__':                
    list1 = [2,3,5,7,8,9,6,54,1,42]
    bubble(list1)
print(list1)

 

     优化一:如果某一轮两两比较中没有任何元素交换,这说明已经都排好序了,算法结束,可以使用一个Flag做标记,默认为false,如果发生交互则置为true,每轮结束时检测Flag,如果为true则继续,如果为false则返回。

 

     优化二:某一轮结束位置为j,但是这一轮的最后一次交换发生在lastSwap的位置,则lastSwap到j之间是排好序的,下一轮的结束点就不必是j--了,而直接到lastSwap即可,:

快速排序

 

     快速排序首先找到一个基准,下面程序以第一个元素作为基准(pivot),然后先从右向左搜索,如果发现比pivot小,则和pivot交换,然后从左向右搜索,如果发现比pivot大,则和pivot交换,一直到左边大于右边,此时pivot左边的都比它小,而右边的都比它大,此时pivot的位置就是排好序后应该在的位置,此时pivot将数组划分为左右两部分,可以递归采用该方法进行。快排的交换使排序成为不稳定的。

def first_sort(numbers,i,j):
    temp = numbers[i]
    while i!=j:
        while i<j and numbers[j]>temp:
            j = j - 1
        if i<j:
            numbers[i] = numbers[j]
            i = i + 1
        while i<j and numbers[i]<temp:
            i = i + 1
        if i<j:
            numbers[j] = numbers[i]
            j = j - 1
        numbers[i] = temp
        return i
         
def quick_sort(numbers,i,j):
    if i<j:
        middle = first_sort(numbers,i,j)
        quick_sort(numbers,i,middle-1)
        quick_sort(numbers,middle+1,j)
 
if __name__=='__main__':
    quick_sort(L,0,len(L)-1)
print (L)

堆排序

 

      堆排序是把数组看作堆,第i个结点的孩子结点为第2*i+1和2*i+2个结点(不超出数组长度前提下),堆排序的第一步是建堆,然后是取堆顶元素然后调整堆。建堆的过程是自底向上不断调整达成的,这样当调整某个结点时,其左节点和右结点已经是满足条件的,此时如果两个子结点不需要动,则整个子树不需要动,如果调整,则父结点交换到子结点位置,再以此结点继续调整。

 

     下述代码使用的大顶堆,建立好堆后堆顶元素为最大值,此时取堆顶元素即使堆顶元素和最后一个元素交换,最大的元素处于数组最后,此时调整小了一个长度的堆,然后再取堆顶和倒数第二个元素交换,依次类推,完成数据的非递减排序。

 

     堆排序的主要时间花在初始建堆期间,建好堆后,堆这种数据结构以及它奇妙的特征,使得找到数列中最大的数字这样的操作只需要O(1)的时间复杂度,维护需要logn的时间复杂度。堆排序不适宜于记录数较少的文件

def BuildHeap(seq):
    length = len(seq)
    for i in range(0, int((length / 2)))[::-1]:
        AdjustHeap(seq, i, length)

def AdjustHeap(seq, root, length):
    lchild = 2 * root + 1
    rchild = 2 * root + 2
    rootmax = root
    if lchild < length and seq[lchild] > seq[rootmax]:
       rootmax = lchild
    if rchild < length and seq[rchild] > seq[rootmax]:
       rootmax = rchild
    if rootmax != root:# 如果做了堆调整,则rootmax的值等于左节点或者右节点的,进行对调值操作
       seq[rootmax], seq[root] = seq[root], seq[rootmax]
       AdjustHeap(seq, rootmax, length)

def HeapSort(seq):
    length = len(seq)
    BuildHeap(seq)#建立初始堆
    for i in range(0, length)[::-1]:
        seq[0], seq[i] = seq[i], seq[0]#将根节点取出与最后一位做对调
        AdjustHeap(seq, 0, i)#对前面len-1个节点继续进行堆调整过程
    return seq

if __name__ == "__main__":
    l = [2, 1]
    print(l)
    HeapSort(l)
    print(l)

归并排序

   归并排序是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。这需要将待排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行.logn.次,因此,总的时间复杂度为O(nlogn)。

   归并排序在归并过程中需 要与原始记录序列同样数量的存储空间存放归并结果,因此空间复杂度为O(n)

   归并算法需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。

 

def ConfiationAlgorithm(str):
    if len(str) <= 1: #子序列
        return str
    mid = (len(str) / 2)
    left = ConfiationAlgorithm(str[:mid])#递归的切片操作
    right = ConfiationAlgorithm(str[mid:len(str)]) 
    result = []
    #i,j = 0,0

    while len(left) > 0 and len(right) > 0:
        if (left[0] <= right[0]):
            #result.append(left[0])
            result.append(left.pop(0))
            #i+= 1
        else:
            #result.append(right[0])
            result.append(right.pop(0))
            #j+= 1

    if (len(left) > 0):
        result.extend(ConfiationAlgorithm(left))
    else:
        result.extend(ConfiationAlgorithm(right))
    return result   

希尔排序

 

     希尔排序是对插入排序的优化,基于以下两个认识:1. 数据量较小时插入排序速度较快,因为n和n2差距很小;2. 数据基本有序时插入排序效率很高,因为比较和移动的数据量少。

 

     因此,希尔排序的基本思想是将需要排序的序列划分成为若干个较小的子序列,对子序列进行插入排序,通过则插入排序能够使得原来序列成为基本有序。这样通过对较小的序列进行插入排序,然后对基本有序的数列进行插入排序,能够提高插入排序算法的效率。

 

     希尔排序的划分子序列不是像归并排序那种的二分,而是采用的叫做增量的技术,例如有十个元素的数组进行希尔排序,首先选择增量为10/2=5,此时第1个元素和第(1+5)个元素配对成子序列使用插入排序进行排序,第2和(2+5)个元素组成子序列,完成后增量继续减半为2,此时第1个元素、第(1+2)、第(1+4)、第(1+6)、第(1+8)个元素组成子序列进行插入排序。这种增量选择方法的好处是可以使数组整体均匀有序,尽可能的减少比较和移动的次数,二分法中即使前一半数据有序,后一半中如果有比较小的数据,还是会造成大量的比较和移动,因此这种增量的方法和插入排序的配合更佳。

 

     希尔排序的时间复杂度和增量的选择策略有关,上述增量方法造成希尔排序的不稳定性。

def ShellSort(data,flag):
    '''
    data: list,to be sorted
    flag: 0->asc,1->desc
    return a new sorted list
    '''
    retData=[]
    
    #copy data to retData
    for item in data:
        retData.append(item)    
    
    #sort retData
    count=len(retData)
    step=count/2;
    while step>0:
        i=0
        while i<count:
            j=i+step
            while j<count:
                t=retData.pop(j)
                k=j-step
                #asc
                if flag==0:
                    while k>=0:
                        if t>=retData[k]:
                            retData.insert(k+1, t)
                            break
                        k=k-step
                        
                    if k<0:
                        retData.insert(0, t)
                #desc
                elif flag==1:    
                    while k>=0:
                        if t<=retData[k]:
                            retData.insert(k+1, t)
                            break
                        k=k-step
                    if k<0:
                        retData.insert(0, t)
                        
                j=j+step
            
            i=i+1
        
        step=step/2
    
    return retData


data=ShellSort(test,0)
print 'Asc:',data

二叉树排序

 

     二叉树排序法借助了数据结构二叉排序树,二叉排序数满足三个条件:(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;(3)左、右子树也分别为二叉排序树。根据这三个特点,用中序遍历二叉树得到的结果就是排序的结果。

 

     二叉树排序法需要首先根据数据构建二叉排序树,然后中序遍历,排序时间复杂度为O(nlogn),构建二叉树需要额外的O(n)的存储空间,有相同的元素是可以设置排在后边的放在右子树,在中序变量的时候也会在后边,所以二叉树排序是稳定的。

 

     在实现此算法的时候遇到不小的困难,指针参数在函数中无法通过new赋值,后来采用取指针地址,然后函数设置BST** tree的方式解决。

猜你喜欢

转载自blog.csdn.net/z1164072826/article/details/79531132