算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法表明着用系统的方法描述解决问题的策略机制。也就是说,可以对必定规范的输入,在有限时间内得到所要求的输出。若是一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不一样的算法可能用不一样的时间、空间或效率来完成一样的任务。一个算法的优劣能够用空间复杂度与时间复杂度来衡量。html
一个算法应该具备如下七个重要的特征:node
①有穷性(Finiteness):算法的有穷性是指算法必须能在执行有限个步骤以后终止;git
②确切性(Definiteness):算法的每一步骤必须有确切的定义;算法
③输入项(Input):一个算法有0个或多个输入,以刻画运算对象的初始状况,所谓0个输 入是指算法自己定出了初始条件;shell
④输出项(Output):一个算法有一个或多个输出,以反映对输入数据加工后的结果。没 有输出的算法是毫无心义的;数据库
⑤可行性(Effectiveness):算法中执行的任何计算步骤都是能够被分解为基本的可执行 的操做步,即每一个计算步均可以在有限时间内完成(也称之为有效性);编程
⑥高效性(High efficiency):执行速度快,占用资源少;数组
⑦健壮性(Robustness):对数据响应正确。数据结构
计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间,时间复杂度经常使用大O符号(大O符号(Big O notation)是用于描述函数渐进行为的数学符号。更确切地说,它是用另外一个(一般更简单的)函数来描述一个函数数量级的渐近上界。在数学中,它通常用来刻画被截断的无穷级数尤为是渐近级数的剩余项;在计算机科学中,它在分析算法复杂性的方面很是有用。)表述,使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的状况。app
大O,简而言之能够认为它的含义是“order of”(大约是)。
无穷大渐近
大O符号在分析算法效率的时候很是有用。举个例子,解决一个规模为 n 的问题所花费的时间(或者所需步骤的数目)能够被求得:T(n) = 4n^2 - 2n + 2。
当 n 增大时,n^2; 项将开始占主导地位,而其余各项能够被忽略——举例说明:当 n = 500,4n^2; 项是 2n 项的1000倍大,所以在大多数场合下,省略后者对表达式的值的影响将是能够忽略不计的。
数学表示扫盲贴 http://www.cnblogs.com/alex3714/articles/5910253.html
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 } }
定义:若是一个问题的规模是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的值很是小,由于,在 这个问题中增长一个元素就致使运行时间加倍。不幸的是,确实有许多问题 (如著名的“巡回售货员问题” ),到目前为止找到的算法都是指数的。若是咱们真的遇到这种状况,一般应该用寻找近似最佳结果的算法替代之。 |
名称 |
复杂度 |
说明 |
备注 |
冒泡排序 |
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. |
|
箱排序 |
O(n) |
设置若干个箱子,把关键字等于 k 的记录全都装入到第k 个箱子里 ( 分配 ) ,而后按序号依次将各非空的箱子首尾链接起来 ( 收集 ) 。 |
分配排序的一种:经过" 分配 " 和 " 收集 " 过程来实现排序。 |
data_set = [ 9,1,22,31,45,3,6,2,11 ] loop_count = 0 for j in range(len(data_set)): for i in range(len(data_set) - j- 1): # -1 是由于每次比对的都 是i 与i +1,不减1的话,最后一次对比会超出list
#获取范围,-j是由于,每一次大loop就表明排序好了一个最大值,放在了列表最后面,下次loop就不用再运算已经排序好了的值 了 if data_set[i] > data_set[i+1]: #switch tmp = data_set[i] data_set[i] = data_set[i+1] data_set[i+1] = tmp loop_count +=1 print(data_set) print(data_set) print("loop times", loop_count)
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,
data_set = [ 9,1,22,31,45,3,6,2,11 ] smallest_num_index = 0 #初始列表最小值,默认为第一个 loop_count = 0 for j in range(len(data_set)): for i in range(j,len(data_set)): if data_set[i] < data_set[smallest_num_index]: #当前值 比以前选出来的最小值 还要小,那就把它换成最小值 smallest_num_index = i loop_count +=1 else: print("smallest num is ",data_set[smallest_num_index]) tmp = data_set[smallest_num_index] data_set[smallest_num_index] = data_set[j] data_set[j] = tmp print(data_set) print("loop times", loop_count)
The worst-case runtime complexity is O(n2).
插入排序(Insertion Sort)的基本思想是:将列表分为2部分,左边为排序好的部分,右边为未排序的部分,循环整个列表,每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到所有记录插入完成为止。
插入排序很是相似于整扑克牌。
在开始摸牌时,左手是空的,牌面朝下放在桌上。接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌的正确位置,要将它与手中已有的牌从右到左地进行比较。不管何时,左手中的牌都是排好序的。
也许你没有意识到,但其实你的思考过程是这样的:如今抓到一张7,把它和手里的牌从右到左依次比较,7比10小,应该再往左插,7比5大,好,就插这里。为何比较了10和5就能够肯定7的位置?为何不用再比较左边的4和2呢?由于这里有一个重要的前提:手里的牌已是排好序的。如今我插了7以后,手里的牌仍然是排好序的,下次再抓到的牌还能够用这个方法插入。编程对一个数组进行插入排序也是一样道理,但和插入扑克牌有一点不一样,不可能在两个相邻的存储单元之间再插入一个单元,所以要将插入点以后的数据依次日后移动一个单元。
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] '''
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(一般选用数组的第一个数)做为关键数据,而后将全部比它小的数都放到它前面,全部比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变更
注:在待排序的文件中,若存在多个关键字相同的记录,通过排序后这些具备相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具备相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
要注意的是,排序算法的稳定性是针对全部输入实例而言的。即在全部可能的输入实例中,只要有一个实例使得算法不知足稳定性要求,则该排序算法就是不稳定的。
排序演示
示例
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
6
|
2
|
7
|
3
|
8
|
9
|
下标
|
0
|
1
|
2
|
3 |
4
|
5
|
数据
|
3
|
2
|
7
|
6
|
8
|
9
|
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
3
|
2
|
6
|
7
|
8
|
9
|
下标
|
0
|
1
|
2
|
3
|
4
|
5
|
数据
|
3
|
2
|
6
|
7
|
8
|
9
|
#_*_coding:utf-8_*_ __author__ = 'Alex Li' 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、一、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
如何判断一棵树是彻底二叉树?按照定义,
教材上的说法:一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树。这个概念很好理解,
就是一棵树,深度为k,而且没有空位。
首先对满二叉树按照广度优先遍历(从左到右)的顺序进行编号。
一颗深度为k二叉树,有n个节点,而后,也对这棵树进行编号,若是全部的编号都和满二叉树对应,那么这棵树是彻底二叉树。
class TreeNode(object): def __init__(self,data=0,left=0,right=0): self.data = data self.left = left self.right = right class BTree(object): def __init__(self,root=0): self.root = root def preOrder(self,treenode): if treenode is 0: return print(treenode.data) self.preOrder(treenode.left) self.preOrder(treenode.right) def inOrder(self,treenode): if treenode is 0: return self.inOrder(treenode.left) print(treenode.data) self.inOrder(treenode.right) def postOrder(self,treenode): if treenode is 0: return self.postOrder(treenode.left) self.postOrder(treenode.right) print(treenode.data) if __name__ == '__main__': n1 = TreeNode(data=1) n2 = TreeNode(2,n1,0) n3 = TreeNode(3) n4 = TreeNode(4) n5 = TreeNode(5,n3,n4) n6 = TreeNode(6,n2,n5) n7 = TreeNode(7,n6,0) n8 = TreeNode(8) root = TreeNode('root',n7,n8) bt = BTree(root) print("preOrder".center(50,'-')) print(bt.preOrder(bt.root)) print("inOrder".center(50,'-')) print (bt.inOrder(bt.root)) print("postOrder".center(50,'-')) print (bt.postOrder(bt.root))
堆排序,顾名思义,就是基于堆。所以先来介绍一下堆的概念。
堆分为最大堆和最小堆,其实就是彻底二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,二者对左右孩子的大小关系不作任何要求,其实很好理解。有了上面的定义,咱们能够得知,处于最大堆的根节点的元素必定是这个堆中的最大值。其实咱们的堆排序算法就是抓住了堆的这一特色,每次都取堆顶的元素,将其放在序列最后面,而后将剩余的元素从新调整为最大堆,依次类推,最终获得排序的序列。
堆排序就是把堆顶的最大数取出,
将剩余的堆继续调整为最大堆,具体过程在第二块有介绍,以递归实现
剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束
#_*_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()
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本,该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,而后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。由于直接插入排序在元素基本有序的状况下(接近最好状况),效率是很高的,所以希尔排序在时间效率比直接插入排序有较大提升
首先要明确一下增量的取法:
第一次增量的取法为: d=count/2;
第二次增量的取法为: d=(count/2)/2;
最后一直到: d=1;
看上图观测的现象为:
d=3时:将40跟50比,因50大,不交换。
将20跟30比,因30大,不交换。
将80跟60比,因60小,交换。
d=2时:将40跟60比,不交换,拿60跟30比交换,此时交换后的30又比前面的40小,又要将40和30交换,如上图。
将20跟50比,不交换,继续将50跟80比,不交换。
d=1时:这时就是前面讲的插入排序了,不过此时的序列已经差很少有序了,因此给插入排序带来了很大的性能提升。