常用算法与设计模式

2023-11-13

时间复杂度

二、计算方法
1.一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
2.一般情况下,算法的基本操作重复执行的次数是模块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))。
3.常见的时间复杂度
按数量级递增排列,常见的时间复杂度有:
常数阶O(1),  对数阶O(log2n),  线性阶O(n),  线性对数阶O(nlog2n),  平方阶O(n^2), 立方阶O(n^3),..., k次方阶O(n^k), 指数阶O(2^n) 。
其中,
1.O(n),O(n^2), 立方阶O(n^3),..., k次方阶O(n^k) 为多项式阶时间复杂度,分别称为一阶时间复杂度,二阶时间复杂度。。。。
2.O(2^n),指数阶时间复杂度,该种不实用
3.对数阶O(log2n),   线性对数阶O(nlog2n),除了常数阶以外,该种效率最高
例:算法:
  for(i=1;i<=n;++i)
  {
     for(j=1;j<=n;++j)
     {
         c[ i ][ j ]=0; //该步骤属于基本操作 执行次数:n^2
          for(k=1;k<=n;++k)
               c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //该步骤属于基本操作 执行次数:n^3
     }
  }
  则有 T(n)= n^2+n^3,根据上面括号里的同数量级,我们可以确定 n^3为T(n)的同数量级
  则有f(n)= n^3,然后根据T(n)/f(n)求极限可得到常数c
  则该算法的 时间复杂度:T(n)=O(n^3)
四、

定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数 T(n)称为这一算法的“时间复杂性”。

当输入量n逐渐加大时,时间复杂性的极限情形称为算法的“渐近时间复杂性”。

我们常用大O表示法表示时间复杂性,注意它是某一个算法的时间复杂性。大O表示只是说有上界,由定义如果f(n)=O(n),那显然成立f(n)=O(n^2),它给你一个上界,但并不是上确界,但人们在表示的时候一般都习惯表示前者。

此外,一个问题本身也有它的复杂性,如果某个算法的复杂性到达了这个问题复杂性的下界,那就称这样的算法是最佳算法。

“大O记法”:在这种描述中使用的基本参数是 n,即问题实例的规模,把复杂性或运行时间表达为n的函数。这里的“O”表示量级 (order),比如说“二分检索是 O(logn)的”,也就是说它需要“通过logn量级的步骤去检索一个规模为n的数组”记法 O ( f(n) )表示当 n增大时,运行时间至多将以正比于 f(n)的速度增长。

这种渐进估计对算法的理论分析和大致比较是非常有价值的,但在实践中细节也可能造成差异。例如,一个低附加代价的O(n2)算法在n较小的情况下可能比一个高附加代价的 O(nlogn)算法运行得更快。当然,随着n足够大以后,具有较慢上升函数的算法必然工作得更快。

O(1)

Temp=i;i=j;j=temp;                    

以上三条单个语句的频度均为1,该程序段的执行时间是一个与问题规模n无关的常数。算法的时间复杂度为常数阶,记作T(n)=O(1)。如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。


O(n^2)

2.1. 交换i和j的内容
     sum=0;                 (一次)
     for(i=1;i<=n;i++)       (n次 )
        for(j=1;j<=n;j++) (n^2次 )
         sum++;       (n^2次 )
解:T(n)=2n^2+n+1 =O(n^2)

2.2.   
    for (i=1;i<n;i++)
    {
        y=y+1;         ①   
        for (j=0;j<=(2*n);j++)    
           x++;        ②      
    }         
解: 语句1的频度是n-1
          语句2的频度是(n-1)*(2n+1)=2n^2-n-1
          f(n)=2n^2-n-1+(n-1)=2n^2-2
          该程序的时间复杂度T(n)=O(n^2).         

O(n)      
                                                      
2.3.
    a=0;
    b=1;                      ①
    for (i=1;i<=n;i++) ②
    {  
       s=a+b;    ③
       b=a;     ④  
       a=s;     ⑤
    }
