排序与搜索(Python实现)

Jupyter notebook 模式传送门

首先总结一下:

  • 在无序表或者有序表上的顺序搜索,其时间复杂度为O(n);
  • 在有序表上进行二分查找,在最差情况下,复杂度为O(log n);
  • 散列表可以实现常数级时间的搜索;
  • 冒泡排序、选择排序和插入排序是O(n2)的算法;
  • 希尔排序在插入排序的基础上进行了改进,采用对递增子表排序的方法,其时间复杂度可以在O(n)和O(n^2)之间;
  • 归并排序的时间复杂度是O(nlog n),但归并的过程需要额外存储空间;
  • 快速排序的时间复杂度是O(nlog n),但如果分割点偏离列表中心的话,最坏情况下会退化到O(n^2)。快速排序不需要额外的存储空间。

搜索

顺序搜索

列表是无序的
  • 按照顺序依次遍历,直到找到要寻找的目标值
  • Code1函数接受两个参数,一个为列表,另一个为待寻找的目标值,如果找到了则返回True,否则返回False
  • 顺序搜索的复杂度为O(n),n为列表的长度
'''Code1'''
def sequentialSearch(alist,item):
    pos = 0
    found = False
    while pos < len(alist) and not found:
        if alist[pos] == item:
            found = True
        else:
            pos += 1
    return found    
'''测试'''
alist = [1,2,32,45,8,17,19,0,78]
sequentialSearch(alist,17)
True
sequentialSearch(alist,7)
False
列表是有序
  • 此时,当目标值在列表之中时,和上面的搜寻方式一样,依次进行对比;但是当目标值不在列表中时,当搜索到一定程度,我们就可以停止搜索了。
  • 假定,列表是递增的:
