天天看点

数据结构 JAVA描述(十一) 选择排序(直接选择排序,树形选择排序,堆排序)直接选择排序树形选择排序堆排序

直接选择排序

  • 置i初值为0
  • 当i < n-1时,重复下列步骤
    • 在无序子序列中{a[i], ……a[n-1]}中选出最小的a[min]
    • 若min!=i,则交换
    • i++
/**
     * @description 直接选择排序算法 
     * @return
     * @time 2016年1月5日 上午1:28:19
     */
    public static int[] selectSort(int[] before){
        int[] a = Arrays.copyOf(before, before.length);
        for(int i = ; i < a.length - ; i++){
            // 每趟从a[i]开始的自序列中寻找最小关键字值的元素下标
            int min = i;
            for(int j = i + ; j < a.length; j++){
                if(a[j] < a[min]){
                    min = j;
                }
            }
            if(min != i){
                int temp = a[i];
                a[i] = a[min];
                a[min] = temp;
            }
        }
        return null;
    }
           

算法性能分析

  • 空间复杂度:O(1)
  • 时间复杂度:O(n²)

    外循环需执行n-1次,内循环需执行n-1-i次,因此总比较次数是∑(n-1-i)=1/2n(n-1)。最坏情况(逆序)下的移动次数是3(n-1),最好情况(有序)下的移动次数是0。

  • 算法稳定性:不稳定

树形选择排序

在直接选择排序中,关键字的比较次数为1/2n(n-1),实际上,很多关键字之间进行了不止一次的比较。能否在选择最小关键字的过程中,把关键字的比较的结果保存下来,以便在以后需要的时候直接查看这个比较结果呢?

树形选择排序又称为锦标赛排序,将n个记录两两比较,关键字值小的升到父结点,直到构成一个含有n个结点的完全二叉树,这样的二叉树称为胜者树。如果开始的元素个数n不是2的k次幂,则将叶子节点补足到2的k次幂。

胜者树采用顺序存储结构:

  • 变量初始化,令待排序结点个数为n,则叶子结点个数为n,树的结点总数为2*n-1,叶子结点在顺序表中的起始存放位置n-1
  • 将a[0……n-1]依次赋值到tree[loadindex……TreeSize-1]
  • 构造胜者树,n个结点两两比较,得到n/2个关键字值小的结点;再将n/2个结点比较得到n/4,直到得到最小的关键字
  • 调整胜者树,先将根结点保存到原数组a中,再把对应叶子结点值改为“极大值”,然后从该叶子结点开始修改到根上各结点的值。
  • 重复上面两个步骤
/**
     * @description 树形选择排序算法,构造胜者树的过程,取根结点是最小关键值 
     * @return
     * @time 2016年1月5日 上午1:28:19
     */
    public static int[] tournamentSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length);
        TreeNode[] tree; //胜者树结点数组
        int leafSize = ; //胜者树叶子结点数
        //得到叶子结点的个数,该个数必须是2的次幂
        while(leafSize < a.length){
            leafSize *= ;
        }
        int TreeSize =   * leafSize - ; //胜者树的所有结点数
        int loadIndex = leafSize - ; //叶子结点存放位置的起始位置
        tree = new TreeNode[TreeSize];

        int j = ;
        //把待排序结点复制到胜者树的叶子结点中
        for(int i = loadIndex; i < TreeSize; i++){
            tree[i] = new TreeNode();
            tree[i].setIndex(i);
            if(j < a.length){
                tree[i].setActive();
                tree[i].setData(a[j++]);
            }
            else{
                tree[i].setActive();
            }
        }
        int i = loadIndex;  //进行初始化,比较查找关键字值最小的结点
        while(i > ){
            j = i;
            //处理各对比赛者
            while(j < TreeSize - ){
                if(tree[j + ].getActive() ==  || (tree[j].getData() <= tree[j + ].getData())){
                    tree[(j - )/] = tree[j];  //左孩子胜出
                }
                else{ 
                    tree[(j - )/] = tree[j + ]; //右孩子胜出
                }
                j += ; //下一对比赛者 
            }
            i = (i - )/; //处理上层结点,类似下一轮比赛(已经决出一轮了)
        }

        //处理剩余的n-1个元素
        for(i = ; i < a.length - ; i++){
            a[i] = tree[].getData(); //将胜者树的根(最小值)存入数组
            tree[tree[].getIndex()].setActive(); //冠军不再参加比赛
            updateTree(tree, tree[].getIndex()); //调整有胜者树
        }
        //最后一个元素只需赋值就结束了 不需要再调整(即再进行下一轮比赛)
        a[a.length - ] = tree[].getData(); 
        return a;
    }

    /**
     * @description 树形选择排序的调整算法
     *              从当前最小关键字的叶子结点开始到根结点路径上的所有结点关键字的修改
     * @param tree
     * @param i i是当前最小关键字的下标 
     * @author liuquan
     * @date  2016年1月5日
     */
    private static void updateTree(TreeNode[] tree, int i){
        //因为i是此时最小的关键字(已是冠军),所以在叶子结点中要将其除去比赛资格,对手直接晋级(升为父结点)
        if(i %  == ){ //i为偶数,自己是右结点,对手是左结点,左结点晋级
            tree[(i - )/] = tree[i - ];          
        }
        else{
            tree[(i - )/] = tree[i + ];
        }
        i = (i - ) / ;

        int j = ;
        while(i > ){
            if(i %  == ){ //i为偶数,自己是右结点,对手是左结点 
                j = i - ;
            }
            else{
                j = i + ;
            }

            //比赛对手中有一个为空
            if(tree[i].getActive() ==  || tree[j].getActive() == ){
                if(tree[i].getActive() == ){
                    tree[(i - ) / ] = tree[i];
                }
                else{
                    tree[(i - ) / ] = tree[j];
                }
            }

            //比赛对手都在
            if(tree[i].getData() < tree[j].getData()){
                tree[(i - ) / ] = tree[i];
            }
            else{
                tree[(i - ) / ] = tree[j];
            }

            i = (i - ) / ;     
        }
    }

    /**
     * @description 树形选择排序的胜者树结点结构
     * 
     * @author liuquan
     * @date  2016年1月5日
     */
    private static class TreeNode{
        private int data; //数据域
        private int index; //待插入结点在满二叉树中的序号
        private int active; //参加选择标志,1表示参选,0表示不参选
        public int getData() {
            return data;
        }
        public void setData(int data) {
            this.data = data;
        }
        public int getIndex() {
            return index;
        }
        public void setIndex(int index) {
            this.index = index;
        }
        public int getActive() {
            return active;
        }
        public void setActive(int active) {
            this.active = active;
        }       
    }
           

