zoukankan      html  css  js  c++  java
  • 算法导论笔记: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)。

  • 相关阅读:
    Longest Palindromic Substring
    PayPal MLSE job description
    Continuous Median
    Remove Duplicates From Linked List
    Valid IP Address
    Longest substring without duplication
    Largest range
    Subarray sort
    Multi String Search
    Suffix Trie Construction
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247241.html
Copyright © 2011-2022 走看看