算法导论笔记:06堆排序

       满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点(也可以这样理解,除叶子节点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上)

 

       1:堆排序的时间复杂度为O(nlgn)。具有空间原址性的特点,也就是任意时刻只需常数个额外元素空间存储临时数据。

 

       2:堆是一颗近似完全二叉树:除了最底层外,该树是完全充满的,最底层是从左向右填充的。

 

       3:堆可以用数组存储,对于元素下标i,可以很快得出它的父节点,左孩子,右孩子的下标(i从0开始):

PARENT(i) = (i-1)/2   

LEFT(i) = 2i + 1    

RIGHT(i) = 2i + 2    

 

       一般而言,上述计算父母,左孩子,右孩子索引的函数都是通过宏或者内联函数实现

 

       4:堆有两种:最大堆和最小堆。

       最大堆是:除了根节点之外,所有节点i都要满足:A[PARENT(i)] >= A[i]。即某个节点的值最多与父节点一样大。因此,最大堆中的最大元素放在根节点中,任意一个子树中所包含的所有节点的值都小于等于子树根节点的值。如果是从小到大排序,一般用最大堆。

       最小堆是:除了根节点之外,所有节点i都要满足:A[PARENT(i)] <= A[i]。最小堆的最小值放在根节点中。如果是从大到小排序,用最小堆。

 

       5:一个包含n个元素的堆,高度为lgn 。堆结构上的一些基本操作的运行时间与堆高度成正比,所以时间复杂度为O(lg n)。

 

       6:当用数组表示n个元素的堆时,叶节点的下标分别是:((n-2)/2)+1, ((n-2)/2)+2,……,n。(默认元素下标从0开始,所以,最后一个结点的父节点一定是最后一个内部结点。)

 

       7:最大堆上的操作有:

       MAX-HEAPIFY     :维护最大堆的性质,时间复杂度为O(lg n)。

       BUILD-MAX-HEAP:从无序的数组构造一个最大堆,时间复杂度为O(n)。

       HEAPSORT: 对数组进行原址排序,时间复杂度为O(nlg n)

       MAX-HEAP-INSERT, HEAP-EXTRACT-MAX,  HEAP-INCREASE-KEY,  HEAP-MAXIMUM

      

       8:MAX-HEAPIFY:维护最大堆性质,输入为数组A和下标i。调用MAX-HEAPIFY时,始终假定以LEFT(i)RIGHT(i)为根的左右子树都已经是最大堆了。MAX-HEAPIFY通过让A[i]的值在最大堆中“逐级下降”,从而使得以A[i]为根节点的子树成为最大堆。

       该算法中,每一步都是从A[i],RIGHT[i],LEFT[i]这三者中选出最大值。如果A[i]是最大值,则算法结束,否则,将A[i]与最大值A[j](j= RIGHT[i] or LEFT[i])交换,从而使i及其孩子满足最大堆的性质。然后针对j再次调用MAX-HEAPIFY过程,直到堆的末尾。代码如下:

//from up to down to keep the property of max  heap (recursion)
void  maxheapify(int  *set,  int  index,  int  size)
{
       int  left = LEFT(index);
       int  right = RIGHT(index);


       int  largest;
       if(left < size && set[index] < set[left])
       {
              largest = left;
       }
       else
       {
              largest = index;
       }

       if(right < size && set[largest] < set[right])
       {
              largest = right;
       }



       if(largest != index)
       {
              exchange(set+index, set+largest);
              maxheapify(set, largest, size);
       }
}


//from up to down to keep the property of max  heap (loop)
void maxheapify2(int *set, int index, int size)
{
       int left;
       int right;

       int largest;

       while(index < size)
       {
              left = LEFT(index);
              right = RIGHT(index);

              if(left < size && set[index] < set[left])
              {
                     largest = left;
              }
              else
              {
                     largest = index;
              }

              if(right < size && set[largest] < set[right])
              {
                     largest = right;
              }

              if(largest != index)
              {
                     exchange(set + index, set + largest);
                     index = largest;
              }
              else
              {
                     return;
              }
       }
}

 

       若以i为根节点的子树元素个数为n,则其左孩子结点的个数最多为2/3 * n证明如下:假设左子树节点数为x,右子树节点数为y,所以n = x + y + 1。因堆是从左向右填充的,所以x >= y。如果要使x达到最大值,那么这种情况就是堆的最底层元素恰好半满。

       假设堆的高度为h,那么左子树高度为h-1,右子树高度为h-2。所以,n <= 2^(h+1) - 1,

