数据结构与算法(python)(算法入门)

数据结构与算法(python)(算法部分)

一、算法入门

1.时间、空间复杂度

时间复杂度:T(n)=O(f(n)) 估计算法运行时间的式子(单位)用最大数量级
f(n)基础操作重复执行的次数 O(1) O(n) O(log n) {O(1)、O(n2+n)}表述不正确
快速判断:
确定问题规模n
循环减半: logn
k层关于n的循环 nk

按效率排序 O(1)<O(log n)<O(n)<O(nlog n)<O(n2)<O(n2log n) <O(N3)
复杂时间复杂度 O(n!) O(2n) O(nn)

空间复杂度:S(n)=O(f(n)) 评估算法内存占用大小
使用了几个变量: O(1)
使用了长度为n的一位列表:O(n)
使用m行n列的二维列表:O(mn)
原则:"空间换时间 "

2.递归

复习:递归
特点:调用自身、结束条件。

def fun3(x):
    if x>0:
        print(x)
        fun3(x-1)
# 输入x=3   输出 :3 2 1
# 
def fun3(x):
    if x>0:
        fun3(x-1)
        print(x)
#输入 x=3  输出 1 2 3  先执行调用函数的操作,函数无法执行时执行打印 从内向外打印
fun3(3)

递归实例:汉诺塔问题:三根柱子ABC,在一根上由上到下按从小到大放64个圆盘,按大小顺序重新摆放到另一个柱子上。

# 汉诺塔问题:
# n=2时:
# 1.小圆盘A到B
# 2.大圆盘A到C
# 3.小圆盘B到C
# n个盘子  下面一个看成一个整体,上面n-1个看成一个整体
# 1.n-1个圆盘A经过C到B(比原规模小1的原问题)
# 2.第n个圆盘A到C(一步)
# 3.n-1个圆盘B经过A到C(比原规模小1的原问题)
#递推式:h(x)=2h(x-1)+1   大约2^n
#h(64)=18446744073709551615   每秒移动一个,要5800亿年
def hanoi(n,a,b,c):
    if n>0:
        hanoi(n-1,a,c,b)
        print("moving from %s to %s"%(a,c))
        hanoi(n-1,b,a,c)
hanoi(2,'A','B','C')

二、查找问题

查找:在一些数据元素中,通过一定的方法找出与给定关键词相同的数据元素的过程。
列表查找(线性表查找):从列表中查找指定元素
输入:列表、待查找元素
输出:元素下标
内置列表查找函数:index() 就是线性查找

1.顺序查找

顺序查找:也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。
时间复杂度o(n)

#顺序查找
def linear_search(li,val):
    for ind,v in enumerate(li):
        if v==val:
            return ind
        else:
            return None

2.二分查找

二分查找:又叫折半查找,从有序列表的初始候选区 li[0:n]开始,通过对待查找的与候选区中间值的比较,可以使候选区减少一半。
时间复杂度o(log n) 但是需要先排序

#二分查找:  1-9 中查找元素3
def binary_search(li,val):
    left=0
    right=len(li)-1
    while left<=right:
        mid=(left+right)//2
        if li[mid]==val:
            return mid
        elif li[mid]>val:#待查找的值在mid左侧
            right=mid-1
        else:#li[mid]<val 待查找的值在mid右侧
            left=mid+1
    else:
        return None
li=[1,2,3,4,5,6,7,8,9]
print(binary_search(li,3))

三、列表排序

排序:将一组 无序 的记录序列调整为有序的记录序列
列表排序:将无序列表变为有序列表
输入:列表
输出:有序列表
升序与降序
内置排序函数:sort() 基于归并排序
常见算法排序:
冒泡排序、选择排序、插入排序
快速排序、堆排序、归并排序
希尔排序、计数排序、基数排序

1.冒泡排序

列表每两个相邻的数,如果前面的比后面的大,则交换这两个数
一趟排序完成后,无序区减少一个数,有序区增加一个数
代码关键点:趟、无序区
时间复杂度O( n2)

#冒泡排序
import random
def bubble_sort(li):
    for i in range(len(li)-1):#第i趟
        exchange=False
        for j in range(len(li)-i-1):
            if li[j]>li[j+1]:
                li[j],li[j+1]=li[j+1],li[j]
                exchange=Ture
        if not exchange:#如果没有发生交换则表示已经排好序
            return