算法性能分析

  • 空间复杂度:胜者树的叶子结点个数是2的k次幂,所需存储空间是2*2^k-1
  • 时间复杂度:O(n ㏒₂ n)

    胜者树高度为k+1。第一趟需进行n-1次关键字比较,其余趟关键字比较次数为O(㏒₂ n),总的关键字比较次数为O(n ㏒₂ n)。由于结点移动次数不会超过比较的次数,所以时间复杂度是O(n ㏒₂ n)。

  • 算法稳定性:稳定

堆排序

通俗理解,小顶堆就是每个父结点都比它的两个子结点小;大顶堆则反之。

筛选法调整堆:

通俗说就是把顶元素一直换到最下面,重新构成新堆。

  • 设待调整的堆的完全二叉树存放在a[low……high]中
  • 置初值i = low,j = 2*i+1,temp=a[i]
  • 当 j < high时,重复下列操作
    • 若 j < high-1,且a[j] > a[j+1],则j++
    • 若temp > a[j],则a[i]=a[j]; i = j; j = 2*i+1;否则 j =high
  • a[i] = temp

建初始堆:

  • 将待排序元素建成一棵完全二叉树
  • 将下标为[n/2]-1的元素作为开始调整的子树的根结点
  • 找出此结点的两个孩子中的关键字值较小者,与此结点比较;若此结点大,则交换,然后交换后的子结点作为新的父结点,父结点就成了子结点重复此步骤直到没有子结点为止。
  • 以上步骤的原父结点的位置往前推进一个位置,作为新的调整的子树的根结点,继续重复上步骤
/**
     * @description 堆排序算法
     * @return
     * @author liuquan
     * @date  2016年1月5日
     */
    public int[] heapSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length);
        int n = a.length;
        int temp;
        for(int i = n /  - ; i >= ; i--){ //创建初始堆
            sift(i, n, a);
        }
        for(int i = n - ; i > ; i--){ //每趟将最小关键字值交换到后面,再调整成堆
            temp = a[];
            a[] = a[i];
            a[i] = temp;
            sift(, i, a);
        }
        return a;
    }

    /**
     * @description 筛选法调整堆算法 ,以low为根结点的子树调整成小顶堆
     * @param low
     * @param high
     * @author liuquan
     * @date  2016年1月5日
     */
    private void sift(int low, int high, int[] a){
        int i = low; //子树的根结点
        int j =  * i + ;
        int temp = a[i];
        while(j < high){
            //判断条件j < high - 1 表示有右结点,即j+1 < high
            if(j < high -  && a[j] > a[j + ])
                j++;

            if(temp > a[j]){
                a[i] = a[j]; //孩子结点中的较小值上移
                i = j;
                j =  * i + ;
            }
            else
                j = high + ;
        }
        a[i] = temp;
    }
           

算法性能分析

  • 空间复杂度:O(1)
  • 时间复杂度:O(n㏒₂ n)

    假设产生二叉树的高是k,k=[㏒₂ n]- 1关键字的比较次数最多为2(k+1)次,交换记录至多为k次,所以建好堆后筛选次数不超过2([㏒₂ (n-1) +……+㏒₂ 2])<2n[㏒₂ n]

    由于建初始堆时比较次数不超过4n次,因此最坏情况下时间复杂度为O(n㏒₂ n)

  • 算法稳定性:不稳定

继续阅读