x = 2^h - 1, y = 2^(h-1) - 1。所以 x/y  ≈ 2/1  。所以,x ≈ 2n/3

       所以,递归的算法:T(n) = T(2n/3) + Θ(1)。从而T(n) = O(lg n)。

 

       9:因为MAX-HEAPIFY(A,i)包含一个假设,就是i的左右子树都已经是最大堆了,所以一般情况下,都是自底向上的调用MAX-HEAPIFY比如建堆函数BUILD-MAX-HEAP,该算法把一个大小为n的数组转换为最大堆。因为叶子节点可以认为已经是最大堆了,所以建堆的过程从最后一个内部结点开始,也就是下标为(size-2)/2的元素。从该节点开始,依次向前调用MAX-HEAPIFY,建立最大堆。

       在BUILD-MAX-HEAP中,需要调用n次MAX-HEAPIFY过程,所以错略计算该算法的时间复杂度为O(nlg n)。这不是一个紧缺的上界,因为MAX-HEAPIFY的时间跟高度h有关,h的范围是[0,lg n],而且高度为h的元素个数最多为n/2^(h+1) 。所以,实际上BUILD-MAX-HEAP的时间复杂度为O(n)。代码如下:

 //build the max heap from set with call maxheapify
void buildmaxheap(int *set, int size)
{
       int index = size-1;
       int parent = PARENT(index);
       int i;

       for(i = parent; i >= 0; i--)
       {
              maxheapify2(set, i, size);
       }
}


       10:利用最大堆进行排序的算法HEAPSORT的基本思想是:

       首相利用BUILD-MAX-HEAP算法将一个n元素数组转换为最大堆,此时该数组的最大元素就是A[0],所以,交换A[0]A[n-1]

       然后,将数组A[0…n-2]看成新的数组,该数组中,除了根节点之外,其他左右子树依然满足最大堆的性质,只是根节点因为发生了交换所以有可能不满足最大堆性质,所以,对根节点调用MAX-HEAPIFY即可。

       重复以上这个过程直到堆的大小变为1。

       该算法的时间复杂度为O(n lg n)。代码如下:

//sort use max heap
void maxheapsort(int *heap, int size)
{
       int heapsize = size;
       int i;
       buildmaxheap(heap, size);

       for(i = size - 1; i > 0; i--)
       {
              exchange(heap, heap+i);
              heapsize--;
              maxheapify2(heap, 0, heapsize);
       }
}

 

11:优先队列

       堆排序是一个优秀的算法,但是在实际应用中,快速排序一般会快于堆排序堆还可以作为优先队列的实现。最大堆和最小堆分别对应于最大优先队列和最小优先队列。优先队列是一个集合,集合中的元素都有一个关键字(key),此关键字标识该元素的优先级。最大优先队列的例子是进程调度,每次调度进程时,都是从队列中选择优先级最高的进程进行运行。最小优先队列的例子是事件驱动的模拟器,每个事件以发生时间为关键字,每次选择到时的事件进行模拟。

       优先队列支持的操作有(优先队列可用堆来实现,下面的优先队列的操作实际上是针对最大堆的):

       INSERT:向优先队列中插入元素。

       MAXIMUM:返回优先队列中优先级最大的元素。

       EXTRACT-MAX:返回优先级最大的元素,并且将钙元素删除。

       INCREASE-KEY:将某个元素的关键字增加为k,重新构造优先队列。

       DELETE:从优先队列中删除元素。

 

12:EXTRACT-MAX:返回优先级最大的元素,并且将该元素删除。

     该算法将A[1]记录,然后将最后的元素A[heapsize]存储到A[1]中,然后减少heapsize,然后调用MAXHEAPIFY重新调整最大堆。最后返回记录的A[1]的值。该算法的时间复杂度与MAXHEAPIFY相同,为O(lg n)。代码如下:

