算法图解笔记

第一章

 

二分查找算法:

 

解释及原理:二分查找是一种查找算法,需要输入列表必须是有序的。如果要查找的元素在列表中则返回所在的位置,否则返回None。假设你想一个1-100之间的数字,某人每次猜测后会知道所猜的数字是大了还是小了,如果直接从1开始猜那么至少要猜n次(n是你想的数字,这实际上是简单查找)。如果从中间也就是50开始猜,那么无论大小都已经排除掉一半的数字,接着再从剩下的一半数字的中间开始猜,以此类推知道猜到为止,这就是二分查找的原理。

例子:假设要在一个包含240000个单次的字典中查找一个单词,最多需要多少步呢?

如果要查找的单词在词典的末尾:

简单查找需要:240000步

二分查找需要:18步(对于包含n个元素的列表最多需要log2n步)

速度显而易见,二分查找图示如下:

Python实现:

 

def binary_search(list, searchvalue):

    low_index = 0

    high_index = len(list)-1

    while low_index <= high_index:

        mid_index = int((low_index + high_index) / 2)  # 取整

        guessvalue = list[int(mid_index)]

        if guessvalue == searchvalue:

            return mid_index

        if guessvalue > searchvalue:

            high_index = mid_index - 1

        if guessvalue < searchvalue:

            low_index = mid_index + 1

    return None

 

mylist = [1,2,3,4,5,6,7,8,9]

print(binary_search(mylist, 9))

 

算法的运行时间应该选择效率最高的算法以便最大限度的减少运行时间活占用空间。

线性时间:最多需要猜测的次数与列表长度相同,即列表包含100个元素需要猜测100次。

对数时间:二分查找的运行时间成为对数时间或log时间。即列表包含100个元素最多需要猜7次(log2100大约是7)。

 

 

大O表示法(时间复杂度):

大O表示法指出了算法的速度有多快。但是仅知道算法需要多长时间才能运行完还不够,还需要知道运行时间如何隋列表的长度增长而增加。(简单查找随着列表的长度增加运行时间也在增加,而二分查找却不受此影响)

运行时间为O(n),单位并不是秒。大O表示法指的并不是以秒为单位的速度,而是操作数(可以理解的步数),它指出了算法运行时间的增速。

大O表示法指出了最糟情况下的运行时间。比如在电话簿中找人,简单查找的运行时间是O(n),即所找的人在最后一个。如果所找的人在第一个,对应的运行时间是O(n)还是O(1)呢?这就解释了大O表示法在最糟糕的=情况下运行的时间,是一个保证,简单查找的时间不可能超过O(n)。

除了最糟糕情况下的运行时间还需要考虑平均情况的运行时间。

常见的大O运行时间:

O(log n) 二分查找

O(n) 简单查找

O(n*log n) 快速排序算法(一种速度较快的排序算法)

O(n2) 选择排序算法(一种较慢的排序算法)

O(n!) 旅行商问题的解决方案(一种非常慢的算法)

 

旅行商问题:

有这样一个问题:有一个旅行商需要前往5个城市,同时要确保旅程最短。因此需要对着5个城市进行排列组合(共计120种),每种方式都要计算路线并选出最短路线。如果有6个城市那个就需要执行720次操作(720中排列方式),7个城市5040次操作......推而广之,n个城市就需要n!(n的阶乘)次操作。关于这个问题目前没有较好的解决方案,只能去找近似答案。

 

总结:

算法的速度并非指时间,而是操作数的增速

谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么速度增加

算法的运行时间用大O表示

O(log n) 比O(n)块,当需要搜索的元素增加的时候前者比后者快的越多,元素越多差距越明显。

 

 

 

第二章

 

内存的工作原理:

理解:假设你去演出需要存两件东西,寄存处有一个柜子(内存),里面有很多抽屉。你将你的两件东西分别存在两个抽屉里面,并知道是哪个抽屉(即地址),这样就可以了。计算机就像是很多抽屉的集合,每个抽屉有地址。当需要将数据存在内存中时,请求计算机提供存储空间,并返回给你一个存储的地址。

 

数组和链表:

 

数组:

