『案例』十大经典排序算法(含动图,Python实现)

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:

关于稳定性

名词解释

1、冒泡排序

冒泡排序(BubbleSort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像Abandon在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

defbubbleSort(arr):foriinrange(1,len(arr)):forjinrange(0,len(arr)-i):ifarr[j]>arr[j+1]:arr[j],arr[j+1]=arr[j+1],arr[j]returnarr2、选择排序

defselectionSort(arr):foriinrange(len(arr)-1):#记录最小数的索引minIndex=iforjinrange(i+1,len(arr)):ifarr[j]

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

definsertionSort(arr):foriinrange(len(arr)):preIndex=i-1current=arr[i]whilepreIndex>=0andarr[preIndex]>current:arr[preIndex+1]=arr[preIndex]preIndex-=1arr[preIndex+1]=currentreturnarr4、希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

defshellSort(arr):importmathgap=1while(gap0:foriinrange(gap,len(arr)):temp=arr[i]j=i-gapwhilej>=0andarr[j]>temp:arr[j+gap]=arr[j]j-=gaparr[j+gap]=tempgap=math.floor(gap/3)returnarr5、归并排序

归并排序(Mergesort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(DivideandConquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

defmergeSort(arr):importmathif(len(arr)<2):returnarrmiddle=math.floor(len(arr)/2)left,right=arr[0:middle],arr[middle:]returnmerge(mergeSort(left),mergeSort(right))defmerge(left,right):result=[]whileleftandright:ifleft[0]<=right[0]:result.append(left.pop(0));else:result.append(right.pop(0));whileleft:result.append(left.pop(0));whileright:result.append(right.pop(0));returnresult6、快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个项目要Ο(nlogn)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(nlogn)算法更快,因为它的内部循环(innerloop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divideandconquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

①从数列中挑出一个元素,称为“基准”(pivot);

②重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

③递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

defquickSort(arr,left=None,right=None):left=0ifnotisinstance(left,(int,float))elseleftright=len(arr)-1ifnotisinstance(right,(int,float))elserightifleft

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

defbuildMaxHeap(arr):importmathforiinrange(math.floor(len(arr)/2),-1,-1):heapify(arr,i)defheapify(arr,i):left=2*i+1right=2*i+2largest=iifleftarr[largest]:largest=leftifrightarr[largest]:largest=rightiflargest!=i:swap(arr,i,largest)heapify(arr,largest)defswap(arr,i,j):arr[i],arr[j]=arr[j],arr[i]defheapSort(arr):globalarrLenarrLen=len(arr)buildMaxHeap(arr)foriinrange(len(arr)-1,0,-1):swap(arr,0,i)arrLen-=1heapify(arr,0)returnarr8、计数排序

defcountingSort(arr,maxValue):bucketLen=maxValue+1bucket=[0]*bucketLensortedIndex=0arrLen=len(arr)foriinrange(arrLen):ifnotbucket[arr[i]]:bucket[arr[i]]=0bucket[arr[i]]+=1forjinrange(bucketLen):whilebucket[j]>0:arr[sortedIndex]=jsortedIndex+=1bucket[j]-=1returnarr9、桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

当输入的数据可以均匀的分配到每一个桶中。

当输入的数据被分配到了同一个桶中。

defbucket_sort(s):桶排序min_num=min(s)max_num=max(s)#桶的大小bucket_range=(max_num-min_num)/len(s)#桶数组count_list=[[]foriinrange(len(s)+1)]#向桶数组填数foriins:count_list[int((i-min_num)//bucket_range)].append(i)s.clear()#回填,这里桶内部排序直接调用了sortedforiincount_list:forjinsorted(i):s.append(j)if__name__==__main__:a=[3.2,6,8,4,2,6,7,3]bucket_sort(a)print(a)#[2,3,3.2,4,6,6,7,8]10、基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

THE END
1.递归,搜索,和回溯算法腾讯云开发者社区大家也看到了,我们这个算法篇章的开头就比较长,这主要是因为他们三者关系紧密。 一、什么是递归: 我们在学习C语言和数据结构二叉树部分是就接触了大量的递归。 递归:简单来说就是自己调用自己 。 二、为什么要用到递归 我们先来简单的介绍一下三个用到递归的算法例子,来看看他们有什么共同点 https://cloud.tencent.com/developer/article/2477481
2.经典例题向Java程序员诠释递归,这样的算法公式你们必须懂可以看到,递归写法简单优美,省去考虑很多边界条件的时间。当然,递归算法会保存很多的临时数据,类似于堆栈的过程,如果栈深太深,就会造成内存用尽,程序崩溃的现象。Java为每个线程分配了栈大小,如果栈大小溢出,就会报错,这时候还是选择递推好一点。 观察下面的执行过程也会发现,本程序并没有保存每次的运算结果,第三行的https://maimai.cn/article/detail?fid=1699998565&efid=XrVS821Hh8PpVU3XNWFdPQ
3.java递归是什么意思,怎么用【3】递归算法代码显得很简洁,但递归算法解题的运行效率较低。所以不提倡用递归设计程序。 【4】在递归调用的过程中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。 【5】在做递归算法的时候,一定把握出口,也就是做递归算法必须要有一个明确的递https://www.itcast.cn/news/20190718/17592586079.shtml
4.算法笔记递归的理解3. 存在递归终止条件 把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。 总之:写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 递归代码要警惕堆栈溢出 为什么递归代https://zhuanlan.zhihu.com/p/603180087
5.填充算法(一)递归填充算法51CTO博客(4)注: 上述两个算法实现理论上是没有错误的,但是在实际环境下,却会运行中断,具体原因是因为当递归深度过深时,栈溢出;而由递归深度引起的栈溢出(Stack overflow)是无解的(递归深度无法确定,栈大小修改不便),除非不用递归算法。附:修改工程栈大小: https://blog.51cto.com/u_7174271/6725361
6.什么叫递归,递归有哪些优缺点?递归的优缺点什么叫递归,递归有哪些优缺点? 在Web前端开发中,递归是一种算法或函数调用自身的过程。简而言之,递归是通过将问题分解为更小的子问题来解决问题的方法。递归在前端开发中常用于处理具有嵌套结构的数据,如树或多级列表。 让我们详细讨论递归的优点和缺点:https://blog.csdn.net/zy1992As/article/details/131680404
7.牟春花算法物化集体法律责任与分布式法律责任:规制算法的三重算法是什么? 学界对算法的定义有狭义、中义和广义三种。狭义的算法指的是“一组逐步执行的指令,通过机械的方式实施,以之达到某种预期的效果”。狭义的算法基本上局限在数学递归和数值计算范围内。随着现代计算机技术的出现,算法逐渐从纯粹数学迭代和递归计算转向计算机硬件物化和代码程序编程技术支持,出现了中观意义上的算https://www.jfdaily.com/sgh/detail?id=924830
8.时间复杂度分析,这个很多人都不知道,更别谈会了!递归算法的时间复杂度又该如何计算? 很多算法都是基于递归思想的,我们分析这些递归算法,可以得到关于时间复杂度的递归关系式。比如「归并排序」的时间复杂度一般表示为 ,还有二分查找,汉诺塔问题等等,但是关于递归的时间复杂度并不简单。 对于递归的时间复杂度的计算主要有三种方式:https://www.scholat.com/teamwork/showPostMessage.html?id=9254
9.数据结构与算法思维导图算法: 递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法 1 算法的复杂度 1.1大O复杂度表示法 公式: 1 T(n)表示代码执行的时间; n表示数据规模的大小; f(n) 表示每行代码执行的次数总和。因为这是一个公式, 所以用f(n)来表示。公式中的O,表示代码的执行时间https://www.jianshu.com/p/b2761d11aa2b