'''Code2'''
def orderedSequentialSearch(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 alist[pos] > item:
                stop = True
            else:
                pos = pos + 1
    return found
'''测试'''
alist = [1,3,4,6,17,18,20,99,]
orderedSequentialSearch(alist, 100)
False
alist = [1,3,4,6,17,18,20,]
orderedSequentialSearch(alist,3)
True

二分搜索

  • 对象:有序列表
  • 方法:将从中间项开始搜索,而不是从头开始搜索。
  • 复杂度:log(n)
def binarySerach(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
alist = [1,2,3,4,7,9,18,23,45,99]
binarySerach(alist,18)
True
binarySerach(alist, 100)
False
  • 上面的代码很好的展示了分而治之的策略,也很容易想到使用递归的方式:
'''Code3'''
def binarySerach(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 binarySerach(alist[:midpoint], item)
            else:
                return binarySerach(alist[midpoint+1:], item)
alist = [1,2,3,4,7,9,18,23,45,99]
binarySerach(alist,2)
True
binarySerach(alist,19)
False
9//2
4

散列

  • 散列表是一种数据集合,其中的每个数据通过某种特定的方式进行存储以方便以后的查找。散列表的每一个位置叫做槽,能够存放一个数据项,并以从0开始的递增的整数命名。

  • 某个数据项与在散列表中存储它的槽之间的映射关系叫做 散列函数.散列函数可以将任意一个数据项存储道级和中并返回一个介于槽命名区间内的整数。

  • 如果存在这样一个函数,它能将每一个数据都映射到不同的槽中,那么这样的函数叫做__完美散列函数__

  • 字典是Python中最有用的数据类型之一。字典是一个可以储存密钥-数据对的关联数据类型。
    密钥是用来查找和它相关联的数据值的。我们通常把这个想法称作映射。

  • 映射的抽象数据类型定义如下:它以一个密钥与一个数据值之间关联的无序集合作为结构。
    映射中的密钥都是独特的,以保证和数据值之间的一一对应关系。映射有以下的相关操作:

  • Map() 产生一个新的空映射,返回一个空映射的集合。

  • Put(key,val) 往映射中添加一个新的密钥-数据值对。如果密钥已经存在,那么将旧的数据
    值置换为新的。

  • get(key) 给定一个 key 值,返回关联的数据,若不存在,返回None 。

  • del 从映射中删除一个密钥-数据值对,声明形式为 del map[key]

  • len() 返回映射中的存储密钥-数据值对的个数

  • 接下来,使用两个列表来创建一个散列表类来实现映射的数据结构类型,其中一个称为槽,用来存储密钥;另一个称为data,用来存储数据。

class HashTable:
    def __init__(self):
        self.size = 11
        self.slots = [None] * self.size
        self.data = [None] * self.size
    '''散列函数使用的是最简单的求余法,使用的冲突解决技术是“+1”的线性探测'''
    def put(self,key,data):
        hashvalue = self.hashfunction(key, len(self.slots))
        if self.slots[hashvalue] == None:
            self.slots[hashvalue] = key
            self.data[hashvalue] = data
        else:
            if self.slots[hashvalue] == key:
                self.data[hashvalue] = data
            else:
                nextslot = self.rehash(hashvalue,len(self.slots))
                while self.slots[nextslot] != None and self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot,len(self.slots))
                    if self.slots[nextslot] == None:
                        self.slots[nextslot] = key
                        self.data[nextslot] = data
                    else:
                        self.data[nextslot] = data
    def hashfunction(self, key, size):
        return key%size
    def rehash(self,oldhash,size):
        return (oldhash+1)%size
    def get(self, key):
        startslot = self.hashfunction(key, len(self.slots))
        data = None
        stop = False
        found = False
        position = startslot
        while self.slots[position] != None and not found and not stop:
            if self.slots[position] == key:
                found = True
                data = self.data[position]
            else:
                position = self.rehash(position,len(self.slots))
                if position == startslot:
                    stop = True
        return data
    def __getitem__(self,key):
        return self.get(key)
    def __setitem__(self,key,data):
        return self.put(key,data)
H = HashTable()
H[54] = "cat"
H[26] = "dog"
H[20] = "chicken"
H[17] = "tiger"
H[22] = "lion"
H[33] = "jiji"
H[20]
'chicken'
H[54]
'cat'
H[20] = "duck"
H[20]
'duck'
H.data
['lion', None, None, None, 'dog', None, 'tiger', None, None, 'duck', 'cat']
H.slots
[22, None, None, None, 26, None, 17, None, None, 20, 54]

排序

冒泡排序

  • 要对一个列表多次重复遍历。它要比较相邻的两项,并且交换顺序排错的项。每对
    列表实行一次遍历,就有一个最大项排在了正确的位置。
def bubbleSort(alist):
    for passnum in range(len(alist)-1,0,-1):
        for i in range(passnum):
            if alist[i] > alist[i+1]:
                alist[i], alist[i+1] = alist[i+1] , alist[i]
alist = [1,3,9,4,7,2,10,30,22,32]
bubbleSort(alist)
alist
[1, 2, 3, 4, 7, 9, 10, 22, 30, 32]

短路冒泡排序

def shortBubbleSort(alist):
    exchanges = True
    passnum = len(alist)-1
    while passnum > 0 and exchanges:
        exchanges = False
        for i in range(passnum):
            if alist[i] > alist[i+1]:
                exchanges = True
                alist[i],alist[i+1] = alist[i+1],alist[i]
                passnum -= 1
alist = [1,3,9,4,7,2,10,30,22,32]
shortBubbleSort(alist)
alist
[1, 2, 3, 4, 7, 9, 10, 22, 30, 32]

选择排序

  • 选择排序提高了冒泡排序的效率,每次遍历只交换一次数据。如果按升序排序,则第一次遍历则最大值已经归位,第二次遍历次大值归位
  • 需要遍历n-1次。共两层循环,第一次循环为遍历的次数,第二层循环寻找最大值所处的位置。
def selectSort(alist):
    for fillslot in range(len(alist)-1,0,-1):
        positionOfMax = 0
        for location in range(1,fillslot+1):
            if alist[location] > alist[positionOfMax]:
                positionOfMax = location
        temp = alist[fillslot]
        alist[fillslot] = alist[positionOfMax]
        alist[positionOfMax] = temp
        
alist = [54,24,92,77,31,55,20,12]
selectSort(alist)
alist
[12, 20, 24, 31, 54, 55, 77, 92]

插入排序

  • 复杂度为O(n^2)
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] = alist[position-1]
            position = position - 1
            alist[position] = currentvalue
alist = [54,12,23,44,77,31,55,20,28]
insertSort(alist)
alist
[12, 20, 23, 28, 31, 44, 54, 55, 77]
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] ,alist[position-1] = alist[position-1] ,alist[position]
            position = position - 1
alist = [54,12,23,44,77,31,55,20,10,8,0,28]
insertSort(alist)
alist
[0, 8, 10, 12, 20, 23, 28, 31, 44, 54, 55, 77]

希尔排序

  • 希尔排序有时又叫做“缩小间隔排序”,它以插入排序为基础,将原来要排序的列表划分为一
    些子列表,再对每一个子列表执行插入排序,从而实现对插入排序性能的改进。划分子列的特定
    方法是希尔排序的关键。我们并不是将原始列表分成含有连续元素的子列,而是确定一个划分列
    表的增量“i”,这个i更准确地说,是划分的间隔。然后把每间隔为i的所有元素选出来组成子列
    表。
def shellSort(alist):
    sublistcount = len(alist) // 2
    while sublistcount > 0:
        for startposition in range(sublistcount):
            gapInsertSort(alist, startposition,sublistcount)
        print("after increments of size: ",sublistcount, "The list is: ",alist)
        sublistcount = sublistcount//2

def gapInsertSort(alist,start,gap):
    for i in range(start+gap, len(alist), gap):
        currentvalue = alist[i]
        position = i
        while position >= gap and alist[position-gap] > currentvalue:
            alist[position] = alist[position-1]
            position = position - 1
            alist[position] = currentvalue

