堆排序和归并排序

来源:互联网 发布:cpda数据分析师 编辑:程序博客网 时间:2024/05/17 04:03

一、堆排序
1.1 简介
  堆排序与快速排序,归并排序一样都是时间复杂度为O(n*logn)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的堆。
  堆的定义:n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆。其中i=1,2,…,n/2向下取整。
  情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小顶堆)
  情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大顶堆)
概括来说就是,堆是具有下列性质的完全二叉树:
  每个分支节点的值都大于或等于其左右孩子的值,称为大顶堆;
  每个分支节点的值都小于或等于其做右孩子的值,称为小顶堆;
因此,其根节点一定是所有节点中最大(最小)的值。
  下图即为最大堆和最小堆的实例:

这里写图片描述

  下面要讲解的堆排序(Heap Sort)就是利用大顶堆或小顶堆的性质进行排序的方法。其核心思想是:将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆的根节点。将它与堆数组的末尾元素交换,然后将剩余的n-1个序列重新构造成一个大顶堆。反复执行前面的操作,最后获得一个有序序列。
  因此由上可以发现实现堆排序需要解决以下两个问题:
  (1)如何将n 个待排序的数建成堆;
  (2)输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。
1.2 实现堆排序的细节
1、对n 个元素初始建堆的过程
  建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
  1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。
  2)筛选从第个结点为根的子树开始,该子树成为堆。
  3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
  如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)

这里写图片描述

2、调整小顶堆的方法
  1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
  2)将根结点与左、右子树中较小元素的进行交换。
  3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
  4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
  5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
  具体过程如下图实例所示:

这里写图片描述

1.3 python实现的代码
  初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个 堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

import randomimport math#随机生成0~100之间的数值def get_andomNumber(num):      lists=[]      i=0      while i<num:          lists.append(random.randint(0,100))          i+=1    return lists# 调整堆def adjust_heap(lists, i, size):    lchild = 2 * i + 1  #i节点的左孩子    rchild = 2 * i + 2   #i节点的左孩子    max = i    if i < size / 2:        if lchild < size and lists[lchild] > lists[max]:  //i节点跟其左孩子比较大小,保留最大项            max = lchild        if rchild < size and lists[rchild] > lists[max]:  //i节点跟其左孩子比较大小,保留最大项            max = rchild        if max != i:            lists[max], lists[i] = lists[i], lists[max] //找到最大项后,将其与i节点进行交换            adjust_heap(lists, max, size)   //递归构建最大堆# 创建堆def build_heap(lists, size):    for i in range(0, (int(size/2)))[::-1]:  //i是拥有孩子的节点        adjust_heap(lists, i, size)  //调用调整堆函数构建最大堆# 堆排序def heap_sort(lists):    size = len(lists)    build_heap(lists, size)   //构建最大堆    for i in range(0, size)[::-1]:   //[::-1]:翻转i的列表,即i是从size-1开始,一次开始递减。        lists[0], lists[i] = lists[i], lists[0]   //将堆顶元素(最大值)放入列表,然后重新构建最大堆        adjust_heap(lists, 0, i)         return listsa = get_andomNumber(10)print("排序之前:%s" %a)b = heap_sort(a)print("排序之后:%s" %b)

1.4 总结
  堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。
  堆排序在最坏的情况下,其时间复杂度也为O(nlogn),这显然好于冒泡、简单选择和直接插入排序的O(n2)的时间复杂度。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。

二、归并排序
2.1 简介
  归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。它是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
  归并的实例如下图所示:

这里写图片描述

  归并的过程如下:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
2.2 python实现的代码

def mergesort(seq):      if len(seq)<=1:          return seq      mid=int(len(seq)/2)      left=mergesort(seq[:mid])      right=mergesort(seq[mid:])      return merge(left,right)  def merge(left,right):      result=[]      i,j=0,0      while i<len(left) and j<len(right):          if left[i]<=right[j]:              result.append(left[i])              i+=1          else:              result.append(right[j])              j+=1      result+=left[i:]      result+=right[j:]      return result  if __name__=='__main__':      seq=[4,5,7,9,7,5,1,0,7,-2,3,-99,6]  print(mergesort(seq)) 

2.3 总结
  (1)归并排序对原始序列元素分布情况不敏感,其时间复杂度为O(nlogn)。
  (2)归并排序在计算过程中需要使用一定的辅助空间,用于递归和存放结果,因此其空间复杂度为O(n+logn)。
  (3)归并排序中不存在跳跃,只有两两比较,因此是一种稳定排序。
  总之,归并排序是一种比较占用内存,但效率高,并且稳定的算法。

原创粉丝点击