Python基本的数据结构与算法

本文介绍了Python中的二分查找算法及其效率,讨论了大O表示法,对比了数组和链表在访问、插入和删除操作中的性能差异,并概述了选择排序、递归、快速排序、冒泡排序和插入排序等基本算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.二分法
二分法查找是一种算法,其输入是一个有序的元素列表,如果要查找的元素包含在列表当中,二分查找返回其位置;否则返回null。查找元素时每次都会从中间位置开始判断查找,每查找一次都会排除一半元素。
特点
(1)从n个有序元素中查找目标元素,使用简单查找方法,最多需要查找n步,查找n个元素,而使用二分查找算法最多查找log(n)步,查找log(n)个元素;
(2)二分查找的前提列表元素是有序的,按照一定的排序规则,如数值大小,字母等进行排序;

def binary_search(list,item):
    //low和high用于跟踪要在其中查找的列表部分
    low = 0
    high = len(list)-1
    while low <= high:
        //只要范围没有缩小到一个元素,就检查中间的元素
        mid = (low + high)//2#如果(low+high)不是偶数,Python自动将mid向下取整
        guess = list[mid]
        //如果查找到了元素,返回该元素的索引位置
        if guess == item:
            return mid
        //猜的数字大了
        if guess > item:
            high = mid - 1
        else:#如果猜的数字小了
            low = mid + 1
    return None #没有指定的元素
my_list = [1,3,5,7,9,11]//初始化一个my_list测一下自己的二分查找
print(binary_search(my_list,3))// => 1 不要忘记索引从0开始,第二个位置的索引为1
print(binary_search(my_list,-1))//在Python中,None表示空,它意味着没有找到指定的元素
my_list = ["科科","帅哥","风一样的男人"]//列表中既可以是数值型也可以是字符串
print(binary_search(my_list,"帅哥"))// => 1 不要忘记索引从0开始,第二个位置的索引为1
print(binary_search(my_list,"有钱人"))//在Python中,None表示空,它意味着没有找到指定的元素          

运行时间
运行时间 :简单查找算法运行的时间是线性时间,二分查找运行时间是对数时间。一般而言,应该选择效率最高的算法,以最大限度地减少运行时间或占用时间。那么问题来了,算法的效率该怎么说明呢?
大O表示法
大O表示法指出算法的速度有多快,算法运行时间的增速。如O(n)中的n指的是“操作数”,也就是算法执行时操作了几次。有鉴于此,我们不仅要知道算法需要多长时间才能运行完毕,还需要知道运行时间如何随列表增长而增加。
5种运行时间
常见的用大O表示法表示的5种运行时间:
(1)O(log n),也叫对数时间,这样的算法包括二分查找;
(2)O(n),也叫线性时间,这样的算法包括简单查找;
(3)O(n*log n ),这样的算法包括快速排序法,一种较快的排序算法,还有合并排序;
(4) O(n2),n的平方,平方时间,这样的算法包括选择排序和插入排序,一种速度较慢的排序算法;
(5)O(n!),阶乘时间,这样的算法包括旅行商问题的解决方案,一种非常慢的算法。

学习完简单查找算法、二分查找算法和大O表示法的启示:
(1)算法的速度指的并非时间,而是操作数的增速
(2)谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
(3)算法的运行时间用大O表示法表示
(4)O(log n)比O(n)快,当需要搜索的元素越多时,前者比后者快得越多;
2.数组和链表
在讲述数组和链表之前,我们先了解一个术语——索引。
索引指元素的位置,因此我们不说“元素20的位置为1”,而说“元素20位于索引1处”。
数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组、二维数组、多维数组。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表和数组作为存储数据的数据结构,也有其优缺点,具体表现在“访问”、“插入”和“删除”操作中。
(1) 访问,数组既支持顺序访问,又支持随机访问,而链表仅支持顺序访问,所以数组的访问时间O(1),而链表的访问运行时间O(n);
(2)插入,在链表中插入元素时,只需要将它前面元素指向的地址进行修改即可,而在数组中插入元素的时候,必须将后面的元素都往后移,如果没有足够的空间,可能还需要将这个数组复制到其他地方。这样就导致数组和链表在“插入”操作中的运行时间不一样,数组的运行时间是O(n),而链表的运行时间是O(1);
(3)删除,使用数组时,删除元素之后,必须将后面的元素都向前移动,而使用链表时,只需修改前一个元素指向的地址即可。所以,使用数组时,删除操作的运行时间是O(n),而使用链表时,删除操作的运行时间是O(1)。
数组与链表区别
(1)数组中元素存储的内存是连续的,链表中的元素可存储在内存的任何地方,换句话说,在数组中,元素都是紧紧地靠在一起,而在链表中,元素并非靠在一起;
(2)数组的优势在于访问/读取元素方面,而链表的优势在于插入元素方面;
3.选择排序
问题:将数组元素按从小到大的升序排序(当然你也可以降序排列)。思路:首先,先编写一个用于找出数组元素中最小元素的函数。然后把这个找的元素从原数组中移除,放入新建的数组(列表)中,即可得到一个升序排列的数组(列表)。其中,移除的方法使用list.pop(index)函数。¶