alist = [54,12,23,44,77,31,55,20,28]
shellSort(alist)
alist
after increments of size:  4 The list is:  [54, 12, 23, 44, 77, 31, 28, 55, 20]
after increments of size:  2 The list is:  [54, 23, 12, 44, 28, 20, 77, 31, 55]
after increments of size:  1 The list is:  [12, 20, 23, 28, 31, 44, 54, 55, 77]





[12, 20, 23, 28, 31, 44, 54, 55, 77]
list(range(4,9,4))
[4, 8]
def shellSort(alist):
    sublistcount = len(alist) // 2
    while sublistcount > 0:
        for startposition in range(sublistcount):
            gapInsertSort(alist, startposition,sublistcount)
        print("after increments of size: ",sublistcount, "The list is: ",alist)
        sublistcount = sublistcount//2

def gapInsertSort(alist,start,gap):
    for i in range(len(alist)//gap):
        currentvalue = alist[i]
        position = i
        while position >= gap and alist[position-gap] > currentvalue:
            alist[position] = alist[position-1]
            position = position - 1
            alist[position] = currentvalue

alist = [54,12,23,44,77,31,89,39,51,55,20,28]
shellSort(alist)
alist
after increments of size:  6 The list is:  [54, 12, 23, 44, 77, 31, 89, 39, 51, 55, 20, 28]
after increments of size:  3 The list is:  [54, 12, 44, 23, 77, 31, 89, 39, 51, 55, 20, 28]
after increments of size:  1 The list is:  [12, 20, 23, 28, 31, 39, 44, 51, 54, 55, 77, 89]





[12, 20, 23, 28, 31, 39, 44, 51, 54, 55, 77, 89]

alist = [54,12,23,44,77,31,55,20,28]
shellSort(alist)
alist
after increments of size:  4 The list is:  [54, 12, 23, 44, 77, 31, 55, 20, 28]
after increments of size:  2 The list is:  [54, 12, 23, 44, 77, 31, 55, 20, 28]
after increments of size:  1 The list is:  [12, 20, 23, 28, 31, 44, 54, 55, 77]





[12, 20, 23, 28, 31, 44, 54, 55, 77]

归并排序

  • 归并排序是一种递归算法,它持续地将一个列表分成两半。如果列表是空的或者
    只有一个元素,那么根据定义,它就被排序好了(最基本的情况)。如果列表里的元素超过一
    个,我们就把列表拆分,然后分别对两个部分调用递归排序。一旦这两个部分被排序好了,那么
    这种被叫做归并的最基本的操作,就被执行了。归并是这样一个过程:把两个排序好了的列表结
    合在一起组合成一个单一的,有序的新列表。
  • 复杂度为O(nlogn)
def merge_sort(lst):
    if len(lst) <= 1:
        return lst          # 从递归中返回长度为1的序列

    middle = len(lst) // 2
    left = merge_sort(lst[:middle])     # 通过不断递归,将原始序列拆分成n个小序列
    right = merge_sort(lst[middle:])
    return merge(left, right)

def merge(left, right):
    i, j = 0, 0
    result = []
    while i < len(left) and j < len(right):  # 比较传入的两个子序列,对两个子序列进行排序
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])         # 将排好序的子序列合并
    result.extend(right[j:])
    return result
alist = [54,12,23,44,77,31,89,39,51,55,20,28]
merge_sort(alist)
[12, 20, 23, 28, 31, 39, 44, 51, 54, 55, 77, 89]

快速排序

  • 快速排序用了和归并排序一样分而治之的方法来获得同样的优势,但同时不需要使用额外的
    存储空间。
  • 快速排序首先选择一个中值。虽然有很多不同的方法来选择这个数值,我们将会简单地选择
    列表里的第一项。中值的作用在于协助拆分这个列表。中值在最后排序好的列表里的实际位置,
    我们通常称之为分割点的,是用来把列表变成两个部分来随后分别调用快速排序函数的。
  • 时间复杂度:O(nlgn),但是最坏情况下会退化到O(n^2)
def QuickSort(myList,start,end):
    #判断low是否小于high,如果为false,直接返回
    if start < end:
        i,j = start,end
        #设置基准数
        base = myList[i]

        while i < j:
            #如果列表后边的数,比基准数大或相等,则前移一位直到有比基准数小的数出现
            while (i < j) and (myList[j] >= base):
                j = j - 1

            #如找到,则把第j个元素赋值给第个元素i,此时表中i,j个元素相等
            myList[i] = myList[j]

            #同样的方式比较前半区
            while (i < j) and (myList[i] <= base):
                i = i + 1
            myList[j] = myList[i]
        #做完第一轮比较之后,列表被分成了两个半区,并且i=j,需要将这个数设置回base
        myList[i] = base

        #递归前后半区
        QuickSort(myList, start, i - 1)
        QuickSort(myList, j + 1, end)
    return myList
alist = [54,12,23,44,77,31,89,39,51,55,20,28]
QuickSort(alist,0,len(alist)-1)
[12, 20, 23, 28, 31, 39, 44, 51, 54, 55, 77, 89]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值