当前位置: 首页 > 科技观察

Go语言算法之美——高级排序

时间:2023-03-14 14:30:39 科技观察

本文转载自微信公众号《roseduan写的地方》,作者roseduan。转载本文请联系roseduan写公众号的地方。在本文中,我们就来看看在实践中比较常用也比较复杂的几种排序算法,即希尔排序、堆排序、快速排序和归并排序。1.希尔排序希尔排序其实是插入排序的一种优化。回想一下插入排序的过程是:将数据分为已排序区间和未排序区间,依次遍历未排序区间的值,插入到已排序区间的合适位置。插入排序最大的缺点之一是:一次只能移动一位,在某些极端情况下效率会很低;比如数据是235790,如果把0移动到元素的头部,就需要遍历整个数组。这就是希尔排序的优化点。它的核心思想是将数据中的元素分成多个组,每个组分别插入和排序。举个简单的例子:有数据3533421014192744,先将数据分成四组,以其长度的1/2(即4)为步长,分别为{35,14},{33,19},{42,27},{10,44}。然后对每一组分别插入排序,排序后的结果如下:然后步长减半为2,将数组分成两组,分别为{14,27,35,42},{19,10,33,44}:然后分别对这两组进行插入排序,结果为1410271935334244。最后步长减半为1,数组分为一组(在实际上是数组本身),然后进行插入排序,这样就完成了希尔排序的过程。可以看出,希尔排序将数组分成了多个组。其实就是尽可能让数据在本地有序。代码如下:funcShellSort(data[]int){length:=len(data)step:=length/2forstep>=1{fori:=0;istep-1&&data[j-step]>k;j-=step{data[j]=data[j-step]}data[j]=k}step/=2}}Hill排序的实际应用不多,其相关复杂度如下:TimeComplexityBestO(nlogn)WorstO(n2)AverageO(nlogn)SpaceComplexityO(1)Stabilityno2.Heapsorting要理解堆排序,首先要理解什么是二叉堆。二叉堆(以下简称堆)是一种非常优雅的数据结构。它是一种特殊的二叉树。满足二叉树的两个特点,就可以称为堆:是完全二叉树。堆中任意节点的值都必须大于一个等于(或小于等于)其子树中所有节点值的堆,对于大于等于其子树中节点值的节点,称为大顶堆子树,反之称为小顶堆。下面是堆的两个例子:从定义和上面的图中可以看出,堆的一个特点是堆顶元素是堆中最大(或最小)的元素。堆实际上可以使用数组存储。堆顶元素是数组的第一个元素,对于任意一个下标为i的节点,其左子节点为2*i+1,其右子节点为2*i+2。有了这样的对应关系,堆在数组中的存储是这样的:了解了什么是堆之后,我们进入正题,看看如何实现基于堆的排序。堆排序一般有两个步骤,即建堆和排序,下面依次介绍。构造堆构造堆是指将无序数组构造成堆(这里用大顶堆来解释),使其符合堆的特性。举个例子,对于一个完全无序的数组,它的原始状态和存储结构如下图所示:要让它成为一个大顶堆,我们可以这样做:从第一个非叶子节点开始,比较它的值依次对子节点进行比较,如果小于子节点的值,则交换节点的顺序,然后依次向下比较,直到叶子节点。这样就可以一直满足堆的特性,任何一个结点的值总是大于它的子树中所有结点的值。排序堆建好后,进行排序。前面说过,堆有一个很重要的特点,就是堆顶元素是最大的元素。我们遍历数组的长度,每次取堆顶元素(下标为0的那个元素),和数组的最后一个元素交换,然后把剩下的数据重新整理成堆,继续取堆顶的最大元素,等等。结合两步就是堆排序的完整实现,代码如下://堆排序funcHeapSort(data[]int){//buildheaplength:=len(data)fori:=(length-2)/2;i>=0;i--{heapify(data,length,i)}//对length>0{length--data[length],data[0]=data[0],data[length]heapify排序(数据,长度,0)}}funcheapify(数据[]int,大小,iint){for{max:=iif2*i+1data[max]{max=2*i+1}if2*i+2data[max]{max=2*i+2}if??i==max{break}data[i],data[max]=data[max],data[i]i=max}}相对复杂度如下:时间复杂度BestO(nlogn)WorstO(nlogn)AverageO(nlogn)SpaceComplexityO(1)StabilityNoMergesort归并排序基于分而治之的思想。分而治之,顾名思义,就是分而治之。这是一种解决问题的方法。它将原问题分解为多个相同或相似的子问题,然后对子问题进行求解,并将得到的子问题的解进行合并。问题可以解决。分而治之的思想是很多复杂算法的基础,比如归并排序、快速排序、二分查找等等。言归正传,让我们看看归并排序。它的概念很容易理解。如果我们要对一组数据进行排序,可以将这个数组分成两个子数组,然后对子数组进行分组。对子数组排序后,将结果合并,即可得到对原始数据进行排序的结果。下图展示了一个问题分解成多个子问题的过程:子问题解决后,需要合并结果。合并的过程如下图所示:代码实现如下://mergesortfuncMergeSort(data[]int){mergeSortHelper(data,0,len(data)-1)}funcmergeSortHelper(data[]int,lo,hiint){iflo