数据结构-高级排序

简述     

高级排序算法:希尔排序和快速排序。

其执行效率分别为希尔排序的O(N*(logN)^2)和快速排序的O(N*logN)。

归并排序相较于简单排序中冒泡排序、选择排序和插入排序的执行速度要快,但是需要原始数据空间的两倍。而希尔排序和快速排序不需要大量的辅助空间。

具体实现

希尔排序

package com.jikefriend.arraysort;

/**
 * 希尔排序,其基于插入排序,不适合大规模数据排序
 *
 *  希尔排序通过加入插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,
 *  从而使数据项能大跨度地移动。当这些数据项排过一趟序后,希尔排序算法减小数据项
 *  的间隔再进行排序,依此进行下去
 *
 *  进行这些排序时数据项之间的间隔称为增量
 *  对于10个元素,通过 h = h * 3 + 1 确定间隔,有4和1
 *  间隔为4时对(0,4,8)(1,5,9)(2,6)(3,7)分别排序,实际不是按此分组排序,
 *  而是先排第一组的前两个数据项,然后是第二组的前两个数据项,以此类推,
 *  当所有组的前两个数据项有序后,算法返回再对三个数据项的组进行排序,因此4-增量的实际执行顺序是
 *  (0,4)(1,5)(2,6)(3,7)(0,4,8)(1,5,9)
 *  间隔为1时,排序后,算法结束
 *
 * 排序间隔序列的子数组,逐渐减小间隔,直至为1
 * 注意:后续的排序不撤销前期排序的结果
 */
public class ShellSort {

    private int[] theArray;
    private int nElems;

    public ShellSort(int max)
    {
        theArray = new int[max];
        nElems = 0;
    }

    public void insert(int value)
    {
        theArray[nElems++] = value;
    }

    public void display()
    {
        System.out.print("A=");
        for (int i = 0; i < nElems; i++)
            System.out.print(theArray[i] + " ");
        System.out.println();
    }

    public void shellSort()
    {
        int inner, outer;
        int temp;

        int h = 1;
        while (h <= nElems / 3)                              //找到间隔序列的h初始值
            h = h * 3 + 1;

        while (h > 0)                                        //根据公式 h = (h - 1) / 3 递减,直到h = 1
        {
            for (outer = h; outer < nElems; outer++)         //以h为间隔对数组排序
            {
                temp = theArray[outer];
                inner = outer;

                while (inner > h - 1 && theArray[inner - h] >= temp)
                {
                    theArray[inner] = theArray[inner - h];
                    inner -= h;
                }
                theArray[inner] = temp;
            }
            h = (h - 1) / 3;
        }
    }

    public static void main(String[] args) {
        int maxSize = 10;
        ShellSort arr = new ShellSort(maxSize);

        for (int i = 0; i < maxSize; i++) {
            arr.insert((int)(Math.random() * 99));
        }
        arr.display();
        arr.shellSort();
        arr.display();
    }
}

划分

package com.jikefriend.arraysort;

/**
 * 划分
 *  划分数据是把数据分成两组,
 *  使所有关键字大于特定值的数据项在一组,
 *  所有关键字小于特定值的数据项在一组。
 *
 * 注意:
 *  划分是不稳定的,即每一组中的数据项并不是按照它原来的顺序排列的。
 *  事实上,划分往往会颠倒组中一些数据项的顺序。
 */
public class PartitionArray {

    private int[] theArray;
    private int nElems;

    public PartitionArray(int max)
    {
        theArray = new int[max];
        nElems = 0;
    }

    public void insert(int value)
    {
        theArray[nElems++] = value;
    }

    public int size()
    {
        return nElems;
    }

    public void display()
    {
        for (int i = 0; i < nElems; i++)
            System.out.print(theArray[i] + " ");
        System.out.println();
    }