//return the max elements and del it
int heap_extractmax(int *set, int size)
{
       if(size < 1)
       {
              printf("heap size error ");
              return -1;
       }
       int max = set[0];
       int heapsize = size;

       set[0] = set[size-1];
       set[size-1] = NEINFINITE;
       heapsize = size - 1;
       maxheapify(set,0,heapsize);
       return max;
}

 

       14:INCREASE-KEY:将某个元素的关键字增加为key,重新构造优先队列。

        该算法将A[i]的关键字置为key,然后从索引i开始向上比较,如果A[i] > A[PARENT(i)],则交换A[i] 和 A[PARENT(i)],然后i= PARENT(i)。重复这个过程直到I = 1。

       该算法与MAXHEAPIFY类似,只不过该算法是从下往上维护最大堆的性质。该算法的时间复杂度为O(lg n)。代码如下:

//from down to up to keep the property of max heap
void heap_increasekey(int *set, int index, int key, int size)
{
       int i, p;
       if(set[index] > key)
       {
              printf("current is %d, key is %d, error ", set[index], key);
              return ;
       }
       set[index] = key;
       i = index;

       while(i > 0)
       {
              p = PARENT(i);
              if(key > set[p])
              {
                     set[i] = set[p];
                     i = p;
              }
              else
              {
                     set[i] = key;
                     break;
              }
       }
}

 

       15:INSERT:向优先队列中插入元素。

       该算法首先将heapsiize增加,然后置A[heapsize]为 。然后调用INCREASE-KEY(set, heapsize, key)即可,该算法的时间复杂度为O(lg n)。代码如下:

//insert elements to max heap
void maxheapinsert(int *set, int key, int heapsize, int size)
{
       if(heapsize >= size)
       {
              printf("heapsize larger than size, error ");
              return;
       }
       heapsize ++;
       set[heapsize - 1] = NEINFINITE;
       heap_increasekey(set, heapsize-1, key, heapsize);
}

 

       16:DELETE:从优先队列中删除元素。

       该算法将最后的元素A[heapsize]存储到A[index]中,然后减少heapsize。然后比较当前index元素的值与父母的值大小,如果A[index] < A[PARENT(index)],则从上往下维护堆的性质(MAXHEAPIFY),否则,从下往上维护堆的性质(INCREASE-KEY)。代码如下:

//del elements from max heap,note: it should be up to down or down to up
void maxheapdel(int *set, int index, int size)
{
       int heapsize = size-1;
       int p = PARENT(index);
       int key = set[size-1];

       if(key < set[p])
       {//down
              set[index] = set[size-1];
              maxheapify(set, index, heapsize);
}
       else
       {//up
              heap_increasekey(set,index,key,heapsize);
       }


       17:设计一个复杂度为O(nlg k)的算法,将k个有序链表合并为一个有序链表,其中n为所有链表元素的总和。

       首先将所有k个链表的第一个元素组成一个最小堆,然后EXTRACT-MIN得到综合链表的第一个元素,然后将MIN所在链表的第二个元素INSERT到最小堆中,然后再次EXTRACT-MIN。如此重复下去,知道最小堆为空。该算法时间复杂度为O(nlg k)

 

18:一个m * n的YOUNG氏矩阵中,每一行的数据都是从左向右进行排序的,每一列的元素都是从上到下排序的,如果矩阵中某个元素不存在,则记该元素为 比如包含元素{9,6,3,2,4,8,5,14,12}的4 x 4的Young氏矩阵如下:

2 3 4 5

6 8 9 12

14 ∞ ∞ ∞

∞ ∞ ∞ ∞

       a:给出一个O(m + n)的EXTRACT-MIN算法。

       类似于堆的EXTRACT-MIN算法,首先将A[1][1]记录下来以备返回,然后将矩阵的最后一个元素A[m][n]存储到A[1][1]中,然后比较A[1][1]。A[1][2]和A[2][1]的值,将其中的最小值与A[1][1]进行对换,然后再次调用该过程。每次调用都是比较A[i][j]、A[i][j+1]和A[i+1][j]的值,将最小值记录到A[i][j]中,然后针对(I,j+1)或者(j+1, i)再次调用该过程(类似于MINHEAPIFY)。可见该过程的时间复杂度为O(m+n)。

 

       b:设计一个O(m + n)的算法,确定某个数是否在该矩阵中。

       每次通过将X与矩阵中最右上角的元素进行比较,如果X大,则说明X可能在剩下的(m-1) * n的矩阵中,如果X小,说明X有可能在剩下的m * (n-1)矩阵中。该算法的时间复杂度为O(m + n)。

原文地址:https://www.cnblogs.com/gqtcgq/p/7247241.html