解:语句1的频度:2,        
           语句2的频度: n,        
          语句3的频度: n-1,        
          语句4的频度:n-1,    
          语句5的频度:n-1,                                  
          T(n)=2+n+3(n-1)=4n-1=O(n).
                                                                                                 

O(log2n )

2.4.
     i=1;       ①
    while (i<=n)
       i=i*2; ②
解: 语句1的频度是1,  
          设语句2的频度是f(n),   则:2^f(n)<=n;f(n)<=log2n    
          取最大值f(n)= log2n,
          T(n)=O(log2n )

O(n^3)

2.5.
    for(i=0;i<n;i++)
    {  
       for(j=0;j<i;j++)  
       {
          for(k=0;k<j;k++)
             x=x+2;  
       }
    }
解:当i=m, j=k的时候,内层循环的次数为k当i=m时, j 可以取 0,1,...,m-1 , 所以这里最内循环共进行了0+1+...+m-1=(m-1)m/2次所以,i从0取到n, 则循环共进行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6所以时间复杂度为O(n^3).
                                  

我们还应该区分算法的最坏情况的行为和期望行为。如快速排序的最 坏情况运行时间是 O(n^2),但期望时间是 O(nlogn)。通过每次都仔细 地选择基准值,我们有可能把平方情况 (即O(n^2)情况)的概率减小到几乎等于 0。在实际中,精心实现的快速排序一般都能以 (O(nlogn)时间运行。
下面是一些常用的记法:


访问数组中的元素是常数时间操作,或说O(1)操作。一个算法如 果能在每个步骤去掉一半数据元素,如二分检索,通常它就取 O(logn)时间。用strcmp比较两个具有n个字符的串需要O(n)时间。常规的矩阵乘算法是O(n^3),因为算出每个元素都需要将n对 元素相乘并加到一起,所有元素的个数是n^2。
指数时间算法通常来源于需要求出所有可能结果。例如,n个元 素的集合共有2n个子集,所以要求出所有子集的算法将是O(2n)的。指数算法一般说来是太复杂了,除非n的值非常小,因为,在 这个问题中增加一个元素就导致运行时间加倍。不幸的是,确实有许多问题 (如著名的“巡回售货员问题” ),到目前为止找到的算法都是指数的。如果我们真的遇到这种情况,通常应该用寻找近似最佳结果的算法替代之。

常用排序

 

名称

复杂度

说明

备注

冒泡排序
Bubble Sort

O(N*N)

将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮

 

插入排序

Insertion sort

O(N*N)

逐一取出元素,在已经排序的元素序列中从后向前扫描,放到适当的位置

起初,已经排序的元素序列为空

选择排序

O(N*N)

首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此递归。

 

快速排序

Quick Sort

O(n *log2(n))

先选择中间值,然后把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使用这个过程(递归)。

 

堆排序HeapSort

O(n *log2(n))

利用堆(heaps)这种数据结构来构造的一种排序算法。堆是一个近似完全二叉树结构,并同时满足堆属性:即子节点的键值或索引总是小于(或者大于)它的父节点。

近似完全二叉树

希尔排序

SHELL

O(n1+£)

0<£<1

选择一个步长(Step) ,然后按间隔为步长的单元进行排序.递归,步长逐渐变小,直至为1.

 

箱排序
Bin Sort

O(n)

设置若干个箱子,把关键字等于 k 的记录全都装入到第k 个箱子里 ( 分配 ) ,然后按序号依次将各非空的箱子首尾连接起来 ( 收集 ) 。

分配排序的一种:通过" 分配 " 和 " 收集 " 过程来实现排序。

 

选择排序

The algorithm works by selecting the smallest unsorted item and then swapping it with the item in the next position to be filled.

The selection sort works as follows: you look through the entire array for the smallest element, once you find it you swap it (the smallest element) with the first element of the array. Then you look for the smallest element in the remaining array (an array without the first element) and swap it with the second element. Then you look for the smallest element in the remaining array (an array without first and second elements) and swap it with the third element, and so on. Here is an example,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<code class="python plain">source </code><code class="python keyword">=</code> <code class="python plain">[</code><code class="python value">29</code><code class="python plain">,</code><code class="python value">64</code><code class="python plain">,</code><code class="python value">73</code><code class="python plain">,</code><code class="python value">34</code><code class="python plain">,</code><code class="python value">20</code><code class="python plain">]</code>

  

  

count = 0

for in range(len(source)):

    for in range(i,len(source)):

        source_ele = source[i] #要被拿来对比且替换的原始值

  

        if source[j] < source_ele : #只要这个列表后面的值比最前面的值大,那就把它俩个做个对调

            source[i] = source[j]

            source[j] = source_ele

        count +=1

    print(source)

print("运行次数:",count)

Example.

29, 64, 73, 34,  20
20,  64, 73, 34,  29
20, 29,  7334, 64 
20, 29, 34,  7364 
20, 29, 34, 64, 73 

The worst-case runtime complexity is O(n2).  

插入排序(Insertion Sort)

插入排序(Insertion Sort)的基本思想是:将列表分为2部分,左边为排序好的部分,右边为未排序的部分,循环整个列表,每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。

1

2

3

4

5

6

7

8

9

10

11

12

13

source = [92, 77, 67, 8, 6, 84, 55, 85, 43, 67]

   

   

for index in range(1,len(source)):

    current_val = source[index] #先记下来每次大循环走到的第几个元素的值

    position = index

   

    while position > 0 and source[position-1] > current_val: #当前元素的左边的紧靠的元素比它大,要把左边的元素一个一个的往右移一位,给当前这个值插入到左边挪一个位置出来

        source[position] = source[position-1] #把左边的一个元素往右移一位

        position -= 1 #只一次左移只能把当前元素一个位置 ,还得继续左移只到此元素放到排序好的列表的适当位置 为止

   

    source[position] = current_val #已经找到了左边排序好的列表里不小于current_val的元素的位置,把current_val放在这里

    print(source)

结果:

[77, 92, 67, 8, 6, 84, 55, 85, 43, 67]
[67, 77, 92, 8, 6, 84, 55, 85, 43, 67]
[8, 67, 77, 92, 6, 84, 55, 85, 43, 67]
[6, 8, 67, 77, 92, 84, 55, 85, 43, 67]
[6, 8, 67, 77, 84, 92, 55, 85, 43, 67]
[6, 8, 55, 67, 77, 84, 92, 85, 43, 67]
[6, 8, 55, 67, 77, 84, 85, 92, 43, 67]
[6, 8, 43, 55, 67, 77, 84, 85, 92, 67]
[6, 8, 43, 55, 67, 67, 77, 84, 85, 92]  

快速排序(quick sort)

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动  

注:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。 

排序演示

示例

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

下标

0

1

2

3

4

5

数据

6

2

7

3

8

9

创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。

我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:

下标

0

1

2

3

4

5

数据

3

2

7

6

8

9

i=0 j=3 k=6

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

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

i=2 j=3 k=6

称上面两次比较为一个循环。

接着,再递减变量j,不断重复进行上面的循环比较。

在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

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

然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

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

#_*_coding:utf-8_*_
 
 
def quick_sort(array,left,right):
    '''
 
    :param array:
    :param left: 列表的第一个索引
    :param right: 列表最后一个元素的索引
    :return:
    '''
    if left >=right:
        return
    low = left
    high = right
    key = array[low] #第一个值
 
    while low < high:#只要左右未遇见
        while low < high and array[high] > key: #找到列表右边比key大的值 为止
            high -= 1
        #此时直接 把key(array[low]) 跟 比它大的array[high]进行交换
        array[low] = array[high]
        array[high] = key
 
 
        while low < high and array[low] <= key : #找到key左边比key大的值
            low += 1
            #array[low] =
        #找到了左边比k大的值 ,把array[high](此时应该刚存成了key) 跟这个比key大的array[low]进行调换
        array[high] = array[low]
        array[low] = key
 
    quick_sort(array,left,low-1) #最后用同样的方式对分出来的左边的小组进行同上的做法
    quick_sort(array,low+1, right)#用同样的方式对分出来的右边的小组进行同上的做法
 
 
 
if __name__ == '__main__':
 
    array = [96,14,10,9,6,99,16,5,1,3,2,4,1,13,26,18,2,45,34,23,1,7,3,22,19,2]
    #array = [8,4,1, 14, 6, 2, 3, 9,5, 13, 7,1, 8,10, 12]
    print("before sort:", array)
    quick_sort(array,0,len(array)-1)
 
    print("-------final -------")
    print(array)

二叉树

树的特征和定义

  树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。树在计算机领域中也得到广泛应用,如在编译源程序时,可用树表示源程序的语法结构。又如在数据库系统中,树型结构也是信息的重要组织形式之一。一切具有层次关系的问题都可用树来描述。

 

树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:

树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。

每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,8,7是3的子节点, 3是1,8,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图中的1,8,9,5节点。从图中还可以看到,上面的树总共有4个层次,6位于第一层,9位于第四层。树中节点的最大层次被称为深度。也就是说,该树的深度(depth)为4。 

如果我们从节点3开始向下看,而忽略其它部分。那么我们看到的是一个以节点3为根节点的树:

三角形代表一棵树

再进一步,如果我们定义孤立的一个节点也是一棵树的话,原来的树就可以表示为根节点和子树(subtree)的关系:

 

上述观察实际上给了我们一种严格的定义树的方法:

1. 树是元素的集合。

2. 该集合可以为空。这时树中没有元素,我们称树为空树 (empty tree)。

3. 如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与它的子树的根节点用一个边(edge)相连。

上面的第三点是以递归的方式来定义树,也就是在定义树的过程中使用了树自身(子树)。由于树的递归特征,许多树相关的操作也可以方便的使用递归实现。我们将在后面看到。 

树的实现

树的示意图已经给出了树的一种内存实现方式: 每个节点储存元素和多个指向子节点的指针。然而,子节点数目是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变化。这种不确定性就可能带来大量的内存相关操作,并且容易造成内存的浪费。

一种经典的实现方式如下:

树的内存实现

拥有同一父节点的两个节点互为兄弟节点(sibling)。上图的实现方式中,每个节点包含有一个指针指向第一个子节点,并有另一个指针指向它的下一个兄弟节点。这样,我们就可以用统一的、确定的结构来表示每个节点。

计算机的文件系统是树的结构,比如Linux文件管理背景知识中所介绍的。在UNIX的文件系统中,每个文件(文件夹同样是一种文件),都可以看做是一个节点。非文件夹的文件被储存在叶节点。文件夹中有指向父节点和子节点的指针(在UNIX中,文件夹还包含一个指向自身的指针,这与我们上面见到的树有所区别)。在git中,也有类似的树状结构,用以表达整个文件系统的版本变化 (参考版本管理三国志)。

 二叉树: 

二叉树是由n(n≥0)个结点组成的有限集合、每个结点最多有两个子树的有序树。它或者是空集,或者是由一个根和称为左、右子树的两个不相交的二叉树组成。

特点:

(1)二叉树是有序树,即使只有一个子树,也必须区分左、右子树;

(2)二叉树的每个结点的度不能大于2,只能取0、1、2三者之一;

(3)二叉树中所有结点的形态有5种:空结点、无左右子树的结点、只有左子树的结点、只有右子树的结点和具有左右子树的结点。

 

二叉树(binary)是一种特殊的树。二叉树的每个节点最多只能有2个子节点:

二叉树

由于二叉树的子节点数目确定,所以可以直接采用上图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。 

如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。

(如果我们假设树中没有重复的元素,那么上述要求可以写成:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小)

二叉搜索树,注意树中元素的大小

二叉搜索树可以方便的实现搜索算法。在搜索元素x的时候,我们可以将x和根节点比较:

1. 如果x等于根节点,那么找到x,停止搜索 (终止条件)

2. 如果x小于根节点,那么搜索左子树

3. 如果x大于根节点,那么搜索右子树

二叉搜索树所需要进行的操作次数最多与树的深度相等。n个节点的二叉搜索树的深度最多为n,最少为log(n)。

二叉树的遍历

遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。

前序遍历:根节点->左子树->右子树

中序遍历:左子树->根节点->右子树

后序遍历:左子树->右子树->根节点

例如:求下面树的三种遍历

 

前序遍历:abdefgc

中序遍历:debgfac

后序遍历:edgfbca

 二叉树的类型

(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树

(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。

(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

如何判断一棵树是完全二叉树?按照定义,

教材上的说法:一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树。这个概念很好理解,

就是一棵树,深度为k,并且没有空位。

首先对满二叉树按照广度优先遍历(从左到右)的顺序进行编号。

一颗深度为k二叉树,有n个节点,然后,也对这棵树进行编号,如果所有的编号都和满二叉树对应,那么这棵树是完全二叉树。

 

image

如何判断平衡二叉树?

image

(b)左边的图 左子数的高度为3,右子树的高度为1,相差超过1

(b)右边的图 -2的左子树高度为0  右子树的高度为2,相差超过1

二叉树遍历实现 

#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import time,random
def sift_down(arr, node, end):
    root = node
    #print(root,2*root+1,end)
    while True:
        # 从root开始对最大堆调整
 
        child = 2 * root +1  #left child
        if child  > end:
            #print('break',)
            break
        print("v:",root,arr[root],child,arr[child])
        print(arr)
        # 找出两个child中交大的一个
        if child + 1 <= end and arr[child] < arr[child + 1]: #如果左边小于右边
            child += 1 #设置右边为大
 
        if arr[root] < arr[child]:
            # 最大堆小于较大的child, 交换顺序
            tmp = arr[root]
            arr[root] = arr[child]
            arr[child]= tmp
 
            # 正在调整的节点设置为root
            #print("less1:", arr[root],arr[child],root,child)
 
            root = child #
            #[3, 4, 7, 8, 9, 11, 13, 15, 16, 21, 22, 29]
            #print("less2:", arr[root],arr[child],root,child)
        else:
            # 无需调整的时候, 退出
            break
    #print(arr)
    print('-------------')
 
def heap_sort(arr):
    # 从最后一个有子节点的孩子还是调整最大堆
    first = len(arr) // 2 -1
    for i in range(first, -1, -1):
        sift_down(arr, i, len(arr) - 1)
    #[29, 22, 16, 9, 15, 21, 3, 13, 8, 7, 4, 11]
    print('--------end---',arr)
    # 将最大的放到堆的最后一个, 堆-1, 继续调整排序
    for end in range(len(arr) -1, 0, -1):
        arr[0], arr[end] = arr[end], arr[0]
        sift_down(arr, 0, end - 1)
        #print(arr)
def main():
    # [7, 95, 73, 65, 60, 77, 28, 62, 43]
    # [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    #l = [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    #l = [16,9,21,13,4,11,3,22,8,7,15,27,0]
    array = [16,9,21,13,4,11,3,22,8,7,15,29]
    #array = []
    #for i in range(2,5000):
    #    #print(i)
    #    array.append(random.randrange(1,i))
 
    print(array)
    start_t = time.time()
    heap_sort(array)
    end_t = time.time()
    print("cost:",end_t -start_t)
    print(array)
    #print(l)
    #heap_sort(l)
    #print(l)
 
 
if __name__ == "__main__":
    main()

堆排序

堆排序,顾名思义,就是基于堆。因此先来介绍一下堆的概念。
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。

堆排序就是把堆顶的最大数取出,

将剩余的堆继续调整为最大堆,具体过程在第二块有介绍,以递归实现

剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束

#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import time,random
def sift_down(arr, node, end):
    root = node
    #print(root,2*root+1,end)
    while True:
        # 从root开始对最大堆调整
 
        child = 2 * root +1  #left child
        if child  > end:
            #print('break',)
            break
        print("v:",root,arr[root],child,arr[child])
        print(arr)
        # 找出两个child中交大的一个
        if child + 1 <= end and arr[child] < arr[child + 1]: #如果左边小于右边
            child += 1 #设置右边为大
 
        if arr[root] < arr[child]:
            # 最大堆小于较大的child, 交换顺序
            tmp = arr[root]
            arr[root] = arr[child]
            arr[child]= tmp
 
            # 正在调整的节点设置为root
            #print("less1:", arr[root],arr[child],root,child)
 
            root = child #
            #[3, 4, 7, 8, 9, 11, 13, 15, 16, 21, 22, 29]
            #print("less2:", arr[root],arr[child],root,child)
        else:
            # 无需调整的时候, 退出
            break
    #print(arr)
    print('-------------')
 
def heap_sort(arr):
    # 从最后一个有子节点的孩子还是调整最大堆
    first = len(arr) // 2 -1
    for i in range(first, -1, -1):
        sift_down(arr, i, len(arr) - 1)
    #[29, 22, 16, 9, 15, 21, 3, 13, 8, 7, 4, 11]
    print('--------end---',arr)
    # 将最大的放到堆的最后一个, 堆-1, 继续调整排序
    for end in range(len(arr) -1, 0, -1):
        arr[0], arr[end] = arr[end], arr[0]
        sift_down(arr, 0, end - 1)
        #print(arr)
def main():
    # [7, 95, 73, 65, 60, 77, 28, 62, 43]
    # [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    #l = [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    #l = [16,9,21,13,4,11,3,22,8,7,15,27,0]
    array = [16,9,21,13,4,11,3,22,8,7,15,29]
    #array = []
    #for i in range(2,5000):
    #    #print(i)
    #    array.append(random.randrange(1,i))
 
    print(array)
    start_t = time.time()
    heap_sort(array)
    end_t = time.time()
    print("cost:",end_t -start_t)
    print(array)
    #print(l)
    #heap_sort(l)
    #print(l)
 
 
if __name__ == "__main__":
    main()

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常用算法与设计模式 的相关文章

  • java Swing 时间选择器控件

    效果图 简要说明 一个视图类 HongYeLingGuDate 一个抽象接口 SelectHYDateAbstract 需要导入的jar包 http download csdn net detail male09 9884835 中的 fl
  • keil5报错:* RESTRICTED VERSION WITH 0800H BYTE CODE SIZE LIMIT; USED: 08B5H BYTE (108%) *保姆级教程

    项目场景 keil uVsion5的编译的时代码的字节过大出现 问题描述 RESTRICTED VERSION WITH 0800H BYTE CODE SIZE LIMIT 原因分析 原因就是你的keil为2048字节代码限制的版本 而你
  • docker从入门到实践

    本文的主要目的是帮助零基础的读者快速上手docker 并掌握一些相关的常见命令 同时 本文也是作者对学习docker的一个笔记记录 本文主要是从实例出发 讲解一些docker的基本操作 若有不妥或错误之处 烦请指出 不胜感激 PS 关于do
  • [机器学习与scikit-learn-27]:算法-回归-多元线性回归的几何原理、线性代数原理、本质(去掉激活函数的神经元)

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 123540305 目录 第1章 回归概
  • 虚拟显示器

    这个虚拟显示器只有驱动程序 没有直接双击就能安装的安装包 所以安装起来相对麻烦一点 不过这也是我找到的唯一的 免费 纯软件实现的方案了 原文链接 英文 https www amyuni com forum viewtopic php t 3
  • STM32最小系统|CSDN创作打卡

    文章目录 STM32最小系统的构成 晶振电路 复位电路 电源电路 下载电路 STM32最小系统的构成 晶振电路 晶振在起振的一瞬间会产生电感 为了消除这些电感干扰 会 在晶振的两端接入无极性电容 10 33pf 这里不推荐使用内部时钟源 R
  • idea不显示 target文件夹的解决方式

    第一种 比较简单的解决方式 但是可能适用面比较小 解决方式如下所示 第二种 稍微麻烦一点 如下图所示 点击 file gt 搜索框内输入 File Types 进入到图中所示的界面 把里面的 target删除掉就可以了 重启一下idea 如
  • 从0开始的leetCode: Add Two Number

    我的解法是 class Solution public ListNode addTwoNumbers ListNode l1 ListNode l2 int i 0 ListNode pre l1 while l1 null l1 val
  • DEBUG

    UnicodeDecodeError utf 8 codec can t decode byte 0x80 in position 3131 invalid start byte解决办法 阿里drl binpacking使用TensorFl
  • paddlenlp调用ERNIE、使用ERNIEKIT

    目录 paddle调用ERNIE 安装paddle和paddlenlp 下载 加载ERNIE预训练模型 tokenizer 获取文本语义特征向量表示 ERNIEKIT实践 安装 配置nltk 下载ERNIEKIT源码 运行ERNIEKIT
  • C#的基础语法---15种基本数据类型

    1 c 的基本单元是class class 类 即指同一类对象的抽象化概念 类里面的所有东西叫做类成员 属性 方法 事件 2 属性类型 C 中的数据类型一共15种 整数型 8种 位 代表整数的取值范围 2的位数次方 求出来 1就是最大值 符
  • 深入浅出讲解IDS(入侵检测系统)

    一 什么是IDS IDS是英文 intrusion Detection Systems 的缩写 中文意思是 入侵检测系统 入侵检测系统 是一种对网络传输进行即时监视 在发现可疑传输时发出警报或者采取主动反应措施的网络安全设备 专业上来讲 I
  • 我的创作纪念日-学习不止,笃行不怠

    机缘 平时的学习都会看很多视频教程 有一些会带ppt或者文稿 很多都没有 基本上都是猴子搬玉米 边学边忘 缺乏总结 就想尝试在CSDN上边学边记录 做总结 效果不错 在 CSDN 上学习并记录总结的具体方法如下 注册 CSDN 账号并登录
  • wsl2 安装ubuntu已经开启vt(虚拟化)仍然报错0x80370102

    一条命令 Enable WindowsOptionalFeature Online FeatureName VirtualMachinePlatform
  • 丰田一页纸极简思考法excel1+logic3

    公司书柜中无意get到的 因为当时对 思维 比较感兴趣 然后这本本图表内容多 书薄字少 就想着借出来看了 中午饭空的时间用了大概一个小时的样子 可以读一大半 废话了这么多 分享下这本书的内容吧 在丰田公司 有一个不成文的要求 不论是会议纪要
  • C语言中setjmp和longjmp函数

    C语言中setjmp和longjmp函数 setjmp和longjmp是C语言独有的 只有将它们结合起来使用 才能达到程序控制流有效转移的目的 按照程序员的预先设计的意图 去实现对程序中可能出现的异常进行集中处理 先来看一下这两个函数的定义

随机推荐

  • truncate mysql批量删除表的数据

    模板拼接执行语句 SELECT CONCAT truncate TABLE table schema TABLE NAME FROM INFORMATION SCHEMA TABLES WHERE table schema IN db na
  • 一文读懂如何配置Linux权限

    配置 var tmp fstab权限 Linux中权限是指控制用户或进程对文件或目录的访问 修改或执行的权限 Linux中有3种权限 读取权限 read 写入权限 write 执行权限 execute 权限是用数字表示的 有三位 每一位表示
  • Unity 控制摄像机镜头的上下左右移动

    private float FollowPosx FollowPosy private float moveAmount 5 控制镜头的移动速度 Update is called once per frame void Update if
  • linux 线程优先级设置

    include
  • 用iframe完美嵌入

  • SpringBoot秒杀系统实战13-秒杀商品详情页+秒杀倒计时功能实现

    程序鹏 于 2020 05 08 00 00 00 发布 1326 收藏 4 分类专栏 Spring Boot 秒杀系统 文章标签 java spring boot web 版权 Spring Boot 同时被 2 个专栏收录 29 篇文章
  • LeetCode【28】实现 strStr()

    题目 实现 strStr 函数 给定一个 haystack 字符串和一个 needle 字符串 在 haystack 字符串中找出 needle 字符串出现的第一个位置 从0开始 如果不存在 则返回 1 示例 1 输入 haystack h
  • 使用Python的requests库发送HTTPS请求时的SSL证书验证问题

    问题描述 使用python的requests库去发送https请求 有时候不设置verify False不报错 有时候又报错 问题原因 使用Python的requests库发送HTTPS请求时 设置verify False参数可以跳过SSL
  • java设置有时效的变量_java设置全局变量

    图片来源于网络 如有侵权请联系删除 前言 java系统自带的api很多 而设置全局变量也是有在System对象中一个具体的方法 而Springboot启动类一层层递进的过程中就有使用该方法来存储全局变量 1 实例 在Springboot中的
  • MAC Unity安装教程

    缘起 这边app要做一个简单调研 验证是否可以利用unity改善app中h5页面需要展示的3d和复杂报表效果 于是就此开始了调研 这边只是想简单将unity集成到工程中 然后想办法嵌入h5来进行展示测试 安装地址 https unity3d
  • Java:字符串中a出现的次数

    1 问题描述 求字符串 abcguegduauwdakolaa 中a出现的次数 2 题解 2 1 题解一 思路 每次返回当前下标 使用indexOf求当前下标的后一位到字符串结束出现的第一个a的下标 String s abcguegduau
  • JS的防抖函数理解、手动封装

    1 认识防抖函数 当事件触发时 相应的函数并不会立即触发 而是会等待一定的时间 当事件密集触发时 函数的触发会被频繁的推迟 只有等待了一段时间也没有事件触发 才会真正的执行响应函数 举个例子 防抖函数的引用场景 输入框中频繁的输入内容 搜索
  • 网站开发之DIV+CSS简单布局网站入门篇(五)

    这篇文章主要介绍如何使用DIV和CSS简单布局一个网站的首页 通常将网站划分为顶部 Logo 导航条 中部 页面主要内容 左右栏目 底部 制作方介绍 超链接 这是非常基础的一篇引入性文章 采用案例的方式进行介绍的 希望对你有所帮助 运行结果
  • elementUI的日期选择器获取选择时间的格式,获取时间戳等

    elementUI的日期选择器获取选择时间的格式 获取时间戳等 在使用日期选择器的时候 我们需要把时间进行格式化 然后再传给后端 比如传时间戳 value format timestamp
  • RAID(磁盘阵列)

    一 RAID的简述 RAID是英文 Redundant Array of Independent Disks 的缩写 翻译成中文是 独立磁盘冗余阵列 简称磁盘阵列 Disk Array 简单的说 RAID是一种把多块独立的硬盘 物理硬盘 按
  • vue 打印文件

    1 下载插件 npm install vue print nb save 2 全局注册 在main js 中引入 打印文件 import Print from vue print nb Vue use Print 3 需要打印部分设置 id
  • 三种通信方式——单工、半双工和双工通信

    数据通常是在两个站 点对点 之间进行传输 按照数据流的方向可分为三种传输模式 单 工 半双工 全双工 一 单工通信 simplex 单工通信只支持信号在一个方向上传输 正向或反向 任何时候不能改变信号的传输方向 为保证正确传送数据信号 接收
  • 锐捷6800 vrrp mstp配置实例

    直接把配置考出来了 大家先看看把 6810A show run System software version 2 42 5 Build Feb 2 2007Rel Building configuration Current config
  • 使用SSH远程连接安卓手机Termux - Android手机服务器

    文章目录 1 安装ssh 2 安装cpolar内网穿透 3 远程ssh连接配置 4 公网远程连接 5 固定远程连接地址 转载自cpolar极点云的文章 公网SSH远程连接Termux 电脑使用安卓Termux 无需公网IP 使用安卓机跑东西
  • 常用算法与设计模式

    时间复杂度 二 计算方法 1 一个算法执行所耗费的时间 从理论上是不能算出来的 必须上机运行测试才能知道 但我们不可能也没有必要对每个算法都上机测试 只需知道哪个算法花费的时间多 哪个算法花费的时间少就可以了 并且一个算法花费的时间与算法中