li=[random.randint(0,100) for i in range(10)]
print(li)
bubble_sort(li)
print(li)

2.选择排序

一趟排序记录最小的数,放到第一个位置
在一趟记录列表无序区最小的数,放到第二个位置
关键点:有序区和无序区、无序区最小数的位置
新建一个列表,多一倍内存
时间复杂度O(n2) min和remove操作都需要一次遍历

#选择排序
def select_sort_simple(li):
    li_new=[]
    for i in range(len(li)):
        min_val=min(li)
        li_new.append(min_val)
        li.remove(min_val)
    return li_new
li=[9,6,3,5,2,1,4,8,7]
print(select_sort_simple(li))

时间复杂度O(n2)

#改进选择排序
def select_sort(li):
    for i in range(len(li)-1):
        min_loc = i
        for j in range(i,len(li)):
            if li[j]<li[min_loc]:
                min_loc = j
        li[i], li[min_loc]=li[min_loc], li[i]     
li=[9,6,3,5,2,1,4,8,7]
select_sort(li)
print(li)

3.插入排序

初始有序区只有一个元素,每次从无序区取一个元素插入到有序区的正确位置
时间复杂度O(n2)

#插入排序
def insert_sort(li):
    for i in range(1,len(li)):#i表示摸到的牌下标
        tmp = li[i]
        j=i-1 #j指的是手里牌的下标
        while j>=0 and li[j]>tmp:
            li[j+1]=li[j]
            j-=1
        li[j+1]=tmp
    return li
li=[9,6,3,5,2,1,4,8,7]
print(insert_sort(li))

4.快速排序

思路:取一个元素p,使元素p归位
列表被分成两个部分,左边都比p小,右边都比p大
递归完成排序
时间复杂度O(nlogn)
问题 :递归
最坏情况:倒序 需要 n2

#快速排序
def partition(li, left, right):
    tmp = li[left]
    while left < right:
        while left<right and li[right] >= tmp:#从右面找比tmp小的数
            right -= 1 #往左走一位
        li[left]=li[right]   #把右边的值写到左边的空位
        # print(li)
        while left<right and li[left]<=tmp:
            left+=1
        li[right]=li[left]   #把左边的值写到右边的空位
        # print(li)
    li[left]=tmp   #把tmp归位
    return left
#快排递归框架
def quick_sort(li, left, right):
    if left<right:  #至少两个元素
        mid= partition(li, left,right)
        quick_sort(li,left,mid-1)
        quick_sort(li,mid+1,right)
li=[5, 3, 2, 6, 9, 7, 8, 1, 4]
print(li)
quick_sort (li, 0, len(li)-1)
print(li)

5.堆排序(补充树部分的内容)

堆排序前传:树与二叉树
树是一种数据结构,可以递归定义的数据结构
是由n个节点组成的集合:
如果n=0,则是空树
n>0,存在一个节点为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树
基本概念:根节点、叶子节点 ;树的深度(高度);树的度(有几个分叉就几个度,树的度是最多的度);
孩子节点/父节点;子树

二叉树:
完全二叉树:叶节点只能出现在最下面一层和次下层,并且最下面一层的节点都集中在该层的若干位置的二叉树。
深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
满二叉树:一个二叉树每一层的节点数都达到最大值

二叉树的存储方式:链式存储方式(后面讲数据结构时候补充) 顺序存储方式
顺序存储:父节点和左孩子 :i 到2i+1 父节点和右孩子:i到2i+2 孩子找父节点 (i-1)/2

堆:一种特殊的完全二叉树结构
大根堆:一个完全二叉树,满足任一节点都比其他孩子节点大
小根堆:一个完全二叉树,满足任一节点都比其他孩子节点小
堆的向下调整:
假设节点的左右子树都是堆,但自身不是堆,可以通过一次向下调整来将其变换成一个堆。

堆排序过程:
1.建立堆
2.得到堆顶元素为最大元素
3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
4.堆顶元素为第二大元素
5.重复步骤3,直到堆变空

构造堆:
先看最后一个非叶子节点

