• 堆和堆排序(堆实现优先级队列)


    堆是一种特殊类型的二叉树,它具有2个性质:

    1.每个节点的值大于等于其每个子节点的值

    2该树完全平衡,最后一层的叶子都处于最左侧的位置

    n个元素称为对,当且仅当它的关键字序列k1,k2,.....kn满足

     ki<=K2i, Ki<=K(2i+1); 1<=i<=floor(n/2);

    或者反过来。

    堆有最大堆和最小堆,最大最小堆等。可以直接跳到后面看。

    注意,我们用数组表示堆时,根节点存放在H[1]中

    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    //元素上移操作
    /*数组h[]及被上移的元素下标i
    输出,维持堆的性质的数组h[]
    */
    
    template<class T>
    void sift_up(T h[],int i)
    {
        if(i!=1)
        {
            while(i!=1)
            {
                if(h[i]>h[i/2])
                {
                        swap(h[i],h[i/2]);
                        i=i/2;
                }
                else
                    break;
                
            }
        }
    }
                    
    
    /*下移操作
    和儿子节点关键字大的进行比较
    */
    
    template<class T>
    void sift_down(T h[],int n,int i)
    {
    
        if((2*i)<=n)
        {
            while((i=2*i)<=n)
            {
                if((i+1<=n)&&(h[i+1]>h[i]))
                    i=i+1;
                if(h[i/2]<h[i])
                    swap(h[i/2],h[i]);
                else
                    break;
            }
        }
    }
    
    //删除元素
    /*输入:数组h[],数组的元素个数n,被删除的元素下标
     输出:维持堆的性质的数组H[],及删除后的元素个数n
     */
    //为了删除H[i],可用堆中最后一个元素取代h[i],然后根据被删除元素和取代
    //元素 的矮小,确定是做下移还是上移操作
    template<class T>
    void deleteElem(T h[],int &n,int i)
    {
        T x,y;
        x=h[i],y=h[n];
        n=n-1;
        if(i<=n)
        {
            h[i]=y;
            if(y>x)
                sift_up(h,i);
            else
                sift_down(h,n,i);
        }
    }
    
    /*删除关键字最大的元素,关键字最大的元素位于根节点,把根节点去掉*/
    template<class T>
    T delete_max(T h[],int &n)
    {
        T x;
        x=h[1];
        deleteElem(h,n,1);
        return x;
    }
    
    
    
    //插入操作
    /*为了把元素x插入堆中,只要把堆的大小增1后,把x放到堆的末端,然后对x上移即可。
    */
    template<class T>
    void insert(T h[],int &n,T x)
    {
        n++;
        h[n]=x;
        sift_up(h,n);
    }
    
    //构造一个队
    //输入数组h[],数组的元素个数n
    //输出:n个元素的堆H[]
    template<class T>
    void make_heap1(T a[],T h[],int n)
    {
        int i,m=0;
        for(i=0;i<n;i++)
            insert(h,m,a[i]);
    }
    
    //我们可以把数组本身构造成一个堆,调整过程是从最后一个树叶,找到它上面的分支节点,从这个
    //节点开始做下移操作,一直到根节点为止,最后就成了一个堆*、
    template<class T>
    void make_heap(T a[],int n)
    {
        int i;
        a[n]=a[0];
        for(i=n/2;i>=1;i--)
            sift_down(a,n,i);
    }
    
    
    int main()
    {
        int a[100]={2,4,5,3,8,18,13,15,20,25};
        int h[100];
        make_heap1(a,h,10);
    
    
        for(int i=1;i<=10;i++)
            cout<<h[i]<<ends;
        cout<<endl;
        make_heap(a,10);
        for(int i=1;i<=10;i++)
            cout<<a[i]<<ends;
        cout<<endl;
        int n=10;
        delete_max(a,n);
        for(int i=1;i<=n;i++)
            cout<<a[i]<<ends;
    
    }

    注意上面两种构造堆方法不同,输出的堆不是一样的,但都是堆

    结果:

    堆的排序

       可以利用堆的性质,对数组A排序,假定A元素个数为n,根据最大堆的性质,根节点元素a[1]就是最大元素,此时,只要交换a[1]和a[n],则a[n]就成为数组关键字最大的元素。就相当于把a[n]从堆中删去,元素个数减1,而交换到h[1]的新元素,破坏了堆的结构,因此要对a[1]做下移操作,使其恢复堆的结果,经过这样交换后,a[1]----a[n-1]成为新的堆,其个数为n-1,反复进行这种操作,a[1]就是A中最小的元素了。堆排序代码:

    template<class T>
    void make_heap(T a[],int n)
    {
        int i;
        a[n]=a[0];
        for(i=n/2;i>=1;i--)
            sift_down(a,n,i);
    }
    
    void heap_sort(int a[],int n)
    {
         make_heap(a,n);
        for(int i=n;i>1;i--)
        {
            swap(a[1],a[i]);
            sift_down(a,i-1,1);
        }
    }

    注意我们传入的数组a[]的维度应该大于它的元素个数。

     -----------------------------------------------

    算法导论:

    Heapify

    最大堆为例,伪代码:

    MAX-HEAPIFY(A, i)

      l = LIFT(i)

      r = RIGHT(i)

      if l <= A.heapsize and A[l] > A[i]

        largest = l

      else largest = i

      if r <= A.heapsize and A[r] > A[largest]

        largest = r

      if largest != i

        exchage A[i] with A[largest]

        MAX-HEAPIFY(A, largest)

     怎么求时间复杂度?

    用主定理求得T(n)=o(lgn),或者说,MAX-HEAPIFY作用域一个高度为h的节点所需的运行时间为O(h)。高度h为lgn

    Build the heap

    我们已经知道,当用数组存储了n个元素的堆时,叶子节点的下标为n/2+1,n/2+2.........n;(因为假设节点为i,只要i*2>n就说明

    i为叶子节点。这样求的i=n/2+1)。叶子都可以看成只有一个元素的堆,过程build-max-heap对树中的每一个其他节点都调用一次

    max-heapify.

    BUILD-MAX-HEAP(A)  

       heap-size[A]=length(A);

       for i=length(A)/2 downto 1

             MAX-HEAPIFY(A,i)

    上面的时间复杂度为O(n)。

    可以在线性时间内,将一个无序数组建成一个最小堆

    算法导论6-3-2:为什么i从n/2 down to 1,而不是从1 to n/2.

    为了保证在调用Max-heapify(A,i)时,以Left[i]和right(i)为根的2颗二叉树都是最大堆。

    因为自底至顶的循环方法可以首先让底部的元素满足堆的形式,最后让顶部元素满足堆的性质。如果自顶至低会有灾难性的后果,会有特殊情况使之树的局部不完备。

    我的理解:开始时调用max-heapify(a,1)由于a[1]的左子树和右子树还不是堆,所以有问题。

    堆排序算法:

      建立了最大堆后,因为数组中最大元素在根A[1],则可以通过它与A[n]互换来达到最终正确的位置。现在,如果从 堆中“去掉节点n”,

    可以很容易将A[1..n-1]建成最大堆,原来根的子女仍是最大堆,而新的根元素可能违背了最大堆性质,这时调用MAX-HEAPIFY(A,1)就可以保持这一性质.

    for i = A.length downto 2

        exchange A[1] with A[i]

        A.heap-size = A.heap-size - 1

        MAX-HEAPIFY(A, 1)

    循环了n-1遍,每次调用后堆的size减1.为什么减1?

    时间复杂度为O(nlgn).

    代码:

    #include<iostream>
    using namespace std;
    
    int heapSize;
     
    void maxHeapify(int a[],int heapSize,int i)// 
    {
        int l=2*i+1;//由于是从0开始,left为2*i+1,,right为2*i+2;
        int r=2*i+2;
        int largest;
        if(l<heapSize && a[l]>a[i])
            largest=l;
        else
            largest=i;
    
        if(r<heapSize && a[r]>a[largest])
            largest=r;
        
        if(largest!=i)
        {
            swap(a[i],a[largest]);
            maxHeapify(a,heapSize,largest);
        }
    }
    
    void buildMaxHeap(int a[],int  n)
    {
        heapSize=n;//赋值
        for(int i=n/2-1;i>=0;i--)//这里要特别注意,由于是从0开始,不会n/2
        {
            maxHeapify(a,n,i);
        }
    }
    void heapSort(int a[],int n)
    {
        heapSize=n;
        for(int i=n-1;i>=1;i--)
        {
            swap(a[0],a[i]);
            heapSize--;//很重要
            maxHeapify(a,heapSize,0);//第0个元素,也就是根节点
        }
    }
    
    #define aSize 10;
    
    int main()
    {
        int a[10]={4,1,3,2,16, 9,10,14,8,7};
        buildMaxHeap(a,10);
        for(int i=0;i<10;i++)
            cout<<a[i]<<ends;
        cout<<endl<<endl;
        heapSort(a,10);
        for(int i=0;i<10;i++)
            cout<<a[i]<<ends;
        cout<<endl;
         
    }

    由于我们从0开始,而不是从1开始,产生了很多陷阱,画红线的都是我写的时候不小心出错的地方。要特别注意:

    数据来自算法导论P77:

     int a[10]={4,1,3,2,16, 9,10,14,8,7};

    非递归maxheapify:

    void adjust_max_heap(int *datas,int length,int i)
    {
        int left,right,largest;
        int temp;
        while(1)
        {
            left = LEFT(i);   //left child
            right = RIGHT(i); //right child
            //find the largest value among left and rihgt and i.
            if(left <= length && datas[left] > datas[i])
                largest = left;
            else
                largest = i;
            if(right <= length && datas[right] > datas[largest])
                largest = right;
            //exchange i and largest
            if(largest != i)
            {
                temp = datas[i];
                datas[i] = datas[largest];
                datas[largest] = temp;
                i = largest;
                continue;
            }
            else
                break;
        }
    }

    最小堆只需要改以下判断条件:

    void minHeapify(int a[],int heapSize,int i)
    {
        int l=2*i+1;
        int r=2*i+2;
        int smallest;
        if(l<heapSize && a[l]<a[i])
            smallest=l;
        else
            smallest=i;
        if(r<heapSize && a[r]<a[smallest])
            smallest=r;
    
        if(smallest!=i)
        {
            swap(a[i],a[smallest]);
            minHeapify(a,heapSize,smallest);
        }
    }
    
    
    void minHeapify2(int *a,int n,int m) 
    { 
        int i=m; 
        int j=2*i+1; 
        int tmp=a[i]; 
        while(j<n) 
        { 
            if(j+1<n&&a[j]>a[j+1]) 
                j++; 
            if(a[j]>=tmp) 
                break; 
            else 
            { 
                a[i]=a[j]; 
                i=j; 
                j=2*i+1; 
            } 
        } 
        a[i]=tmp; 
    } 

    http://buptdtt.blog.51cto.com/2369962/864190

    优先级队列:

    优先级队列有两种:最大优先级队列和最小优先级队列,这两种类别分别可以用最大堆和最小堆实现。书中介绍了基于最大堆实现的最大优先级队列。一个最大优先级队列支持的操作如下操作:

    insert(S,x)把元素x插入集合S

    maximum(s) 返回S中具有最大关键字的元素

    extract-max(S) 去掉并返回S中具有最大关键字的元素

    increase-key(S,x,k) 将元素x的值增加到k(也就是使值增大为k),这里k值不能小于x原来的值。

    heap-maximum 用了O(1)的时间

    heap-maximum(A)

      return A[1];

    extract_max(A)

    Heap-Increase-Key(A,i,key):将节点i的值增加到key,这里key要比i节点原来的数大。

    新增大的关键字与母亲比较,如果大于母亲则不断往上移动。

    heap-insert实现了insert操作,这个程序首先加入一个关键字为负无穷大的叶节点来扩展最大堆,然后调用heap-increase,key来设置新节点的关键字的正确值,并保持最大堆的性质:

    总之,一个堆可以在O(lgn)的时间内,支持大小为n的集合上的任意优先队列操作

    #include<iostream>
    using namespace std;
    
    inline int parent(int i)
    {
        return i>>1;
    }
    inline int left(int i)
    {
        return i<<1;
    }
    inline int right(int i)
    {
        return (i<<1)|1; ////位运算乘2后,结果是偶数所以最后一位一定是0, 所以|1将会把最后一位变成1,从而实现加1的效果
    }
    void maxHeapify(int a[],int heapSize,int i)// 
    {
        int l=left(i);
        int r=right(i);
        int largest;
        if(l<=heapSize && a[l]>a[i])
            largest=l;
        else
            largest=i;
    
        if(r<=heapSize && a[r]>a[largest])
            largest=r;
        
        if(largest!=i)
        {
            swap(a[i],a[largest]);
            maxHeapify(a,heapSize,largest);
        }
    }
    
    void buildMaxHeap(int a[],int  n)
    {
        int heapSize=n;//赋值
        for(int i=n/2;i>=1;i--)//这里要特别注意,由于是从0开始,不会n/2
        {
            maxHeapify(a,n,i);
        }
    }
    
    void heapSort(int a[],int n)
    {
        int heapSize=n;
        for(int i=n;i>=2;i--)
        {
            swap(a[1],a[i]);
            heapSize--;
            maxHeapify(a,heapSize,1);
        }
    }
    
    
    
    int maximum(int A[])
    {
        return A[1];
    }
    int extractMax(int A[],int heapSize)
    {
        if(heapSize<1)
            return 0;
        int max=A[1];
        A[1]=A[heapSize];
        heapSize--;
        maxHeapify(A,heapSize,1);
    
        return max;
    }
    
    void increaseKey(int A[],int i,int key)
    {
        A[i]=key;
        while(i>1 && A[parent(i)]<A[i])
        {
            swap(A[parent(i)],A[i]);
            i=parent(i);
        }
    }
    
    void maxHeapInsert(int A[],int heapSize,int key)
    {
        heapSize++;
        A[heapSize]=-32768;
        increaseKey(A,heapSize,key);
    }
    
    
    int main()
    {
        int a[15]={0,4,1,3,2,16, 9,10,14,8,7};//第0个为0仅仅是占位,不算,后面有10个元素
        buildMaxHeap(a,10);
        for(int i=1;i<=10;i++)
            cout<<a[i]<<ends;
        cout<<endl<<endl;
        //heapSort(a,10);
        for(int i=1;i<=10;i++)
            cout<<a[i]<<ends;
        cout<<endl;
        cout<<"maximu"<<maximum(a)<<endl;
        cout<<"extractMax:"<<extractMax(a,10)<<endl;
        for(int i=1;i<=9;i++)//extract后heapSize减1了
            cout<<a[i]<<ends;
        cout<<endl;
        increaseKey(a,6,16);
            for(int i=1;i<=9;i++)//extract后heapSize减1了
            cout<<a[i]<<ends;
    
    }

    习题:

    6.5-8题目如下:请给出一个时间为O(nlgk)、用来将k个已排序链表合并为一个排序链表的算法。此处n为所有输入链表中元素的总数。(提示:用一个最小堆来做k路合并)。

    更多:

    http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html

    http://www.cnblogs.com/dyingbleed/archive/2013/03/04/2941989.html

  • 相关阅读:
    polarsignals frostdb golang嵌入式列存
    fgprof golang profiler 支持on cpu 以及off cpu
    windows jenkins openssh 集成问题
    buf 工具的一些概念
    nginx njs 0.7.7发布
    nginx proxy_pass 包含路径问题
    基于iap 的安全控制
    frida 动态检测工具集
    buf buf.work.yaml 一个好用的功能
    一些不错的nginx 开发资料
  • 原文地址:https://www.cnblogs.com/youxin/p/2610688.html
Copyright © 2020-2023  润新知