摘自书本:
这一部分将要给出几个解决以下排序问题的算法:
输入:n 个数的序列( a1,a2,……,an )。
输出:输入序列的一个重排 (a1’,a2’,……,an’ ),使 a1’ <= a2' <= …… <= an' 。
输入序列通常是一个 n 元 数组,但也可能由其他形式来表示,如链表。
输入数据的结构
在实际中,待排序的数很少是孤立的值,他们通常是一个称为 记录 的数据集的一部分。每个记录有一个关键字 key,它是待排序的值。记录的其他数据称为 卫星数据,即他们通常以 key 为中心传送。在一个排序的算法中,当交换关键字时,卫星数据必也交换。如果记录都很大,我们可以交换一组指向各个记录的指针而不是记录本身,以求将数据移动量减到最小。
在一定的意义上,正是这些实现细节才使得一个完整的程序不同于算法。不管我们要排序的是单个的数值还是包含数值的大型记录,就排序的方法来说他们都是一样的。因而,为了集中考虑排序问题,我们一般都假设输入仅由数值构成。将对数字的排序算法转换为对记录排序的程序是很直接的。当然,在具体的工程条件下,实际的程序设计可能还会遇到其他难以捉摸的挑战。
堆排序
特点:运行时间 为 O(n lgn),原地排序算法。
(二叉)堆数据结构 是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点值得那个元素对应。树的每一层都是填满的,最后一层可能除外(最后一层从一个结点的左子树开始填)。表示堆的 数组 A 是一个具有两个属性的对象:Length[A]是数组中的元素个数,heap-size[A] 是存放在A中的堆的元素个数。heap-size[A] <= Length[A]。树的根为A[1],给定了某个结点的下标
i,其父节点为 PARENT(i)、左儿子 LEFT(i)和右儿子 RIGHT(i)的下表可以简单地计算出来:
PARENT(i): i/2(向前取整)
LEFT(i):2*i
RIGHT(i):2*i + 1
/* input:一个数组A 和一个下标 i
当 Max-Heapify被调用时,我们假定以 LEFT(i) 和 RIGHT(i)为根的两颗二叉树都是最大堆
但这时 A【i】可能小于其子女,这样就违反了最大堆的性质,Max-Heapify让A【i】在最大堆
中下降,以满足最大堆性质*/
Max-Heapify( A, i ) 堆调整 O(lgn)
1 l = LEFT( i )
2 r = RIGHT( i )
3 if l <= heap-size[A] and A[l] > A[i]
4 then largest = l
5 else largest = i
6 if r <= heap-size[A] and A[r] > A[largest]
7 then largest = r
8 if largest != i
9 then exchange A[i] <-> A[largest]
10 Max-Heapify( A, largest )
当 Max-Heapify被调用时,我们假定以 LEFT(i) 和 RIGHT(i)为根的两颗二叉树都是最大堆
但这时 A【i】可能小于其子女,这样就违反了最大堆的性质,Max-Heapify让A【i】在最大堆
中下降,以满足最大堆性质*/
Max-Heapify( A, i ) 堆调整 O(lgn)
1 l = LEFT( i )
2 r = RIGHT( i )
3 if l <= heap-size[A] and A[l] > A[i]
4 then largest = l
5 else largest = i
6 if r <= heap-size[A] and A[r] > A[largest]
7 then largest = r
8 if largest != i
9 then exchange A[i] <-> A[largest]
10 Max-Heapify( A, largest )
建堆:子数组A[((n/2向前取整)+ 1).... n ] 中的元素都是树中的叶子,因此每个都可以看做只含一个根元素的堆,故而建堆的过程,可以从 A[ length[A]/2 ] 开始调整 直至 A[1] 。
BUILD-MAX-HEAP( A ) 建堆 运行时间的界:O(n)
1 heap-size[A] = length[A]
2 for i = length[A]/2 downto 1
3 do Max-Heapify(A,i)
2 for i = length[A]/2 downto 1
3 do Max-Heapify(A,i)
堆排序
Heap-Sort( A ) 堆排序 时间代价:O(n lgn)
1 BUILD-MAX-HEAP( A )
2 for i = length[A] downto 2
3 do exchange A[1] = A[i]
heap-size[A] = heap-size[A] - 1
Max-Heapify( A, 1 )
1 BUILD-MAX-HEAP( A )
2 for i = length[A] downto 2
3 do exchange A[1] = A[i]
heap-size[A] = heap-size[A] - 1
Max-Heapify( A, 1 )
c 语言实现:
#include<stdio.h>
#include<stdlib.h>
int HEAP_SIZE;
//注:本代码内,数组有效值都从 1 开始
int Length( int A[] )
{
// printf("%d\n",sizeof(A)/sizeof(int) - 1);
// return sizeof(A)/sizeof(int) - 1;
return 5;
}
void Exchange( int *A, int i, int j)
{
int temp;
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
void Max_Heapify(int *A, int i )
{
int l = 2 * i;
int r = 2 * i + 1;
int largest;
if( l <= HEAP_SIZE && A[l] > A[i] ) largest = l;
else largest = i;
if( r <= HEAP_SIZE && A[r] > A[largest] ) largest = r;
if( largest != i )
{
Exchange( A, i, largest );
Max_Heapify( A, largest );
}
}
void Build_Max_Heap( int A[] )
{
int i;
HEAP_SIZE = Length( A );
for( i = Length(A)/2; i >= 1; i-- )
Max_Heapify( A, i);
}
void Heap_Sort( int *A )
{
int i;
Build_Max_Heap( A );
for( i = Length(A); i >= 2; i-- )
{
Exchange( A, 1, i );
HEAP_SIZE--;
// printf("%d\t",HEAP_SIZE);
Max_Heapify( A, 1);
}
}
void main()
{
int A[] = {100,5,4,3,6,1};
int i;
Heap_Sort(A);
for(i = 1; i < 6; i++)
printf("%d\t",A[i]);
printf("\n");
}
优先级队列
虽然堆排序算法是一个很漂亮的算法,但在实际中,快速排序的一个好的实现往往优于堆排序。尽管这样,对数据结构还是有着很大的用处。
一个堆的很常见的应用:作为高效的优先级队列(priority queue)
如堆一样,队列也有两种,最大优先级队列和最小优先级队列。这里集中讨论 基于最大堆实现的最大优先级队列。
优先级队列是一种用来维护由一组元素构成的集合S的数据结构,这一组元素的每一个都有一个关键字key。
最大优先级队列的一个应用: 在一台分时计算机上进行作业调度。这种队列调度对要执行的各作业及它们之间的相对优先关系加以记录。当一个作业做完或被中断时,用 EXTRACT-MAX 操作从所有等待的作业中,选择出具有最高优先级的作业。在任何时候,一个新作业都可以用
INSERT 加入到队列中去。
支持以下操作:
INSERT( S, x ) //把元素 x 插入到集合 S
MAXIMUM( S ) //返回 S 中具有最大关键字的元素
EXTRACT-MAX( S ) //去掉并返回 S 中具有最大关键字的元素
INCREASE-KEY( S, x, K ) //将元素 x 的关键字的值增加到 k,这里 k 值不能小于 x 的原关键字的值
MAXIMUM( S ) //返回 S 中具有最大关键字的元素
EXTRACT-MAX( S ) //去掉并返回 S 中具有最大关键字的元素
INCREASE-KEY( S, x, K ) //将元素 x 的关键字的值增加到 k,这里 k 值不能小于 x 的原关键字的值
伪代码,最大堆实现最大优先级队列
HEAP-MAXIMUM( A ) O(1)
1 return A[1]
HEAP-EXTRACT-MAX( A ) O( lgn ) 原堆中去除堆顶最大值,并返回堆顶最大值
1 if heap-size[A] < 1
2 then error "heap underflow"
3 max = A[1]
4 A[1] = A[heap-size[A]]
5 heap-size[A] = heap-size[A] - 1
6 MAX-HEAPIFY(A,1)
7 return max
1 if heap-size[A] < 1
2 then error "heap underflow"
3 max = A[1]
4 A[1] = A[heap-size[A]]
5 heap-size[A] = heap-size[A] - 1
6 MAX-HEAPIFY(A,1)
7 return max
HEAP-INCREASE-KEY( A, i, key ) O(lgn)
1 if key < A[i]
2 then error "new key is smaller than current key"
3 A[i] = key
4 while i > 1 and A[PARENT(i)] < A[i]
5 do exchange A[i] = A[PARENT(i)]
6 i = PARENT( i )
1 if key < A[i]
2 then error "new key is smaller than current key"
3 A[i] = key
4 while i > 1 and A[PARENT(i)] < A[i]
5 do exchange A[i] = A[PARENT(i)]
6 i = PARENT( i )
MAX-HEAP-INSERT( A, key ) O(lgn)
1 heap-size[A] = heap-size[A] + 1
2 A[heap-size[A]] = -∞
3 HEAP-INCREASE-KEY( A, heap-size[A], key )
1 heap-size[A] = heap-size[A] + 1
2 A[heap-size[A]] = -∞
3 HEAP-INCREASE-KEY( A, heap-size[A], key )
代码实现类似于 堆排序算法,再次不做实现。