列表查找和排序

  1、递归  

#1、打印抱着抱着抱着我的小鲤鱼的我的我的我

def test1(n):
    if n>0:
        print('抱着',end='')
        test1(n-1)
        print('的我',end='')
    else:
        print('我的小鲤鱼',end='')
test1(3)

#2、汉罗塔问题
def hanoi(n,A,B,C):
    '''
    n个盘子时
    先把n-1个圆盘从A经过C移动到B
    把第n个圆盘从A移动到C
    把n-1个圆盘从B经过A移动到C
    '''
    if n > 0:
        #n个盘子从A经过B移动到C
        hanoi(n-1,A,C,B)
        print("%s - > %s" % (A,C))
        hanoi(n-1,B,A,C)

hanoi(4,"A","B","c")

2、列表查找 

(1) 顺序查找

最优时间复杂度:O(1)
最坏时间复杂度:O(n)
 

def linear_search(alist,item):
    try:
        i = alist.index(item)
        return alist[i],i
    except:
        return None

if __name__ == "__main__":
    li = [11,22,33,44,55,66,77,88,99]
    print(linear_search(li,10))  # None
    print(linear_search(li,55))  # (55, 4) 

(2) 二分查找 

二分查找又称折半查找,优点比较次数少查找速度快平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方流适用于不经常变动而查找频繁的有序列表

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否者利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,便查找成功、或直到子表不存在为止,此时查找不成功。

最优时间复杂度:O(1)
最坏时间复杂度:O(log n) 

(a) 非递归版本 

def binary_search(alist,item):
    n = len(alist)
    first = 0
    last = n-1
    while first <= last:
        mid = (first + last) // 2
        if alist[mid] == item:
            return alist[mid],alist.index(item)
        elif item < alist[mid]:
            last = mid - 1
        else:
            first = mid + 1
    return None

if __name__ == "__main__":
    li = [11,22,33,44,55,66,77,88,99]
    print(binary_search(li,10))  # None
    print(binary_search(li,88))  # (88, 7)

(b) 递归版本 

def binary_search(alist,item):
    n = len(alist)
    if n > 0:
        mid = n // 2
        if alist[mid] == item:
            return True
        elif item < alist[mid]:
            return binary_search(alist[:mid],item)
        else:
            return binary_search(alist[mid+1:],item)
    return None

if __name__ == "__main__":
    li = [11,22,33,44,55,66,77,88,99]
    print(binary_search(li,10))  # None
    print(binary_search(li,66))  # True
'''
列表查找:从列表中查找指定元素
    输入:列表、待查找元素
    输出:元素下标或未查到找到元素
'''

'''
顺序查找
    重列表第一个元素开始,顺序进行搜索,直到找到为止
'''

'''
二分查找
    从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半
'''
import time
def cal_time(func):
    def wrapper(*args,**kwargs):
        t1 = time.time()
        result = func(*args,**kwargs)
        t2 =time.time()
        print("%s running time:%s secs." %(func.__name__,t2-t1))
        return result
    return wrapper

#二分法查找
@cal_time
def binary_search(li,val):
    low = 0
    high = len(li)-1
    while low <= high:
        mid = (low + high) // 2
        if li[mid] > val:
            high =mid - 1
        elif li[mid] < val:
            low = mid + 1
        else:
            return mid
    else:
        return None

#顺序查找
@cal_time
def linear_search(li,val):
    try:
        i = li.index(val)
        return i
    except:
        return None

#递归版本的二分查找
@cal_time
def bin_search_rec(li,val,low,high):
    if low <= high:
        mid = (low + high) // 2
        if li[mid] == val:
            return mid
        elif li[mid] > val:
            return bin_search_rec(li,val,low,mid - 1)
        else:
            return bin_search_rec(li,val,mid + 1,high)

li = list(range(0,1000000,2))

print(binary_search(li,30006))
print(linear_search(li,30006))
low = 0
high = len(li) - 1
print(bin_search_rec(li,30006,low,high))
其它表示方式 

3、列表排序

将无序列表变为有序列表

 

(1)冒泡排序

# n = len(alist)
# j代表外层循环,要走多少趟
# i代表内层循环,从头走到尾
# j = 0  i = 0~n-2   range(0,n-1)
# j = 1  i = 0~n-3   range(0,n-1-1)
# j = 2  i = 0~n-4   range(0,n-1-2)
# j = n  i range(0,n-1-j)