    /**
     * 划分算法由两个指针开始工作,分别指向数组的两头。
     * 左边的指针向右移动,右边的指针向左移动。
     * @param left
     * @param right
     * @param pivot
     * @return
     */
    public int partition(int left, int right, int pivot)
    {
        int leftPtr = left - 1;
        int rightPtr = right + 1;

        while (true)
        {
            /** leftPtr当前的值如果比特定值小,则继续右移,因为这个数据项已经处在正确的一侧,直至找到比特定值大的数据项停止移动 */
            while (leftPtr < right && theArray[++leftPtr] < pivot)  //找出比特定值大的数据项
                ;
            /** rightPtr当前的值如果比特定值大,则继续左移,因为这个数据项已经处在正确的一侧,直至找到比特定值小的数据项停止移动 */
            while (rightPtr > left && theArray[--rightPtr] > pivot) //找出比特定值小的数据项
                ;
            /** 当两个while都退出循环时,leftPtr和rightPtr都指向在数组的错误一方位置上的数据,交换这两个数据 */
            if (leftPtr >= rightPtr)                                //如果两个指针相遇,则划分结束
                break;
            else                                                    //如果两个指针还没有相遇,则交换两个数据项
                swap(leftPtr, rightPtr);                            //使小的数据项在特定值的左侧,大的数据项在右侧
        }
        return leftPtr;
    }

    private void swap(int dex1, int dex2)
    {
        int temp = theArray[dex1];
        theArray[dex1] = theArray[dex2];
        theArray[dex2] = temp;
    }

    public static void main(String[] args) {
        int maxSize = 16;
        PartitionArray arr = new PartitionArray(maxSize);

        for (int i = 0; i < maxSize; i++) {
            arr.insert((int)(Math.random() * 199));
        }

        arr.display();
        int pivot = 99;
        System.out.print("Pivot is " + pivot);
        int size = arr.size();

        int partDex = arr.partition(0, size - 1, pivot);

        System.out.println(", Partition is at index " + partDex);
        arr.display();
    }
}

快速排序

使用最右端数据项作为枢纽值

package com.jikefriend.arraysort;

/**
 * 快速排序,O(N*logN),最坏O(N^2)
 * 基本步骤:
 *  1、使用划分算法把数组或子数组划分成左边(较小的关键字)的一组和右边(较大的关键字)的一组;
 *  2、调用自身对左边的一组进行排序;
 *  3、再次调用自身对右边的一组进行排序。
 *
 * 基值情况:当数组只有一个数据项时,认为已经有序。
 *
 * 目标值(枢纽)的选取思想:
 *  1、应该选择具体的一个数据项的关键字的值作为枢纽;
 *  2、可以选择任意一个数据项作为枢纽。为了简便,我们假设总是选择划分的子数组最右端的数据项作为枢纽;
 *  3、划分完成之后,如果枢纽被插入到左右子数组之间的分界处,那么枢纽就落在排序之后的最终位置上。
 */
public class QuickSort1 {

    private int[] theArray;
    private int nElems;

    public QuickSort1(int max)
    {
        theArray = new int[max];
        nElems = 0;
    }

    public void insert(int val)
    {
        theArray[nElems++] = val;
    }

    public void display()
    {
        for (int i = 0; i < nElems; i++)
            System.out.print(theArray[i] + " ");
        System.out.println();
    }

    public void quickSort()
    {
        recQuickSort(0, nElems - 1);
    }

    private void recQuickSort(int left, int right)
    {
        if (right - left <= 0)                                   //当数组的大小 <= 1时,认为已经有序
            return;
        else
        {
            int pivot = theArray[right];                         //选择数组的最右端数据项作为枢纽值

            int partition = partition(left, right, pivot);       //划分数组,使小于枢纽值的数据项在左边子数组,大于枢纽值的在右边子数组
            recQuickSort(left, partition - 1);                   //递归调用该方法对左边数组排序
            recQuickSort(partition + 1, right);                  //递归调用该方法对右边数组排序
        }
    }

    /**
     * 根据枢纽值划分数组
     * @return 完成划分后枢纽值的位置
     */
    private int partition(int left, int right, int pivot)
    {
        int leftPtr = left - 1;
        int rightPtr = right;

        while (true)
        {
            while (theArray[++leftPtr] < pivot)                 //由于枢纽值选取数组最右端,所以leftPtr不会发生数据越界
                ;
            while (rightPtr > 0 && theArray[--rightPtr] > pivot)
                ;

            if (leftPtr >= rightPtr)
                break;
            else
                swap(leftPtr, rightPtr);
        }
        swap(leftPtr, right);                                  //right是枢纽值所在的位置,即数组最右端,完成划分后,将枢纽值保存在分界处
        return leftPtr;
    }

