Python之路【第二十四篇】Python算法排序一

什么是算法

一、什么是算法
python

算法(algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值做为输出。简单来讲算法就是一系列的计算步骤,用来将输入数据转化成输出结果。算法

mark:咱们能够把全部的算法想象为一本“菜谱”,特定的算法好比菜谱中的的一道“老醋花生米”的制做流程,只要按照菜谱的要求制做老醋花生米,那么谁均可以作出一道好吃的老醋花生米。so,这个作菜的步骤就能够理解为:“解决问题的步骤”
数组

二、算法的意义app

假设计算机无限快,而且计算机存储容器是免费的,咱们还须要各类乱七八糟的算法吗?若是计算机无限快,那么对于某一个问题来讲,任何一个均可以解决他的正确方法均可以的!dom

固然,计算机能够作到很快,可是不能作到无限快,存储也能够很便宜可是不能作到免费。函数

那么问题就来了效率:解决同一个问题的各类不一样算法的效率经常相差很是大,这种效率上的差距的影响每每比硬件和软件方面的差距还要大。性能

三、如何选择算法测试

第一首先要保证算法的正确性优化

一个算法对其每个输入的实例,都能输出正确的结果并中止,则称它是正确的,咱们说一个正确的算法解决了给定的计算问题。不正确的算法对于某些输入来讲,可能根本不会中止,或者中止时给出的不是预期的结果。然而,与人们对不正确算法的见解想反,若是这些算法的错误率能够获得控制的话,它们有时候也是有用的。可是通常而言,咱们仍是仅关注正确的算法!ui

第二分析算法的时间复杂度

算法的时间复杂度反映了程序执行时间随输入规模增加而增加的量级,在很大程度上能很好反映出算法的好坏。

时间复杂度

一、什么是时间复杂度

一个算法花费的时间与算法中语句的执行次数成正比例,哪一个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)
通常状况下,算法中基本操做重复执行的次数是问题规模n的某个函数,用T(n)表示,如有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记做T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

二、时间复杂度的计算方法

一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但咱们不可能也没有必要对每一个算法都上机测试由于该方法有两个缺陷:

  • 想要对设计的算法的运行性能进行测评,必须先依据算法编写相应的程序并实际运行。
  • 所得时间的统计计算依赖于计算机的硬件、软件等环境因素,有时候容易掩盖算法的自己优点。

因此只需知道哪一个算法花费的时间多,哪一个算法花费的时间少就能够了。而且一个算法花费的时间与算法中语句的执行次数成正比例,哪一个算法中语句执行次数多,它花费时间就多。

 

通常状况下,算法的基本操做重复执行的次数是模块n的某一个函数f(n),所以,算法的时间复杂度记作:T(n)=O(f(n))。随着模块n的增大,算法执行的时间的增加率和f(n)的增加率成正比,因此f(n)越小,算法的时间复杂度越低,算法的效率越高。 

 在计算时间复杂度的时候,先找出算法的基本操做,而后根据相应的各语句肯定它的执行次数,再找出T(n)的同数量级(它的同数量级有如下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n)=该数量级,若T(n)/f(n)求极限可获得一常数c,则时间复杂度T(n)=O(f(n))。

三、常见的时间复杂度

常见的算法时间复杂度由小到大依次为:

Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

 求解算法的时间复杂度的具体步骤:

  • 找出算法中的基本语句,算法中执行最多的那条语句是基本语句,一般是最内层循环的循环体。
  • 计算基本语句的执行次数的量级,保证最高次幂正确便可查看他的增加率。
  • 用大O几号表示算法的时间性能

 若是算法中包含镶套的循环,则基本语句一般是最内层的循环体,若是算法中包并列的循环,则将并列的循环时间复杂度相加,例如:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

n = 100

for i in range(n):
    print(i)


for i in range(n): ##每循i里的一个元素,for循环内部嵌套的for循环就整个循环一次
    for q in range(n):
        print(q)