# 冒泡排序
# 最优时间复杂度:n (表示遍历一次没有任何可以交换的元素,排序结束)
# 最坏时间复杂度:n方
# 稳定性:稳定
def bubble_sort(alist): n = len(alist) for j in range(n-1): exchange = False for i in range(0,n-1-j): if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1],alist[i] exchange = True if not exchange: return import random li = list(range(10)) random.shuffle(li) print(li) bubble_sort(li) print(li)

(2)选择排序

#选择排序
#从无序取找出最小值,和第一个值做交换

# 最优时间复杂度: n方
# 最坏时间复杂度: n方
# 稳定性: 不稳定
def select_sort(alist):
    n = len(alist)
    for j in range(n-1):
        # 第j趟:有序区li[0:j],无序区li[j+1:n-1]
        min_index = j  #记录无序区最小值的下标
        # 找出最小值的下标,从无序区的第一个位置开始找
        for i in range(j+1,n):
            if alist[min_index] > alist[i]:
                min_index = i
        alist[j],alist[min_index] = alist[min_index],alist[j]

import random
li = list(range(10))
random.shuffle(li)
print(li)
select_sort(li)
print(li)

(3)插入排序

法一:

#插入排序
#从右侧无序取出第一个值,放入左侧有序序列的正确位置上

# 最优时间复杂度: n  (升序排序,序列已近处于升序状态)
# 最坏时间复杂度: n方
# 稳定性: 稳定

def insert_sort(alist):
    n = len(alist)
    # 外层循环,从右边的无序序列中取出多少个元素执行这样的过程
    for j in range(1,n):
        i = j
        # 内层循环,执行从右边的无序序列中取出第一个元素,即i位置的元素,然后插入到前面正确的位置中
        while i > 0:
            if alist[i] < alist[i-1]:
                alist[i-1],alist[i] = alist[i],alist[i-1]
                i -= 1
            else:
                break

import random
li = list(range(10))
random.shuffle(li)
print(li)
insert_sort(li)
print(li)

法二:

每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空

         

#插入排序
@cal_time
def insert_sort(li):
    for i in range(1,len(li)):
        # i既表示趟数,也表示摸到的牌的下标
        j = i - 1  #j为手里最后一张牌,从这张牌开始一直往前看
        #一直到j>=0这张牌还有 并且这张牌还比摸到的牌大,那么这张牌就要往后移一个位置,往前看前一张牌
        tmp = li[i]
        while j >=0 and li[j] > tmp:
            li[j+1] = li[j]  #牌往后移
            j -= 1             #j往前看
        li[j+1] = tmp
li = list(range(10000))
random.shuffle(li)  #打乱顺序
insert_sort(li)
print(li)

(4)希尔排序 

希尔排序(Shell Sort)是插入排序的一种。也种缩小增量排序,是直接插入排序筛法的一种更高效的改进版本,希尔排序是非稳定排序算法,
该方法因DLShell于1959年提出而得名。希尔排序是把记录按下标的一定增量分组,对每钮使用直接插入排序宽法排序;随看增量逐渐减少,
每组包含的关键词越来越多,当增量减至1时,整个文件偿没分成一组,算法便终止。
希尔排序过程 希尔排序的基本想想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。
最后整个表就只有一列了,将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
 

算法实现: 

#希尔排序
# 最优时间复杂度: 根据步长序列的不同而不同
# 最坏时间复杂度: n方
# 稳定性: 不稳定

def shell_sort(alist):
    n = len(alist)
    gap = n // 2
    # gap变化到0 之前,插入算法执行的次数
    while gap > 0:
        # 插入算法,与普通的插入算法的区别就是gap步长
        for i in range(gap,n):
            # j =[gap,gap+l,gap+2,gap+3,…,n-1]
            j = i
            while j > 0:
                if alist[j] < alist[j-gap]:
                    alist[j],alist[j-gap] = alist[j-gap],alist[j]
                    j -= gap
                else:
                    break
        # 缩短gap步长
        gap //= 2

import random
li = list(range(10))
random.shuffle(li)
print(li)
shell_sort(li)
print(li) 

(5)快速排序(必须掌握,应用更广泛) 

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

步骤为:
1.从数列中挑出一个元素,称为“基准”(pavot),
2.重然排序数列,所有元素比基准值小的摆放在基准的前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边),在这个分区结来之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归地(recursive)小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情况,是数列的大小是零或一,也就是永运都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的选代(iteration)中,它至少会把一个元素摆到它最后的位置去。 

法1:

# 最优时间复杂度:nlogn
# 最坏时间复杂度: n方
# 稳定性:不稳定