    private void swap(int dex1, int dex2)
    {
        int temp = theArray[dex1];
        theArray[dex1] = theArray[dex2];
        theArray[dex2] = temp;
    }

    public static void main(String[] args) {
        int maxSize = 10;
        QuickSort1 arr = new QuickSort1(maxSize);

        for (int i = 0; i < maxSize; i++) {
            arr.insert((int)(Math.random() * 99));
        }

        arr.display();
        arr.quickSort();
        arr.display();
    }
}

使用三数据项取中法选取枢纽值

package com.jikefriend.arraysort;

/**
 * 快速排序,O(N*logN),最坏O(N^2)
 * “三数据项取中”划分
 *  找到数组里第一个、最后一个以及中间位置数据项的居中数据项值,作为枢纽值。
 *
 * 由于在选择枢纽的同时,对该三个数据项进行了排序,保证了最左端的数据项小于枢纽值,
 * 最右端的大于枢纽值,两端指针避免的越界。
 */
public class QuickSort2 {

    private int[] theArray;
    private int nElems;

    public QuickSort2(int max)
    {
        theArray = new int[max];
        nElems = 0;
    }

    public void insert(int val)
    {
        theArray[nElems++] = val;
    }

    public void display()
    {
        for (int i = 0; i < nElems; i++)
            System.out.print(theArray[i] + " ");
        System.out.println();
    }

    public void quickSort()
    {
        recQuickSort(0, nElems - 1);
    }

    private void recQuickSort(int left, int right)
    {
        int size = right - left + 1;

        if (size < 10)                                           //当数组的大小 < 10时,使用插入排序
            insertionSort(left, right);
        else
        {
            int pivot = medianOf3(left, right);                  //三数据项取中来选取枢纽值

            int partition = partition(left, right, pivot);       //划分数组,使小于枢纽值的数据项在左边子数组,大于枢纽值的在右边子数组
            recQuickSort(left, partition - 1);                   //递归调用该方法对左边数组排序
            recQuickSort(partition + 1, right);                  //递归调用该方法对右边数组排序
        }
    }

    private int medianOf3(int left, int right)
    {
        int center = (left + right) / 2;

        if (theArray[left] > theArray[center])  //对left和center排序
            swap(left, center);

        if (theArray[left] > theArray[right])   //对left和right排序
            swap(left, right);

        if (theArray[center] > theArray[right]) //对center和right排序
            swap(center, right);

        swap(center, right - 1);                //将选定的枢纽值放到数组最右端-1
        return theArray[right - 1];
    }

    /**
     * 根据枢纽值划分数组
     * @return 完成划分后枢纽值的位置
     */
    private int partition(int left, int right, int pivot)
    {
        int leftPtr = left;
        int rightPtr = right - 1;

        while (true)
        {
            while (theArray[++leftPtr] < pivot)                 //由于枢纽值选取数组最右端,所以leftPtr不会发生数据越界
                ;
            while (theArray[--rightPtr] > pivot)
                ;

            if (leftPtr >= rightPtr)
                break;
            else
                swap(leftPtr, rightPtr);
        }
        swap(leftPtr, right - 1);                                //right - 1是枢纽值所在的位置,即数组最右端-1,完成划分后,将枢纽值保存在分界处
        return leftPtr;
    }
    
    private void swap(int dex1, int dex2)
    {
        int temp = theArray[dex1];
        theArray[dex1] = theArray[dex2];
        theArray[dex2] = temp;
    }

    /**
     * 插入排序,处理小划分
     */
    private void insertionSort(int left, int right)
    {
        int in, out;

        for (out = left + 1; out <= right; out++)
        {
            int temp = theArray[out];
            in = out;

            while (in > left && theArray[in -1] >= temp)
            {
                theArray[in] = theArray[in - 1];
                --in;
            }
            theArray[in] = temp;
        }
    }

    public static void main(String[] args) {
        int maxSize = 10;
        QuickSort2 arr = new QuickSort2(maxSize);

        for (int i = 0; i < maxSize; i++) {
            arr.insert((int)(Math.random() * 99));
        }

        arr.display();
        arr.quickSort();
        arr.display();
    }
}

摘自《 Java 数据结构 算法 (第二版)》 [美] Robert Lafore 著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值