第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n2),则整个算法的时间复杂度为Ο(n+n2)=Ο(n2)。

Ο(1)表示基本语句的执行次数是一个常数,通常来讲,只要算法中不存在循环语句,其时间复杂度就是Ο(1)。

其中Ο(log2n)、Ο(n)、 Ο(nlog2n)、Ο(n2)和Ο(n3)称为多项式时间,而Ο(2n)和Ο(n!)称为指数时间,计算机科学家广泛认为前者(即多项式时间复杂度的算法)是有效算法,把这类问题称为P(Polynomial,多项式)类问题,而把后者(即指数时间复杂度的算法)称为NP(Non-Deterministic Polynomial, 非肯定多项式)问题在选择算法的时候,优先选择前者!

 

OK我懂对于没有算法基础的同窗,看起算法来也很头疼,可是这个是基础和重点,不会算法的开发不是一个合格的开发而且包括语言记得基础也是须要好好整理的!加油吧~~  我们在一块儿看下时间复杂度的详细说明吧

常见的时间复杂度示例

一、O(1)

#O(1)

n = 100 
sum = (1+n) * n/2 #执行一次
sum_1 = (n/2) - 10 #执行一次
sum_2 = n*4 - 10 + 8 /2 #执行一次

这个算法的运行次数函数是f(n)=3。根据咱们推导大O阶的方法,第一步就是把常数项3改成1。在保留最高阶项时发现,它根本没有最高阶项,因此这个算法的时间复杂度为O(1)。

而且:若是算法的执行时间不随着问题规模n的增加而增长,及时算法中有上千条语句,其执行的时间也不过是一个较大的常数。此类算法的时间复杂度记做O(1)

二、O(n2)

n = 100 

for i in range(n): #执行了n次
    for q in range(n): #执行了n2
        print(q) #执行了n2

解:T(n)=2n2+n+1 =O(n2)

通常状况下,对进循环语句只需考虑循环体中语句的执行次数,忽略该语句中步长加一、终值判别、控制转移等成分当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。  

三、O(n)   

#O(n)

n =100 
a = 0 #执行一次
b = 1#执行一次
for i in range(n): #执行n次
    s = a +b #执行n-1次
    b =a #执行n-1次
    a =s #执行n-1次

解:T(n)=2+n+3(n-1)=4n-1=O(n)

四、Ο(n3)

#O(n3)
n = 100
for i in range(n):#执行了n次
    for q in range(n):#执行了n^2
        for e in range(n):#执行了n^3
            print(e)#执行了n^3

简单点来去最大值是:Ο(n3)

五、经常使用的算法的时间复杂度和空间复杂度

排序法 平均时间 最差状况 稳定度 额外空间 备注
冒泡排序 Ο(n2) Ο(n2) 稳定 O(1) n小时较好
交换排序 Ο(n2) Ο(n2) 不稳定 O(1) n小时较好
选择排序 Ο(n2) Ο(n2) 不稳定 O(1) n小时较好
插入排序 Ο(n2) Ο(n2) 稳定 O(1) 大部分已排序时较好
快速排序 Ο(nlogn) Ο(n2) 不稳定 Ο(nlogn) n较大时较好
希尔排序(SHELL) Ο(log2n) Ο(ns)  1<s<2
不稳定 O(1) s是所选分组
归并排序 Ο(log2n) Ο(log2n) 稳定 O(1) n大时较好
堆排序 Ο(log2n) Ο(log2n) 不稳定 O(1) n大时较好
基数排序 Ο(logRB) Ο(logRB) 稳定 O(N)

B是真数(0-9)

R是基数(个十百)

 

 

 

 

 

 

 

 

 

 

 

排序实例

排序算法是在更复杂的算法中的是一个构建基础,因此先看下经常使用的排序。

一、冒泡排序

需求:

请按照从小到大对列表,进行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:相邻两个值进行比较,将较大的值放在右侧,依次比较!

原理图:

原理分析:

