前言
面试中被问到了一个题目(http://www.voidcn.com/blog/u010943214/article/p-3808842.html),然后重温了一遍堆排序。
问题:
给你一个双向链表,有序输出,
限制:
空间复杂度O1,
时间复杂度nlogn,最坏不能退化n2
思路:
1 根据双向链表构建一个二叉树
2 对二叉树进行堆排序
堆的定义
定义:堆如果用一个二叉树表示的话,那应该是具有以下特性的二叉树
a 树的形状要求 -- 这颗二叉树是基本完备的(或者简称完全二叉树),这意味着树的每一层都是满的,最后一层最右边的孩子节点有可能空缺
b 父母优势要求 -- 键的要求 ,父母的键必须大于等于所有子树节点(对于任何叶子节点认为是自动满足的)
堆的特性
1 树的根总是包含了最大元素
2 分形特性, 堆的一个节点以及该节点的子孙也是一个堆
3 可以用数组实现一个堆,方法是从上到下,从左到右记录堆的元素,从1...n的位置记录n个元素,在这种表示法中,
a 父母节点的键将位于前 n/2的位置上,叶子节点占据后 n/2个位置
b 对于一个位于位置i的节点, 它的子女将会位于2i 和 2i+1上,对于一个位于位置i的键来说,它的子女将会位于i/2上。
如何构造一个堆
构造堆的过程叫做堆化,这个过程的输入是一个无序数组,输出是一个满足堆特性的数组。
这个过程是从最后一个父母开始,直到堆根为止,检查这个节点是否满足堆的父母优势,如果不满足,交换父母和其中一个子女节点,在新的子女节点位置上,同样检查父母优势,直到满足了父母优势 或者 节点到达了叶子节点
上树过程有两个直到,就是两个循环。
堆化的算法如下
void HeapfyBottomeUp(int a[], int len) { for(int i = len/2; i >= 1; i--) { int k = i; int temp = a[i]; bool isHeap = false; while(!isHeap && 2*k<=len) { int j = 2*k; if( j+1 <= len && H[j] < H[j+1]) j++; if(temp >= H[j]) isHeap = true; else { H[k] = H[j]; k = j; } } H[k] = temp ; } }
构造一个堆的时间复杂度
堆化可以在O(n)时间复杂度内完成。
具体思路如下,第i层有2的i次个元素(i从0计数), 那最坏情况下,第i层需要对比2(h-i)次,每一个对比都要从当前位置到叶子节点, h 是数的深度。
具体推导如下:
如何从堆中删除最大的键值
1 把堆根和最后一个叶子节点交换
2 堆的数量-1
3 剩余的堆进行堆化的过程。
时间复杂度 O(logn)
堆排序
再来介绍堆排序就简单了
堆排序本质上是说,不断的对堆根实施删除,堆化的过程,直到堆的大小变成1 停止,时间复杂度是 O(nlogn)
算法:
1 构造一个规模是n的堆
2 对这个堆 执行 n-1 次 堆根删除操作。
时间复杂度对比
和 合并排序(归并排序)有同样的时间复杂度,堆排序是在位的,不需要额外的空间, 针对随机文件的计时试验指出(无参考,可以自行对比),堆排序比快排运行的慢,但是和 合并排序有竞争力。
堆排序使用场景
面试题 笔试题。。。。