Python学习总结【第九篇】:Python之算法(排序、搜索)

算法概述

  算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度时间复杂度来衡量。

 

1、算法特征

  • 有穷性(Finiteness):算法的有穷性是指算法必须能在执行有限个步骤之后终止;
  • 确切性(Definiteness):算法的每一步骤必须有确切的定义;
  • 输入项(Input):一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
  • 输出项(Output):一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
  • 可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步,即每个计算步都可以在有限时间内完成(也称之为有效性)。

2、评定

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。一个算法的评价主要从时间复杂度和空间复杂度来考虑。

  • 时间复杂度

    算法的时间复杂度是指执行算法所需要的计算工作量。一般来说,计算机算法是问题规模n 的函数f(n),算法的时间复杂度也因此记做。
    T(n)=Ο(f(n))
    因此,问题的规模n 越大,算法执行的时间的增长率与f(n) 的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)。

  • 空间复杂度

    算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。

  • 正确性

    算法的正确性是评价一个算法优劣的最重要的标准。

  • 可读性

    算法的可读性是指一个算法可供人们阅读的容易程度。[1]

  • 健壮性

    健壮性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。

3、方法

  • 递推法

  递推是序列计算机中的一种常用算法。它是按照一定的规律来计算序列中的每个项,通常是通过计算机前面的一些项来得出序列中的指定项的值。其思想是把一个复杂的庞大的计算过程转化为简单过程的多次重复,该算法利用了计算机速度快和不知疲倦的机器特点。

  • 递归法

  程序调用自身的编程技巧称为递归(recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
注意:
(1) 递归就是在过程或函数里调用自身;
(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

  • 穷举法

  穷举法,或称为暴力破解法,其基本思路是:对于要解决的问题,列举出它的所有可能的情况,逐个判断有哪些是符合问题所要求的条件,从而得到问题的解。它也常用于对于密码的破译,即将密码进行逐个推算直到找出真正的密码为止。例如一个已知是四位并且全部由数字组成的密码,其可能共有10000种组合,因此最多尝试10000次就能找到正确的密码。理论上利用这种方法可以破解任何一种密码,问题只在于如何缩短试误时间。因此有些人运用计算机来增加效率,有些人辅以字典来缩小密码组合的范围。

  • 贪心算法

  贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。
  用贪心法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题, 通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。
贪婪算法是一种改进了的分级处理方法,其核心是根据题意选取一种量度标准,然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量,如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。
  对于一个给定的问题,往往可能有好几种量度标准。初看起来,这些量度标准似乎都是可取的,但实际上,用其中的大多数量度标准作贪婪处理所得到该量度意义下的最优解并不是问题的最优解,而是次优解。因此,选择能产生问题最优解的最优量度标准是使用贪婪算法的核心。
  一般情况下,要选出最优量度标准并不是一件容易的事,但对某问题能选择出最优量度标准后,用贪婪算法求解则特别有效。

  • 分治法

  分治法是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法所能解决的问题一般具有以下几个特征:
(1) 该问题的规模缩小到一定的程度就可以容易地解决;
(2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
(3) 利用该问题分解出的子问题的解可以合并为该问题的解;
(4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

  • 动态规划法

  动态规划是一种在数学和计算机科学中使用的,用于求解包含重叠子问题的最优化问题的方法。其基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。动态规划的思想是多种算法的基础,被广泛应用于计算机科学和工程领域。
  动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。不象前面所述的那些搜索或数值计算那样,具有一个标准的数学表达式和明确清晰的解题方法。动态规划程序设计往往是针对一种最优化问题,由于各种问题的性质不同,确定最优解的条件也互不相同,因而动态规划的设计方法对不同的问题,有各具特色的解题方法,而不存在一种万能的动态规划算法,可以解决各类最优化问题。因此读者在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。

  • 迭代法

  迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。

  • 分支界限法

  分枝界限法是一个用途十分广泛的算法,运用这种算法的技巧性很强,不同类型的问题解法也各不相同。
  分支定界法的基本思想是对有约束条件的最优化问题的所有可行解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为定界)。在每次分支后,对凡是界限超出已知可行解值那些子集不再做进一步分支,这样,解的许多子集(即搜索树上的许多结点)就可以不予考虑了,从而缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。因此这种算法一般可以求得最优解。
  与贪心算法一样,这种方法也是用来为组合优化问题设计求解算法的,所不同的是它在问题的整个可能解空间搜索,所设计出来的算法虽其时间复杂度比贪婪算法高,但它的优点是与穷举法类似,都能保证求出问题的最佳解,而且这种方法不是盲目的穷举搜索,而是在搜索过程中通过限界,可以中途停止对某些不可能得到最优解的子空间进一步搜索(类似于人工智能中的剪枝),故它比穷举法效率更高。

  • 回溯法

回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
其基本思想是,在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

列表查找

含义:从列表中查找指定元素

输入:列表、待查找的元素

输出:元素下标或者未查找到元素

查找算法:

  • 顺序查找:从列表第一个元素开始,顺序进行搜索,直到找到为止--------时间复杂度为O(n)
import random
import time


def cal_time(func):
    """
    计时器函数
    :param func:
    :return:
    """
    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 linear_search(data_set, value):
    for i in range(len(data_set)):
        if data_set[i] == value:
            return i
    return None


data_list = list(range(10))         # 定义列表
random.shuffle(data_list)           # 将列表顺序打乱
print(data_list)
i = linear_search(data_list, 3)     # 查找指定元素的下标值
print(i)

结果:
[7, 1, 3, 6, 2, 8, 0, 4, 5, 9]
linear_search running time: 0.0 secs.
2
View Code
  • 二分查找:从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。------时间复杂度O(logn)
import random
import time


def cal_time(func):
    """
    计时器函数
    :param func:
    :return:
    """

    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 bin_search(data_set, value):
    low = 0
    high = len(data_set) - 1
    while low <= high:
        mid = (low + high) // 2
        if data_set[mid] == value:
            return mid
        elif data_set[mid] > value:
            high = mid - 1
        else:
            low = mid + 1


data_list = list(range(10))  # 定义列表
# random.shuffle(data_list)  # 将列表顺序打乱
print(data_list)
i = bin_search(data_list, 3)  # 查找指定元素的下标值
print(i)

结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]     # 注意该序列为有序序列
bin_search running time: 0.0 secs.
3
View Code

练习:

现有一个学员信息列表(按id增序排列),格式为:

[
{id:1001, name:"张三", age:20},
{id:1002, name:"李四", age:25},
{id:1004, name:"王五", age:23},
{id:1007, name:"赵六", age:33}
]

修改二分查找代码,输入学生id,输出该学生在列表中的下标,并输出完整学生信息。

import random
import time


def cal_time(func):
    """
    计时器函数
    :param func:
    :return:
    """

    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 bin_search(data_set, value):
    low = 0
    high = len(data_set) - 1
    while low <= high:
        mid = (low + high) // 2
        if data_set[mid]["id"] == value:
            return mid
        elif data_set[mid]["id"] > value:
            high = mid - 1
        else:
            low = mid + 1


data_list = [
    {"id": 1001, "name": "张三", "age": 20},
    {"id": 1002, "name": "李四", "age": 25},
    {"id": 1004, "name": "王五", "age": 23},
    {"id": 1007, "name": "赵六", "age": 33}
]


print(data_list)
i = bin_search(data_list, 1007)  # 查找指定元素的下标值
print("ID:%s\nINFO:%s" % (i,data_list[i]))

结果:
[{'name': '张三', 'age': 20, 'id': 1001}, {'name': '李四', 'age': 25, 'id': 1002}, {'name': '王五', 'age': 23, 'id': 1004}, {'name': '赵六', 'age': 33, 'id': 1007}]
bin_search running time: 0.0 secs.
ID:3
INFO:{'name': '赵六', 'age': 33, 'id': 1007}
View Code

 

列表排序

含义:将无序列表变为有序列表

应用场景:

  • 各种榜单
  • 各种表格
  • 给二分排序用
  • 给其他算法用

输入:无序列表

输出:有序列表

常见算法说明:

排序简单级别三人组:

  • 冒泡排序
  • 选择排序
  • 插入排序

快速排序
排序高级二人组:

  • 堆排序
  • 归并排序

不常用的排序:

  • 基数排序
  • 希尔排序
  • 桶排序

资料:

http://blog.csdn.net/hinyunsin/article/details/6311707

http://python.jobbole.com/82270/

1.1 冒泡排序
效率最低

排序原理:

冒泡排序思路:列表每两个相邻的数,前面的比后面的大,则进行交换,时间复杂度

实现案例:

import random
import time


def cal_time(func):
    """
    计时器函数
    :param func:
    :return:
    """

    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 bubble_sort(li):
    """
    冒泡排序
    :param li:
    :return:
    """
    for i in range(len(li) - 1):                        # 第一个值,如果第一个值大于第二个值 位置互换
        for j in range(len(li) - i - 1):                # 第二个值
            if li[j] > li[j + 1]:                       # 如果第一个值大于第二个值
                li[j], li[j + 1] = li[j + 1], li[j]     # 第一个值与第二个值进行交换Python交换变量值写法


data_list = list(range(10))
random.shuffle(data_list)
print("冒泡排序前:", data_list)
bubble_sort(data_list)
print("冒泡排序后:", data_list)

结果:
冒泡排序前: [5, 4, 1, 6, 8, 7, 0, 3, 2, 9]
bubble_sort running time: 0.0 secs.
冒泡排序后: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
View Code

 冒泡排序--优化

import random
import time


def cal_time(func):
    """
    计时器函数
    :param func:
    :return:
    """

    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 bubble_sort_1(li):
    """
    冒泡排序优化
    :param li:
    :return:
    """
    for i in range(len(li) - 1):
        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 = True
        if not exchange:
            break


data_list = list(range(10))
random.shuffle(data_list)
print("冒泡排序前:", data_list)
bubble_sort_1(data_list)
print("冒泡排序后:", data_list)

结果:
冒泡排序前: [8, 4, 1, 0, 9, 6, 3, 2, 7, 5]
bubble_sort_1 running time: 0.0 secs.
冒泡排序后: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
View Code

 

1.2 选择排序

原理:

一趟遍历记录最小的数,放到第一个位置;
再一趟遍历记录剩余列表中最小的数,继续放置;
……

问题是:怎么选出最小的数?

#!/usr/bin/env python
# encoding: utf-8

def xz_px(arg):
    """
    选择排序算法
    :param arg:
    :return:
    """
    px_num = arg[0]          # 从给定的一个序列中假设一个最大值
    """
    循环所有元素值,如果元素值大于假设的最大值,则将这个值赋值给假设的最大值,一次循环完成,追加
    最大值到新序列,经该最大值从原序列中删除
    """
    for j in range(len(arg)):
        if arg[j] < px_num:
            px_num = arg[j]
    px_li.append(px_num)
    del arg[arg.index(px_num)]

    for i in range(len(arg)):       # 判断原序列是否为空,为空结束返回新序列和原序列,否则继续进行排序
        if len(arg) == 0:
            continue
        else:
            xz_px(arg)
    return px_li, arg


li = [1, 22, 9, 33, 100, 10, 4, ]
px_li = []

print('选择排序前:', li)
px_li, li = xz_px(li)    # 存储排序完成的数据
print('选择排序后:', px_li)

# 结果输出:
# 选择排序前: [1, 22, 9, 33, 100, 10, 4]
# 选择排序后: [1, 4, 9, 10, 22, 33, 100]

1.3 插入排序(待完成)
原理:永远维护一个有序的序列

斐波那契数列

 

阶乘

http://www.cnblogs.com/work115/p/5603910.html

九九乘法表

#!/usr/bin/python

for i in range(1, 10):
    line = '|'
    for j in range(1, i+1):
        line = line + ' ' + str(j) + 'x' + str(i) + '=' + str(i*j) + ' '*(3-len(str(i*j))) + '|'
    print('-'*(9*j+1))
    print(line)
print('-'*82)

效果:
---------- | 1x1=1 | ------------------- | 1x2=2 | 2x2=4 | ---------------------------- | 1x3=3 | 2x3=6 | 3x3=9 | ------------------------------------- | 1x4=4 | 2x4=8 | 3x4=12 | 4x4=16 | ---------------------------------------------- | 1x5=5 | 2x5=10 | 3x5=15 | 4x5=20 | 5x5=25 | ------------------------------------------------------- | 1x6=6 | 2x6=12 | 3x6=18 | 4x6=24 | 5x6=30 | 6x6=36 | ---------------------------------------------------------------- | 1x7=7 | 2x7=14 | 3x7=21 | 4x7=28 | 5x7=35 | 6x7=42 | 7x7=49 | ------------------------------------------------------------------------- | 1x8=8 | 2x8=16 | 3x8=24 | 4x8=32 | 5x8=40 | 6x8=48 | 7x8=56 | 8x8=64 | ---------------------------------------------------------------------------------- | 1x9=9 | 2x9=18 | 3x9=27 | 4x9=36 | 5x9=45 | 6x9=54 | 7x9=63 | 8x9=72 | 9x9=81 | ---------------------------------------------------------------------------------------------

 杨辉三角

# -*- coding = utf-8 -*-

def triangles():
    L = [1]
    while True:
        yield L
        L.append(0)
        L = [L[i - 1] + L[i] for i in range(len(L))]

n = 0
for t in triangles():
    print(t)
    n += 1
    if n == 10:
        break

# 结果
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

 

posted @ 2016-06-11 20:47  每天进步一点点!!!  阅读(868)  评论(0编辑  收藏  举报