列表中有5个元素两两进行比较,若是左边的值比右边的值大,就用中间值进行循环替换!
既然这样,咱们还能够用一个循环把上面的循环进行在次循环,用表达式构造出内部循环!

代码实现:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'
import random

maopao_list = [13, 22, 6, 99, 11]
'''
原理分析:
列表中有5个元素两两进行比较,若是左边的值比右边的值大,就用中间值进行循环替换!
既然这样,咱们还能够用一个循环把上面的循环进行在次循环,用表达式构造出内部循环!
'''

def handler(array):
    for i in range(len(array)):
        for j in range(len(array)-1-i):
            '''
            这里为何要减1,咱们看下若是里面有5个元素咱们须要循环几回?最后一个值和谁对比呢?对吧!因此须要减1
            这里为何减i?,这个i是循环的下标,若是咱们循环了一次以后最后一只值已是最大的了还有必要再进行一次对比吗?没有必要~
            '''
            print('left:%d' % array[j],'right:%d' % array[j+1])
            if array[j] > array[j+1]:
                tmp = array[j]
                array[j] = array[j+1]
                array[j+1] = tmp



if __name__ == '__main__':
    handler(maopao_list)
    print(maopao_list)

时间复杂度说明看下他的代码复杂度会随着N的增大而成指数型增加,而且根据判断他时间复杂度为Ο(n2)

二、选择排序

需求:

请按照从小到大对列表,进行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:

第一次,从列表最左边开始元素为array[0],往右循环,从右边元素中找到小于array[0]的元素进行交换,直到右边循环完以后。

第二次,左边第一个元素如今是最小的了,就从array[1],和剩下的array[1:-1]内进行对比,依次进行对比!

对比:

他和冒泡排序的区别就是,冒泡排序是相邻的两两作对比,可是选择排序是左侧的“对比元素”和右侧的列表内值作对比!

原理图:

代码实现:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'


xuanze_list = [13, 22, 6, 99, 11]

print(range(len(xuanze_list)))

def handler(array):
    for i in range(len(array)):
        '''
        循环整个列表
        '''
        for j in range(i,len(array)):
            '''
            这里的小循环里,循环也是整个列表可是他的起始值是i,当这一个小循环完了以后最前面的确定是已经排序好的
            第二次的时候这个值是循环的第几回的值好比第二次是1,那么循环的起始值就是array[1]
            '''
            if array[i] > array[j]:
                temp = array[i]
                array[i] = array[j]
                array[j] = temp
        # print(array)


if __name__ == '__main__':
    handler(xuanze_list)
    print(xuanze_list)

选择排序代码优化:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

import random
import time


def handler(array):
    for i in range(len(array)):
        smallest_index = i  #假设默认第一个值最小
        for j in range(i,len(array)):
            if array[smallest_index] > array[j]:
                smallest_index = j  #若是找到更小的,记录更小元素的下标
        '''
        小的循环结束后在交换,这样整个小循环就以前的选择排序来讲,少了不少的替换过程,就只替换了一次!提高了速度
        '''
        tmp = array[i]
        array[i] = array[smallest_index]
        array[smallest_index] = tmp


if __name__ == '__main__':
    array = []
    old_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    handler(array)
    print(array)
    print('Cost time is :',time.time() - old_time)

三、插入排序

需求

请按照从小到大对列表,进行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:

一个列表默认分为左侧为排序好的,咱们拿第一个元素举例,他左边的全是排序好的,他右侧是没有排序好的,若是右侧的元素小于左侧排序好的列表的元素就把他插入到合适的位置

原理图:

 

代码实现:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'


import random
import time
chaoru_list = [69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]