def quick_sort(alist,first,last):
    # 递归退出条件
    if first >= last:
        return
    # 设定起始元素为要寻找位置的基准元素
    mid_value = alist[first]
    # low为左边的由左指向右的游标
    low = first
    # high为右边的由右指向左的游标
    high = last
    while low < high:
        # high 左移
        while low < high and alist[high] >= mid_value:
            high -= 1
        # 将high指向的元素放到low位置上
        alist[low] = alist[high]

        # low右移
        while low < high and alist[low] < mid_value:
            low += 1
        # 将low指向的元素放到high位置上
        alist[high] = alist[low]

    # 从循环退出时,low = high
    alist[high] = mid_value

    # 对low左边的列表执行快速排序
    quick_sort(alist,first,low-1)

    # 对low右边的列表执行快速排序
    quick_sort(alist,low+1,last)

import random
li = list(range(10))
random.shuffle(li)  #打乱顺序
print(li)
quick_sort(li,0,len(li)-1)
print(li)

法2:
时间复杂度:n * log n

#快速排序

#归位
def partition(data,left,right):
    #随机化
    i = random.randint(left,right)
    li[left] , li[i] = li[i] , li[left]

    tmp = data[left]         #把left的值存起来
    while left < right:

        #从右边找比tmp小的数
        while left < right and data[right] >= tmp: #降序排只需改ht] <= tmp与下面的data[left] >= tmp
            right -=1
        #把找到的数扔到左边去
        data[left] = data[right]
        #从左边找比tmp大的数
        while left < right and data[left] <= tmp: #
            left += 1
        #把找到的数扔到右边去
        data[right] = data[left]
    #归位
    data[left] = tmp
    return left


#二分法排序
def _quick_sort(li,left,right):
    if left < right:
        mid = partition(li,left,right)  #mid为返回的归位元素的下标
        _quick_sort(li,left,mid-1)
        _quick_sort(li,mid+1,right)

#避免打印一系列时间
@cal_time
def quick_sort(li):
    return _quick_sort(li,0,len(li)-1)

li = list(range(10000))
random.shuffle(li)  #打乱顺序
quick_sort(li)
print(li)

  (6)归并排序 

归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位,
然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

# 归并排序
# 最优时间复杂度: nlogn
# 最坏时间复杂度: nlogn
# 稳定性:稳定
# 归并排序之后拿到的是一个新的有序列表,
# 所以会产生一个同等大小空间进行数据的保存与处理

def merge_sort(alist):
    n = len(alist)
    if n <= 1:
        return alist
    mid = n // 2
    # left 采用归并排序后形成的有序的新的列表
    left_li = merge_sort(alist[:mid])
    # right 采用归并排序后形成的有序的新的列表
    right_li = merge_sort(alist[mid:])

    # 将两个有序的子序列合并成为一个新的整体
    # merge(left,right)
    # 建立指向左边和指向右边的两个游标,默认指向两个子序列的第一个元素的位置
    left_pointer, right_pointer = 0,0
    result = []
    # 循环退出条件:左游标或者游标走到列表头部
    while left_pointer < len(left_li) and right_pointer < len(right_li):
        # 如果左边列表游标所指向的元素小于等于右边列表游标所指向的元素
        if left_li[left_pointer] <= right_li[right_pointer]:
            # 把左边列表游标所指向的元素加入新建的列表中
            result.append(left_li[left_pointer])
            # 左游标右移
            left_pointer += 1
        else:
            # 把右游标所指向的元素加入新建的列表中
            result.append(right_li[right_pointer])
            # 右游标右移
            right_pointer += 1
    # 退出循环的时候说明左游标或者右游标已经走到头,
    # 把剩余的右游标或者左游表指向的元素添加到新建的列表中
    result += left_li[left_pointer:]
    result += right_li[right_pointer:]
    return  result

import random
li = list(range(10))
random.shuffle(li)
print(li)
merge_sort(li)
print(li)
li = merge_sort(li)
print(li)
#---------------------------------------执行流程------------------------------------------
# 示例:[54,26,93,17,77,31,44,55,20]
merge_sort([54,26,93,17,77,31,44,55,20])  
# 进入 left_li = merge_sort(alist[:mid])
left_li = merge_sort([54,26,93,17])
        # 进入 left_li = merge_sort(alist[:mid])
        left_li = merge_sort([54,26])
            left_li = merge_sort([54])   # 此时n=1,return得到返回值[54],
                left_li = [54]
            ritht_li = merge_sort([26])
                right_li = [26]
            while # 进入while循环,合并这两个列表并返回
            return reslut   #此时57行的代码得到了返回值 left_li = [26,54]
        # 进入right_li = merge_sort(alist[mid:])
        right_li = merge_sort ([93,17])
            left_li = merge_sort([93])
                right_li = [93]
            right_li = merge_sort([17])
                right_li = [17]
            while # 进入while循环,合并这两个列表并返回
            return reslut   #此时64行的代码得到了返回值 right_li = [17,93]
        while # 进入while循环,把上面两个列表合并成一个有序列表
        return result  #此时最外层left_li = [17,26,54,93]