将数组中的元素存在内存中时位置的相连的。好比说三个人去看电影肯定会坐在三个连着的位置。那如果这时候第四个人打电话说要过来,而此时左右两边有都没有空位置,那个就需要重新找有四个位置的位置,之前的三个人都需要动(移动内存),很麻烦。一种解决办法是’预留位置’,即请求内存提供10个位置,以便添加新来的人,但是会出现两个问题:1当没有新人来得时候剩下的位置将会浪费(浪费内存),2新来的人可能多于7个,这样10个位置也是不够用的,仍然需要移动内存。还有一个问题就是加入新来的一个人想要坐在3个人的中间,那么后边的人都需要移动,如果没有足够的空间那么4个人就都需要移动(删除时也是需要内存移动的)。因此:数组在add和del方便表现不佳。

如果我们想知道某一个元素的位置怎么做呢?数组中每个元素都是有地址的,每个地址在数组中映射为数组的下标(从0开始),这样的话很容易知道每个元素的位置。因此:数组在sel时效率很高。

链表:

链表中的元素可以存在内存的任何地方,每个元素都存储了下一个元素的地址,从而将一系列随机的内存地址串在一起。在添加元素是只需要将元素放入内存同时 将地址存储在前一个元素中(可以理解成多个人同时看电影坐在不同的位置)。因此只要有足够多的内存就可以为链表分配空间。因此:链表在插入元素方面很方便。如果在链表中间插入元素只需要修改前面元素指向的地址(删除同理),也是很方便的。

链表在读取元素时不能直接读取最后一个元素,因为不知道它的地址,所以必须先访问前一个元素,而前一个元素的地址又在前前一个元素总存储,知道读取第一个时。所以链表在读取数据时总是先读取第一个元素才能知道后面的。因此:如果需要跳跃读取,链表的效率比较低。但是如果读取整个列表时效率还是挺高的。

两者的运行时间:O(n)=线性时间  O(1)常量时间

 

选择排序算法:

解释及原理:有一个无序的数组,通过此算法可以将其变有序。思路:遍历整个数组先找出最小的,添加进新数组同时在元素组中删除,然后重复这个步骤直到最后一个元素。这样做并非每次都需要检查n(数组的长度)个元素,而是在第一次检查完n个元素之后,之后的检查的元素依次为n-1,n-2,.......,1。平均每次检查1/2*n,因此运行时间是O(n*1/2*n),但是大O表示法省略诸如1/2这样的常数,因此简单写作O(n2)。选择排序速度不是很快,但却很灵巧。(本办法是每次循环整个数组找出最小的,第二小的,....,然后添加到新数组,这样的运行时间就是O(n2))

 

Python实现:

 

def findSmallest(array):

    # 找出最小的元素的下标

    smallest = array[0]

    smallestindex = 0

    for i in range(1, len(array)):

        if array[i] < smallest:

            smallest = array[i]

            smallestindex = i

    return smallestindex

 

def selectSort(array):

    newarray = []

    for a in range(len(array)):

        smallestindex = findSmallest(array)

        newarray.append(array.pop(smallestindex))

    return newarray

 

if __name__ == '__main__':

    print(selectSort([1,3,5,7,6,4,8,2,9]))

 

 

第三章

递归:

解释及原理:假设一个盒子中有你想要找的东西,而盒子中又有盒子,盒子中的盒子又有盒子,那个你是用什么办法去找你想要的东西呢?一个办法是:创业一个盒子堆并在里面找一个盒子进行查找,如果是盒子就放回盒子堆一遍后边查找,如果是你想的东西那么就大功告成(while实现)。另一个办法是:检查盒子,如果还是盒子就再检查盒子,如果是你想要的东西那么就大功告成(递归实现,自己调用自己)。

 

 

这两种方法的作用其实是一样的,只不过使用while性能可能更高,使用递归可能使程序更容易理解。

Python实现:

 

def digui(number):

    print(number)

    if number <= 1:     # 基线条件:函数不能再调用自己,避免死循环

        return

    else:     # 递归条件:函数调用自己

        digui(number -1)

 

if __name__ == '__main__':

    digui(5)

 

栈:

这部分原文比较好理解,直接贴图:

 

小编写到这就不想写了,一方面是因为这本书确实挺好,之后的章节书里面讲解的非常清楚,无序再挑挑拣拣,另一方面是因为最近工作上的活比较多,所以暂时放下,有时间再进行更新。我建议大家直接看书,有基础的同学在前几张看起来会感觉比较冗余,但是确实有助于理解消化,想看书的朋友也可联系小编要书。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章