def handler(array):
    for i in range(1,len(array)):
        position = i #刚开始往左边走的第一个位置
        current_val = array[i] #先把当前值存下来
        while position > 0 and current_val < array[position -1]:
            '''
            这里为何用while循环,我们在判断左边的值得时候知道他有多少个值吗?不知道,因此用while循环
            何时停下来呢?当左边没有值得时候,或者当他大于左边的值得时候!
            '''
            array[position] = array[position - 1] #若是whille条件成立把当前的值替换为他上一个值
            '''
            好比一个列表:
            [3,2,4,1]
            如今循环到 1了,他前面的元素已经循环完了
            [2,3,4] 1

            首先咱们记录下当前这个position的值 = 1
            [2,3,4,4] 这样,就出一个位置了
            在对比前面的3,1比3小
            [2,3,3,4] 在替换一下他们的值
             在对比2
            [2,2,3,4]
            最后while不执行了在进行替换'array[position] = current_val  #把值替换'
            '''
            position -= 1
        #当上面的条件都不成立的时候{左边没有值/左边的值不比本身的值小}
        array[position] = current_val  #把值替换


if __name__ == '__main__':
    handler(chaoru_list)
    print(chaoru_list)

'''
    array = []#[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]
    old_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    handler(array)
    print(array)
    print('Cost time is :',time.time() - old_time)
'''

四、快速排序

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(一般选用数组的第一个数)做为关键数据,而后将全部比它小的数都放到它前面,全部比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变更.他的时间复杂度是:O(nlogn) ~Ο(n2)

排序示例:

假设用户输入了以下数组:

建立变量i=0(指向第一个数据)[i所在位置红色小旗子], j=5(指向最后一个数据)[j所在位置蓝色小旗子], k=6(赋值为第一个数据的值)。

咱们要把全部比k小的数移动到k的左面,因此咱们能够开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,咱们找到第一个下标3的数据比6小,因而把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:

i=0 j=3 k=6

接着,开始第二次比较,此次要变成找比k大的了,并且要从前日后找了。递加变量i,发现下标2的数据是第一个比k大的,因而用下标2的数据7和j指向的下标3的数据的6作交换,数据状态变成下表:

 i=2 j=3 k=6

称上面两次比较为一个循环。
接着,再递减变量j,不断重复进行上面的循环比较。
在本例中,咱们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。因而,第一遍比较结束。获得结果以下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:

若是i和j没有碰头的话,就递加i找大的,尚未,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。

而后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。
注意:第一遍快速排序不会直接获得最终结果,只会把比k大和比k小的数分到k的两边。为了获得最后结果,须要再次对下标2两边的数组分别执行此步骤,而后再分解数组,直到数组不能再分解为止(只有一个数据),才能获得正确结果。

代码实现:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:luotianshuai
import random
import time

def quick_sort(array,start,end):
    if start >= end:
        return
    k = array[start]
    left_flag = start
    right_flag = end
    while left_flag < right_flag:
        '''
        left_flag = start 默认为0
        right_flag = end 默认为传来的列表总长度
        当left_flag 小与right_flag的时候成立,说明左右两边的小旗子尚未碰头(为相同的值)
        '''
        #右边旗子
        while left_flag < right_flag and array[right_flag] > k:#表明要继续往左一移动小旗子
            right_flag -= 1
        '''
        若是上面的循环中止说明找到右边比左边的值小的数了,须要进行替换
        '''
        tmp = array[left_flag]
        array[left_flag] = array[right_flag]
        array[right_flag] = tmp

        #左边旗子
        while left_flag < right_flag and array[left_flag] <= k:
            #若是没有找到比当前的值大的,left_flag 就+=1
            left_flag += 1
        '''
        若是上面的循环中止说明找到当前段左边比右边大的值,进行替换
        '''
        tmp = array[left_flag]
        array[left_flag] = array[right_flag]
        array[right_flag] = tmp

    #进行递归把问题分半
    quick_sort(array,start,left_flag-1)
    quick_sort(array,left_flag+1,end)

if __name__ == '__main__':
    array = []  # [69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]
    start_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    quick_sort(array,0,len(array)-1)
    end_time = time.time()
    print(array)
    print(start_time,end_time)
    cost_time = end_time - start_time
    print('Cost time is :%d' % cost_time)
相关文章
相关标签/搜索