def findSmallest(arr):
    smallest = arr[0]//声明smallest,存储最小的值
    smallest_index = 0//声明smallest_index,存储最小元素的索引
    for i in range(1,len(arr)):
        if arr[i] < smallest:
            smallest = arr[i]
            smallest = arr[i]
            smallest_index = i
    return smallest_index//返回数组中最小值的索引,方便后面pop函数将其删除,然后把删除的元素保存到新建的列表中
print(findSmallest([3,8,1,3,9]))//返回索引值2
//在编写完寻找最小元素的函数之后,可以使用这个函数编写排序算法
def selectionSort(arr):
    newArr = []//arr是待排序的数组,新建一个列表,用于存储排序结果
    for i in range(len(arr)):
        smallest = findSmallest(arr)//找出数组中最小的元素,将其加入到新数组中
        newArr.append(arr.pop(smallest))//pop(index)表示把数组/列表中第index个位置的元素删除,pop()表示默认删除最后一个元素,pop(-1)表示删除删除最后一个列表值
    return newArr
print(selectionSort([3,8,1,3,9]))

4.递归
(1)递归就是函数调用自己,使得程序更容易理解,但并没有性能上的优势;
(2)由于递归函数调用自己,因此编写递归函数时容易导致无限循环。为了解决这个问题,我们编写递归函数时包含2部分:递归条件和基线条件。递归条件指的是函数调用自己,而基线条件则指的是函数不调用自己,从而避免无限循环。例如下面一个列子,使用递归函数1-10这些数字进行降序排列。

def countdown(i):
    print(i)
    //基线条件
    if i <= 1:
        return 
    else:#递归条件
        countdown(i-1)
print(countdown(10))

5.快速排序
快速排序的主要思想:
(1)选择基准值;
(2)将数组分成两个子数组:小于基准值的元素和大于基准值的元素
(3)对这两个子数组进行快速排序

def quicksort(arr):
    if len(arr) < 2:
    //基准条件:为空或只包含一个元素的数组是“有序”的
        return arr 
    else:
        pivot = arr[0]
        less = [i for i in arr[1:] if i <= pivot]//由所有小于等于基准值的元素组成的子数组
        greater = [i for i in arr[1:] if i > pivot]//由所有大于基准值的元素组成的子数组
        return quicksort(less)+[pivot]+quicksort(greater)//对这两个子数组进行快速排序
print(quicksort([10,5,2,3]))

6.冒泡排序
主要思想:相邻元素两两比较,大的元素往后放置,这样就得到最大值在最大索引处。同理,继续上述操作,即可得到一个排好序的数组。在排序的过程中,每一次比较完毕后,下一次比较的时候就会减少一个元素的比较,换句话说,第一次比较,有0个元素不比,第二次比较,有1个元素不比,第三次比较,有2个元素不比…,总共需要比较数组长度-1次。

//冒泡排序法
def bubble_sort(arr):
    count = 0//count表示交换位置的次数
    //外层for循环,控制列表中余下比较元素的个数
    for i in range(len(arr)-1):
    //内层for循环,控制当前循环比较的元素
        for j in range(len(arr)-1-i):
            if arr[j] > arr[j + 1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]//两元素交换位置,这种写法是Python语言的优雅所在
                count += 1//每交换一次就加1
        if count == 0:
            break
    print(count)

if __name__ == "__main__":
    arr = [21,69,90,55,12]
    bubble_sort(arr)
    print(arr)          

7.插入排序
插入排序的工作方式将数组(Python中相当于list,列表)中的元素分为两部分:一是前部分,已经排好序的;二是后部分,是待排序的。每次取待排序的一个元素,插入到前面已经排好序的部分,要求不破坏前半部分的有序性。那么问题来了,怎么做才能不破坏呢?
方法是这样的,将待插入元素与其前一个元素进行比较,若小于,则将前一个元素后移一位,在将带插入元素与前前一位进行比较,若小于,则将前前一位往后移,如此操作直到遇到小于待插入元素的位置,插入到该位后一位(此位已经在上一步后移了)。

//插入排序的复杂度是O(n^2),在数据量较小时效率较高,当数据量打的时候还是改用其他排序方法
def insertsort(list):
    N=len(l)
    for x in range(1,N):
        key=list[x]
        a=x
        while key<list[a-1]:
            l[a]=l[a-1]
            a=a-1
            if a-1<0:
                break
        list[a]=key

l=[1,3,2,8,5,3,1]
insertsort(l)
print (l) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值