基于二叉树的堆排序、优先队列、二叉查找树、平衡查找树、B树详解

一、二叉树的性质

  • 二叉树第i层上的结点数目最多为2^(i-1)
  • 深度为k的二叉树至多有2^k-1个结点
  • 在任意-棵二叉树中,若终端结点的个数为n0,度为2的 结点数为n2,则no=n2+1

二、二叉树的存储结构

1、顺序存储结构

数组的下标就是二叉树的结点位置(层次结构),比如结点A,在数组中就是位置0,B就是1,C就是2….,以此类推,所以第i个结点的在数组中的位置就是i(i从0开始),i的两个孩子结点在数组中的位置是2i+1和2i+2
《基于二叉树的堆排序、优先队列、二叉查找树、平衡查找树、B树详解》

2、链式存储结构

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
《基于二叉树的堆排序、优先队列、二叉查找树、平衡查找树、B树详解》

三、基于二叉树思想的堆排序

1、堆的存储

堆排序是一种选择排序。是不稳定的排序方法。时间复杂度为O(nlog2n)。特点是在排序过程中,将排序数组看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。根结点下标为0时,下标为n的元素的子结点下标分别为2*n+1,2*n+2,其父结点下标为(n-1)/2,如图所示。(一些程序员喜欢用下标1表示根节点,下标为n的元素的子节点下标分别为2*n,2*n+1,父节点下标为 floor(n/2)
《基于二叉树的堆排序、优先队列、二叉查找树、平衡查找树、B树详解》

2、堆排序的实现

堆排序可以分为两个阶段:堆构造和堆排序(下沉排序)

构造初始堆

初始化堆的时候是对所有的非叶子结点进行筛选。最后一个非终端元素的下标是[n/2]向下取整,所以筛选只需要从第[n/2]向下取整个元素开始,从后往前进行调整。比如,给定一个数组,首先根据该数组元素构造一个完全二叉树。然后从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换,交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。

堆排序

堆排序是一种选择排序。建立的初始堆为初始的无序区。排序开始,首先输出堆顶元素(因为它是最值),将堆顶元素和最后一个元素交换,这样,第n个位置(即最后一个位置)作为有序区,前n-1个位置仍是无序区,对无序区进行调整,得到堆之后,再交换堆顶和最后一个元素,这样有序区长度变为2……。 不断进行此操作,将剩下的元素重新调整为堆,然后输出堆顶元素到有序区。每次交换都导致无序区-1,有序区+1。不断重复此过程直到有序区长度增长为n-1,排序完成。

示例代码:

import java.util.Arrays;

/** * 堆排序 * 1-构造堆 2-下沉排序 * @author Administrator * */
public class HeapSort <Key extends Comparable<Key>>{

    public static void main(String[] args) 
    {
        HeapSort hs = new HeapSort();
         Comparable[]data =
                {73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100};
         Comparable[] dataTemp = new Comparable[data.length+1] ;
         for (int i=0;i<data.length;i++){
             dataTemp[i+1] = data[i];
         }
        hs.sort(dataTemp);
    }
    //比较
    private boolean less(Comparable i,Comparable j)
    {
        return i.compareTo(j) > 0;
    }

    //交换
    private void exch(Comparable[] a, int i, int j)
    {
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    //下沉(堆有序化)
    private void sink(Comparable[] a,int k,int N)
    {
        while(2*k <= N)
        {
            int j = 2*k;
            if (j < N && less(a[j],a[j+1]))//找出子节点中较大的节点,保证交换后左右子节点都小于k节点
            {
                j++;
            }
            if (!less(a[k],a[j]))
            {
                break;
            }
            exch(a,k,j);
            k = j;
        }
    }
    private void sort(Comparable[] data)
    {
        int LEN = data.length-1;
        //构造堆
        for(int i=data.length/2;i>0;i--)
        {
            sink(data,i,LEN);
        }
        System.out.println("构造堆结束"+Arrays.toString(data));
        //下沉排序

        while(LEN>1)
        {
            System.out.println("交换前"+Arrays.toString(data));
            exch(data,1,LEN--);
            System.out.println("交换后"+Arrays.toString(data));
            sink(data,1,LEN);
            System.out.println("下沉"+Arrays.toString(data));
        }
        System.out.println("最终"+Arrays.toString(data));
    }
}

3、堆排序分析

堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。堆排序在最坏的情况下,其时间复杂度也为O(nlgn),构造堆的时间复杂度为O(n)。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。

四、基于二叉堆的优先队列

假如有10亿个数字,我们的需求是从中找到最大的10个数字,这种情况下使用优先队列效率最高。优先队列是一个至少能够提供插入(Insert)和删除最小(DeleteMin)这两种操作的数据结构,也是顺序存储结构。链表,二叉查找树,都可以提供插入(Insert)和删除最小(DeleteMin)这两种操作,但是为什么不用它们而引入了新的数据结构呢。原因在于应用前两者需要较高的时间复杂度。对于链表的实现,插入需要O(1),删除最小需要遍历链表,故需要O(n)。对于二叉查找树,这两种操作都需要O(lgn);而且随着不停的DeleteMin的操作,二叉查找树会变得非常不平衡;同时使用二叉查找树有些浪费,因此很多操作根本不需要。因此这里引入一种新的数据结构,构建初始堆时时间复杂度是O(n),插入和删除只是修改了堆顶和堆底,不需要所有的都排序,只是需要再次调整好堆,因此插入删除一个数的时间复杂度都是O(lgn)。(PS: 自己理解-总时间复杂度O(nlgm),m代表要找出数据的数量 )

堆有序化的过程中我们会遇到两种情况。当某个节点的优先级上升时,我们需要由下至上恢复堆的顺序;当某个节点优先级下降,需要由上至下恢复。
《基于二叉树的堆排序、优先队列、二叉查找树、平衡查找树、B树详解》
由下至上的堆有序化(上浮):

    //上浮(堆有序化)
    private void swim(int k)
    {
        while( k>1 && less(k/2,k) )
        {
            exch(k,k/2);
            k = k/2;
        }
    }

由上至下的堆有序化(下沉):

//下沉(堆有序化)
    private void sink(int k)
    {
        while(2*k <= N)
        {
            int j = 2*k;
            if (j < N && less(j,j+1))//找出子节点中较大的节点,保证交换后左右子节点都小于k节点
            {
                j++;
            }
            if (!less(k,j))
            {
                break;
            }
            exch(k,j);
            k = j;
        }
    }

sink()和swim()是实现优先队列的基础。我们将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置。插入结束后,我们从数组顶端删除最大元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置。

基于二叉堆的优先队列具体实现:

public class PriorityQueueSort<Key extends Comparable<Key>> {

    private Key[] pq;//基于堆的完全二叉树
    private int N=0;//节点数量

    public boolean isEmpty()
    {
        return N==0;
    }

    public int size()
    {
        return N;
    }

    public PriorityQueueSort(int max)
    {
        pq = (Key[]) new Comparable[max+1];
    }

    //比较
    private boolean less(int i,int j)
    {
        return pq[i].compareTo(pq[j]) > 0;
    }

    //交换
    private void exch(int i,int j)
    {
        Key temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
    }

    //上浮(堆有序化)
    private void swim(int k)
    {
        while( k>1 && less(k/2,k) )
        {
            exch(k,k/2);
            k = k/2;
        }
    }

    //下沉(堆有序化)
    private void sink(int k)
    {
        while(2*k <= N)
        {
            int j = 2*k;
            if (j < N && less(j,j+1))//找出子节点中较大的节点,保证交换后左右子节点都小于k节点
            {
                j++;
            }
            if (!less(k,j))
            {
                break;
            }
            exch(k,j);
            k = j;
        }
    }

    //插入
    public void insert(Key v)
    {
        pq[++N] = v;
        swim(N);
    }

    //删除最大值
    public Key delMax()
    {
        Key max = pq[1];
        exch(1,N--);//交换第一个节点和最后一个节点,并将最后一个节点删除
        pq[N+1] = null;//防止游离
        sink(1);//恢复堆的有序性
        return max;
    }
    public static void main(String[] args) 
    {

        // TODO Auto-generated method stub
        int M = 10;//定义要输出最大值的个数
        PriorityQueueSort pqs = new PriorityQueueSort(M+1);
        int arr[] = {2,12,3,54,34,578,8,4,24,45,8756,342,0,754,54,42,756,867,21,3124,56,35,8888};
        for (int i=0;i<arr.length;i++)
        {
            pqs.insert(arr[i]);
        // System.out.println(Arrays.toString(pqs.pq));
            if (pqs.size() > M)
            {
                pqs.delMax();
            }
        }
        System.out.println("其中最小的"+M+"个元素为:");
        for (Comparable i:pqs.pq)
        {
            System.out.print(i+" ");
        }

        //若希望升序输出,用栈
        Stack sta = new Stack();
        while(!pqs.isEmpty())
        {
            sta.push(pqs.delMax());
        }
        System.out.println("\r\n排序后结果为:");
        while(!sta.isEmpty())
        {
            System.out.print(sta.pop()+" ");
        }
    }

}

五、二叉查找树

链式存储结构

六、平衡查找树(2-3树,红黑树)

七、B树

参考资料:
http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html
http://blog.csdn.net/changyuanchn/article/details/14564403

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/taotao12312/article/details/69499629
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