数据结构python版(五)排序与查找基础

1、顺序查找及分析

通过下标按照顺序访问和查找数据项。
无序表中顺序查找代码如下:

def sequetialSearch(alist,item):
    pos=0
    found=False
    while pos <len(alist) and not found:
        if alist[pos]==item:
            found=True
        else:
            pos+=1
    return found

查找中比对次数决定算法的复杂度,为O(n)
如果数据项排了序,用顺序查找法时不需要比较n次,只需要大于则可以提前退出

def orderedSequetialSearch(alist,item):
    pos=0
    found=False
    stop=False
    while pos <len(alist) and not found and not stop:
        if alist[pos]==item:
            found=True
        else:
            if alsit[pos]>item:
                stop=True
            else:
                pos+=1
    return found

2、二分查找(针对有序表)

从中间开始匹配,比较中间项和item的大小,一次操作可以把比对范围减半

def binarySearch(alist,item):
    first=0
    last=len(alist)-1
    found=False
    while first<=last and not found:
        midpoint=(first+last)//2
        if alist[midpoint]==item:
            found=True
        else:
            if item<alist[midpoint]:
                last=midpoint-1
            else:
                first=midpoint+1
    return found

体现了分治策略,因此二分法也适合用递归算法来实现。

def binarySearch(alist,item):
    if len(alist)==0:
        return False
    else:
        midpoint=len(alist)//2
        if alist[midpoint]==item:
            return True
        else:
            if item<alist[midpoint]:
                return binarySearch(alist[:midpoint],item)
            else:
                return binarySearch(alist[midpoint+1:],item)

二分法的复杂度是O(logn),但他还用了切片操作,复杂度是O(k)。当然我们不需要实际进行切片,而只用传入两边的查找值即可。
但二分法需要先对数据项进行排序,因此需要根据实际应用情况使用。

3、冒泡排序和选择排序

(1)冒泡排序的思路是对无需表进行多趟比较交换,每趟包括多次两两相邻比较并将逆序数交换位置,这样能将本趟最大数据归位,故使用n-1次就可以完成排序。

def bubbleSort(alist):
    for passnum in range(len(alist)-1,0,-1):
    #n-1次
        for i in range(passnum):
            if alist[i]>alist[i+1]:
                temp=alist[i]
                alist[i]=alist[i+1]
                alist[i+1]=temp

冒泡排序的复杂度是O(n**2),但其优点是在链表上仍然能够进行该操作。另外通过监测每趟对比是否发生过交换可以提前确定排序是否完成。如果每趟比对没有任何交换发生则可以提前结束。

def shortBubbleSort(alist):
    exchanges=True
    passnum=len(alist)-1
    while passnum>0 and exchanges:
    #n-1次
        exchanges=False
        for i in range(passnum):
            if alist[i]>alist[i+1]:
                exchanges=True
                temp=alist[i]
                alist[i]=alist[i+1]
                alist[i+1]=temp
        passnum-=1

(2)选择排序
选择排序相当于是冒泡排序的优化,每趟并不进行多次交换,而是仅进行一次关于最大项的交换,即先记录最大项的位置,再跟本趟进行最后一项交换,此时对彼此数不变,仍为O(n**2)但交换次数为O(n)。

def selectionSort(alist):
    for passnum in range(len(alist)-1,0,-1):
        positionOfMax=0
        for i in range(passnum):
            if alist[i]>alist[i+1]:
                positionOfMax=i
        temp=alist[passnum]
        alist[passnum]=alsit[positionOfMax]
        alsit[positionOfMax]=temp

4、插入排序算法

插入排序维持一个已排好的子列表,其位置始终在列表的前部,然后逐步扩大这个子列表到全表。(类似扑克牌排序)
第1趟:子列表仅包含第一个数据项,将第二个数据项作为新项插入到子列表的合适位置中。
第k趟:将第k+1个项跟前k个项做对比并移动比自身大的数据项,空出位置来,以便自身加入子列表中。
经过n-1次对比和插入就可以完成排序。
O(n**2)
可以用原列表实现插入排序,第k+1项的位置空出来了,子列表中比它大的数向后移动

def insertSort(alist):
    for index in range(1,len(alist)):
        currentvalue=alist[index]
        position=index
        while position >0 and alist[position-1]>currentvalue:
            alist[position]=alsit[position-1]
                position-=1
        alist[position]=currentvalue

5、谢尔排序

列表越有序,插入排序的比对次数就会越少。从该情况入手,谢尔排序以插入排序为基础,对无序表进行间隔划分子列表,每个子列表都执行插入排序。
随着子列表的数量越来越少,无序表整体越来越接近有序,从而减少比较次数。
子列表间隔一般从n/2开始,每趟倍增,直到1