# 进入left_li = merge_sort(alist[:mid])
right_li = merge_sort([77,31,44,55,20])
    left_li = merge_sort([77,31])
        return [31,73]
    right_li = merge_sort([44,55,20]) # 执行完后返回 right_li = [44,55]
        left_li = merge_sort([44,55])
            left_li = [44]
            right_li = [55]
            while
            return [44,55]
        right_li= [20]
        while
        return[20,44,55]
    while
    return [20,31,44,73]  # 此时最外层 right_li = [20,31,44,73]
# 进入while循环
while  #把上面两个列表合并为一个有序列表
# 返回结果
return result   # result = [17,20,26,31,44,54,55,77,93]

(7)堆排序

  

 二叉树的实现:

class Node(object):
    """定义节点"""
    def __init__(self,item):
        self.elem = item
        self.lchild = None
        self.rchild = None

class Tree(object):
    """二叉树的实现"""
    def __init__(self):
        self.root = None

    def add(self,item):
        """添加节点"""
        node = Node(item)
        if self.root is None:  # 如果根节点为空
            self.root = node  # 把要添加的节点添加到此处
            return
        queue = [self.root]
        while queue: # 只要队列不为空,进行以下操作
            cur_node = queue.pop(0)  # 从当前队列中读出一个节点进行处理

            if cur_node.lchild is None: # 如果当前节点的左孩子为空
                cur_node.lchild = node  # 把要添加的节点添加到此处
                return
            else: # 当前节点的左孩子不为空
                queue.append(cur_node.lchild) # 把当前节点左孩子添加到队列中

            if cur_node.rchild is None: # 如果当前节点的右孩子为空
                cur_node.rchild = node  # 把要添加的节点添加到此处
                return
            else:
                queue.append(cur_node.rchild) # 把当前节点右孩子添加到队列中

    def breadth_travel(self):
        """广度遍历"""
        if self.root is None:
            return
        queue = [self.root]
        while queue:
            cur_node = queue.pop(0)
            print(cur_node.elem,end=' ')
            if cur_node.lchild is not None:
                queue.append(cur_node.lchild)
            if cur_node.rchild is not None:
                queue.append(cur_node.rchild)

if __name__ == "__main__":
    tree = Tree()
    tree.add(1)
    tree.add(2)
    tree.add(3)
    tree.add(4)
    tree.add(5)
    tree.breadth_travel()

 二插树的先序、中序、后续遍历

先序遍历:在先序运历中,我们先访问根节点,然后递归使用先序运历访问左子树,再递归使用先序遍历访问右子树:
根节点->左子树->右子树

    def preorder_travel(self,node):
        """先序遍历"""
        if node is None:
            return
        print(node.elem,end=' ')            # 访问根节点
        self.preorder_travel(node.lchild)   # 遍历左子树
        self.preorder_travel(node.rchild)   # 遍历右子树
        
    # 先序遍历
    # tree.preorder_travel(tree.root)

中序遍历:在中序遍历中,我们递归使用中序遍历访问左子树,然后访问根节点,最后再递归使用中序遍历访问右子树
左子树->根节点->右子树

    def inorder_travel(self,node):
        """中序遍历"""
        if node is None:
            return
        self.inorder_travel(node.lchild)
        print(node.elem,end=' ')
        self.inorder_travel(node.rchild)

后序遍历:在后序选历中,我们先递归使用后序遍历访问左子树和右子树,最后访问根节点
左子树>右子树>根节点

    def postorder_travel(self,node):
        """后序遍历"""
        if node is None:
            return
        self.postorder_travel(node.lchild) 
        self.postorder_travel(node.rchild)
        print(node.elem, end=' ')

在先序、中序、后序中知道:先序中序 或者 中序后续都能反求出二叉树的结构。

posted @ 2020-09-05 15:57  zh_小猿  阅读(284)  评论(0编辑  收藏  举报