#堆向下调整实现
def sift(li,low, high): #li:列表  low:堆的根节点位置 high:堆的最后一个元素位置
    i = low  #堆顶
    j = 2*i+1  #j是左孩子
    tmp = li[low] #把堆顶存起来
    while j <= high:   #只要j位置有数
        if j+1 <= high and li[j+1]>li[j]:   #如果右孩子右且比较大
            j = j+1  #j指向右孩子
        if li[j] > tmp:
            li[i]=li[j]
            i = j
            j = 2*i+1
        else: #tmp更大,把tmp放i的位置上
            li[i] = tmp  #把tmp放在某一级领导位置上
            break
    else:
        li[i] = tmp     #把tmp放到叶子节点上

堆排序:
时间复杂度O(nlogn)
时间复杂度与快排一样,实际应用快排更快一些

def heap_sort(li):
    n = len(li)
    for i in range((n-2)//2, -1, -1):   #i表示建堆的时候调整的部分的根的下标
        sift(li, i, n-1)
    #建堆完成
    #挨个出数
    for i in range(n-1, -1, -1):#i指向当前堆的最后一个元素
        li[0],li[i] = li[i], li[0]
        sift(li,0, i-1)  #i-1是新的high
li=[i for i in range(100)]
import random
random.shuffle(li)
print(li)
heap_sort(li)
print(li)

python堆的内置模块:
import heapq

import heapq   #q代表优先队列
import random
li = list(range(100))
random.shuffle(li)
print(li)
heapq.heapify(li)  #建堆
heapq.heappop(li)  #弹出最小元素
print(li)

堆排序:topk问题(现有n个数,设计算法得到前k大的数(k<n))
解决思路: 排序后切片O(nlogn) 冒泡排序、插入排序、选择排序 O(kn)
堆排序O(nlogk)

def sift(li,low, high): #li:列表  low:堆的根节点位置 high:堆的最后一个元素位置
    i = low  #堆顶
    j = 2*i+1  #j是左孩子
    tmp = li[low] #把堆顶存起来
    while j <= high:   #只要j位置有数
        if j+1 <= high and li[j+1]<li[j]:   #如果右孩子右且比较大
            j = j+1  #j指向右孩子
        if li[j] < tmp:
            li[i]=li[j]
            i = j
            j = 2*i+1
        else: #tmp更大,把tmp放i的位置上
            li[i] = tmp  #把tmp放在某一级领导位置上
            break
    else:
        li[i] = tmp     #把tmp放到叶子节点上
def topk(li,k):
    heap = li[0:k]
    for i in range((k-2)//2,-1,-1):
        sift(heap, i, k-1)
    #1.建堆
    for i in range(k,len(li)):
        if li[i]>heap[0]:
            heap[0]=li[i]
            sift(heap,0, k-1)
    #2.遍历
    for i in range(k-1, -1, -1):
        heap[0],heap[i] = heap[i], heap[0]
        sift(heap,0, i-1)
    #3.出数
    return heap
import random
li = list(range(100))
random.shuffle(li)
print(topk(li,10))

6.归并排序

假设列表的两段有序,将其合成一个有序列表,称为一次归并。
分解:将列表越分越小,直至分成一个元素。
终止条件:一个元素是有序的。
合并:将两个有序列表合并,列表越来越大。
时间复杂度O(nlogn)
空间复杂度O(n)

def merge_sort(li,low,high):
    if low < high:#至少有两个元素,递归
        mid = (loe+high)//2
        merge_sort(li,low,high)
        merge_sort(li,mid+1,high)
        merge(li,low,mid,high)

def merge(li,low,mid,high):
    i = low
    j = mid+1
    ltmp=[]
    while i<=mid and j<=high: #只要两边都有数
        if li[i]<li[j]:
            ltmp.append(li[i])
            i+=1
        else:
            ltmp.append(li[j])
            j+=1
        print(li)
        #while执行完,肯定有一部分没数了
        while i<=mid:
            ltmp.append(li[i])
            i+=1
        while j<=high:
            ltmp.append(li[j])
            j+=1
        li[low:high+1]=ltmp

上述三种排序小结
时间复杂度都是O(nlogn)
运行时间:快速排序<归并排序<堆排序
三种算法缺点:
快速排序:极端情况下效率低
归并排序:需要额外消耗内存
堆排序:在快的排序算法中相对较慢
稳定度:一样的元素位置不变的稳定
在这里插入图片描述

7.希尔排序

希尔排序:一种分组插入排序算法;每趟并不是使某些元素有序,而是使整体越来越接近有序;最后一趟使得所有数据有序。
首先取一个整数 d1=n/2,将元素分为d1个组,每组相邻两元素之间的距离为d1,在各组内进行直接插入排序;
取第二个整数d2=的/2,重复上述分组排序过程,知道di=1,即所有元素在同一组进行直接插入排序。

#希尔排序
def insert_sort_gap(li,gap):
    for i in range(gap,len(li)):#i表示摸到的牌下标
        tmp = li[i]
        j=i-gap #j指的是手里牌的下标
        while j>=0 and li[j]>tmp:
            li[j+gap]=li[j]
            j-=gap
        li[j+gap]=tmp

def shell_sort(li):
    d=len(li)//2
    while d>=1:
        insert_sort_gap(li,d)
        d//=2
    return li
li=[9,6,3,5,2,1,4,8,7]
print(shell_sort(li))

希尔排序时间复杂度要论比较复杂,和选取的gap序列有关。
当使用 Hibbard 增量(hk=2k-1)序列时,时间复杂度为 O(n3/2)
当使用 Sedgewick 增量(hk=4k+3*2k-1+1和hk=4k-1-92k-1+1交替使用;1,5,19,41,109,209)序列时,时间复杂度为 O(n4/3)
在最佳的增量序列下,最坏情况下的时间复杂度可以达到O(nlog2n)
希尔排序在序列基本有序的情况下可以接近O(nlogn)

8.计数排序

对列表进行排序,已知列表中的数范围都在0到100之间,设计时间复杂度为O(n)的算法

#计数排序
def count_sort(li,max_count=100):
    count=[0 for _ in range(max_count+1)]
    for val in li:
        count[val]+=1
    li.clear()
    for ind, val in enumerate(count):
        for i in range(val):
            li.append(ind)
import random
li=[random.randint(0,100) for _ in range(1000)]
print(li)
count_sort(li)
print(li)

9.桶排序

在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?
桶排序:首先将元素分在不同的同种,在对每个桶中的元素排序。
桶排序的表现取决于数据的分布,也就是需要对不同数据排序时采取不同的分桶策略
平均情况时间复杂度O(n+k) n是输入数据的大小 k是桶的数量
最坏情况时间复杂度O(n2k) 所有数据都在一个桶中
空间复杂度O(n+k)

#桶排序
def bucket_sort(li, n=100, max_num=1000):
    buckets=[[] for _ in range(n)]   #创建桶
    for var in li:
        i = min(var//(max_num//n) , n-1)   #i 表示var放到几号桶
        buckets[i].append(var)     #把var加到桶里边
        #保持桶内的顺序
        for j in range(len(buckets[i])-1,0, -1):
            if buckets[i][j]<buckets[i][j-1]:
                buckets[i][j], buckets[i][j-1]=buckets[i][j-1], buckets[i][j]
            else:
                break
    sorted_li = []
    for buc in buckets:
        sorted_li.extend(buc)    #extend把一个列表加在后边
    return sorted_li
import random
li=[random.randint(0,1000) for i in range(100)]
print(li)
print(bucket_sort(li))

10.基数排序

多关键字排序:假如现在有一个员工表,先按照年龄排序,再按照薪资进行稳定的排序
时间复杂度O(nk) n输入数据的大小, k为位数也就是桶个数
空间复杂度O(n+k)

def radix_sort(li):
    max_num= max(li)  #取最大位数
    it= 0
    while 10**it<=max_num:   # ** 次方
        buckets = [[] for _ in range(10)]
        for var in li:
            #取一位数     987%10 it=1  987//10%10  it=2  987//100%10  it=3
            diqit=(var//10**it)%10
            buckets[diqit].append(var)
        #分桶完成
        li.clear()
        for buc in buckets:
            li.extend(buc)
        #把数重新写回i
        it+=1
import random
li=[random.randint(0,1000) for i in range(100)]
print(li)
radix_sort(li)
print(li)

总结

算法部分的内容总结,主要包括判断算法时间、空间复杂度,简单的递归,查找算法和排序算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值