def shellSort(alist):
    sublistcount=len(alist)//2#间隔设定
    while sublistcount>0:
        for startposition in range(sublistcount):
        #子列表排序
            gapInsertionSort(alist,startposition,sublistcount)
            print("After increments of size",sublistcount,"The lsit is",alsit)
        sublistcount=sublistcount//2#间隔缩小
def gapInsertionSort(alist,start,gap):
    for i in range(start+gap,len(alsit),gap):
        currentvalue=alist[i]
        position=i
        while position>=gap and alist[position-gap]>currentvalue:
            alist[position]=alist[position-gap]
            position=position-gap
        alist[position]=currentvalue

看上去谢尔排序以插入排序为基础,并不会比插入排序更好,但是由于每趟使得列表更加有序,这过程中会减少很多原先需要的无效比对,因此会介于O(n)与O(n**2)之间

6、归并排序

将数据表持续分为两半,对两半分别进行归并排序。

def mergeSort(alist):
    if len(alist)>1:
       mid=len(alist)//2
       lefthalf=alist[:mid]
       righthalf=alist[mid:]
       mergeSort(lefthalf)
       mergeSort(righthalf)
       i=j=k=0
       #拉链式交错把左右半部分从小到大加入到结果列表中
       while i<len(lefthalf) and j<len(righthalf):
           if lefthalf[i]<righthalf[j]:
               alsit[k]=lefthalf[i]
               i=i+1
           else:
               alsit[k]=righthalf[j]
               j=j+1
           k=k+1
       #归并左半部分剩余项或右半部分剩余项
       while i<len(lefthalf):
           alsit[k]=lefthalf[i]
           i=i+1
           k=k+1
       while j<len(righthalf):
           alsit[k]=righthalf[j]
           j=j+1
           k=k+1

或者可以用一个更简洁的代码

def mergeSort(alist):
    if len(alist)>1:
       mid=len(alist)//2
       lefthalf=alist[:mid]
       righthalf=alist[mid:]
       mergeSort(lefthalf)
       mergeSort(righthalf)
       merged=[]
       #拉链式交错把左右半部分从小到大加入到结果列表中
       while lefthalf and righthalf:
           if lefthalf[0]<righthalf[0]:
               merged.append(lefthalf.pop(0))
           else:
               merged.append(righthalf.pop(0))
       merged.extend(right if right else left)
       return merged

分裂过程的复杂度是O(logn),归并的过程相当于分裂的每个部分其所有数据项都会被比较和放至一次,复杂度为O(n)
故总时间复杂度为O(nlogn)
最后可以不用切片操作,而只传递两端点
牺牲了额外1倍的存储空间

7、快速排序

依据一个中间值把数据表分为两半,找中位数比较浪费,因此可以随意找一个数来充当中值,eg找第一个数,之后不断地分裂。
分裂数据的手段:
甚至左右标,左标向右移动,碰到比中值大的就停止右标向左移动,碰到比中值小的就停止。然后把左右标所指的数据项进行交换。
继续移动,直到左标移到右标的右侧,停止移动,这时右标所指的位置就是中值应该位于的位置,将这个位置和中值就行交换,分裂完成(左边比中值小,右边比中值大)

def quickSort(alist):
#因为在开始的时候没有其他参数,只能用辅助函数帮忙
    quickSortHelper(alist,0,len(alist)-1)
def quickSortHelper(alist,first,last):
    splitpoint=partition(alist,first,last)#分裂
    quickSortHelper(alsit,first,splitpoint-1)
    quickSortHelper(alsit,splitpoint+1,last)
def partition(alist,first,last):
    midvalue=alist[first]#选定“中值”
    leftmark=first+1
    rightmark=last
    done=False
    while not done:
        while leftmark <=rightmark and \
                  alist[leftmark]<=midvalue:
            leftmark=leftmark+1
        while leftmark <=rightmark and \
                  alist[rightmark]>=midvalue:
            rightmark=rightmark+1
        if rightmark<leftmark:
            done=True
        else:
            temp=alsit[leftmark]
            alist[leftmark]=alsit[rightmark]
            alsit[rightmark]=temp
    temp=alist[first]
    alist[first]=alist[rightmark]
    alist[rightmark]=temp
    return rightmark
                  

在理想情况下如果分类总能把数据表分为相等的两部分,那么分裂部分就是O(logn),而移动需要每一项与中值进行对比,是O(n)。故综合起来是O(nlogn)
而且运行过程中不需要额外的存储空间。
要是极端情况接近于O(n**2),可以用三点取样法取出一个稍微有代表性的中值

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值