关闭

交换式排序算法之快速排序法-java实现讲解

标签: 算法java快速排序
558人阅读 评论(0) 收藏 举报
分类:

转载请注明出处


先介绍所谓分治法

是指将一个问题分成若干个子问题分而治之,是很多高效算法的基础。这些子问题有不重叠的特点。
分治法的精髓:
分–将问题分解为规模更小的子问题;
治–将这些规模更小的子问题逐个击破;
合–将已解决的子问题合并,最终得出“母”问题的解;


分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,然后可以递归地解这些子问题,最后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
分治法所能解决的问题一般具有以下几个特征:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。


分治法在每一层递归上都有三个步骤:
分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
合并:将各个子问题的解合并为原问题的解。

各个子问题的规模应该怎样才为适当?
答: 但人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。换句话说,将一个问题分成大小相等的k个子问题的处理方法是行之有效的。许多问题可以取 k = 2。这种使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。

关于分治法详情见于csdn博客
快速排序法就是运用了分治法的思想,用一个轴值将待排序的序列分成两个子序列,然后对子序列可以独立运用同样的思想分割。最后得到可以求解的递归出口。

快速排序法算法思想

1、选择轴值(pivot),把所有小于pivot的放在它左边,把所有大于pivot的数放在它右边

子序列L 轴值pivot 子序列R
子序列 L,比轴值都小 轴值pivot 子序列R,比轴值都大

这两个子序列L R里面不管是不是有序的,这三块总体是有序的,
2、将待排序的序列分成两个子序列L和R; 使得L序列所有记录都小于等于轴值pivot,R序列所有记录都大于等于轴值pivot。
3、对子序列L 和 R分别在进行快速排序法(递归)

轴值选择
选中轴值尽可能使子序列L 、R长度相等
选择策略
选择最左边/右边记录
选择中间元素
随机选择RQS

源码实现了两种快速排序算法实现。

package com.PengRong.A;
import java.util.*;

public class DemoQuickSort {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int len=10000000;
        int[] arr =new int[len];
        //随机产生十万个数据
        for(int i=0; i<arr.length;i++)
        {
            arr[i] =(int)(Math.random()*1000000);
        }

        //int[] arr ={ 5,8,3,4,10,10,-2,50,70,89,76,0};
        QuickSort qs =new QuickSort();
        qs.GetSystemTime();
        //qs.Sort(0, arr.length-1, arr);
        qs.Sort(arr, 0, arr.length - 1);
        qs.GetSystemTime();
        //qs.Show(arr);
    }

}

class QuickSort
{
    //打印当前数组
    public void Show(int[] arr)
    {
        for(int i=0; i< arr.length; i++ )
        System.out.println("arr[" + i +"]=" + arr[i]);
    }
    //获得当前系统时间
    public void GetSystemTime()
    {
        Calendar cal =Calendar.getInstance();
        System.out.println(cal.getTime());
    }


    /**
     * @author PengRong
     * @param Left 数组的左下标
     * @param Right 数组的右下标
     * @param arr   数组的引用
     * @功能 快速排序法  是快速排序算法实现方法一
     */
    public void Sort(int Left, int Right, int[] arr)
    {
        int L = Left;
        int R = Right;
        //取轴值
        int pivot =arr[(Left + Right)/2];
        int temp = 0;

        //这里是对待排序序列的分割逻辑;这个逻辑比较晦涩难以理解 

        while(L<R)
        {
            //从左边开始遍历,找到第一个大于或等于pivot的数组元素
            while(arr[L] < pivot )
            {
                L++;
            }
            //从右边开始遍历,找到第一个小于等于pivot的数组元素
            while(arr[R] > pivot )
            {
                R--;
            }
            //如果遍历数组元素的时候使得左边的下标L大于或等于右边的下标R;那么退出while循环
            if( L >= R )
            {
                break;
            }
            //交换arr[L] 和arr[R];交换数据的时候呢指针不变
            if(arr[L] != arr[R])
            {
                temp = arr[L];
                arr[L] = arr[R];
                arr[R] = temp;
            }
            //如果是轴值和两边的下标为L R的元素交换,那么R和L 都要更新一下。
            if(arr[L] == pivot)
            {
                --R;
            }
            if(arr[R] == pivot)
            {
                ++L;
            }

        }

        //如果最终L和R指针重合了,那么L加1,R减1
        if( L == R)
        {
            L++;
            R--;
        }


        if(Left < R)
            Sort( Left, R, arr);
        if(Right > L )
            Sort( L, Right, arr);
    }

  //另外一种快速排序算法实现

    /**
     * @author PengRong
     * @param arr
     * @param left数组左下标
     * @param right数组右下标
     * @功能:快速排序的另外一种解法
     */
    public void Sort(int arr[], int left, int right)
    {
        //arr是待排序数组,left ,right分别为数组两端下标
        //如果子序列只有0或1个记录,就不需要排序
        if(left >= right)
            return;
        //选择轴值下标
        int pivot = SelectPivot(left, right);
        //分割前先将轴值与数组末端元素交换,交换这两个值;
       swap(arr, pivot, right);
        //对记录进行分割,返回轴值最终存放的下标
       pivot =Partition(arr, left, right);


       //对轴值左边的子序列进行递归快速排序
      Sort(arr, left, pivot - 1);
      //对轴值右边的子序列进行递归快速排序
      Sort(arr, pivot+1, right );

    }

    /**
     * @author PengRong
     * @param left数组左下标
     * @param right数组右下标
     * @return 返回选择轴值下标值
     * @功能:在一个待排序的数组序列中选择轴值的下标
     */
    private int SelectPivot(int left, int right)
    {
        //参数left, right分别表示序列的左右端下标号
        //选择中间记录作为轴值
          return (left + right)/2;
    }


    /**
     * @author PengRong
     * @param arr
     * @param pivot一个数组元素下标
     * @param right另外一个数组元素下标
     * 功能:交换一个int 型数组的两个元素
     */
    private void swap(int arr[], int pivot, int right)
    {
        int temp = arr[pivot];
        arr[pivot] =arr[right];
        arr[right] = temp;

    }

    /**
     * @author PengRong
     * @param arr数组名
     * @param left数组左下标
     * @param right数组右下标
     * @return 返回的是轴值的下标
     * @功能:快速排序的分割函数,经过这个分割函数后,轴值到达正确位置
     */
    private int Partition(int arr[], int left, int right)
    {
         int tempPivot =0;
         //定义两个游标变量,索引数组左右两边的元素
         int i = left;
         int j = right;
        //将轴值存放在临时变量中,那么 j 位置就空了;等下的while循环先从左边开始
        tempPivot =arr[j];
        //开始分割,i,j不断向中间移动,直到相遇;
        while( i != j )
        {
            //  i 指针右移,直到找到一个大于等于轴值的记录
            while ( ( j>i )  &&  ( tempPivot >arr[i] )  )
            {
                i++;
            } 
            //如果i , j未相遇就将逆序元素换到右边空闲位置
            if( i<j )
            {
                arr[j] =arr[i];
                j--;        //j指针向左移动一步,为j向左移动创造条件
            }

            //j 指针左移,直到找到一个小于等于轴值的记录
            while(( j>i) && ( tempPivot < arr[j] )  )
            {
                j--;
            }
            //如果i,j未相遇就将逆序元素换到左边空闲位置
            if( i<j )
            {
                arr[i] =arr[j];
                i++;        //i指针向右移动一步
            }

        }
       arr[i] =tempPivot;
        return i; 
    }

}

对与第一种快速排序的方法用这个例子简单解释下。比如有一个数组arr,其中元素是如下

0 1 2 3 4 5 6
8 9 -5 -2 40 -9 15

调用Sort(0,6,arr)函数
L=Left=0;
R=Right=6;
pivot =-2;
通过第一次while循环发现当L=0, R=5时候需要交换数据;交换arr[0]和arr[5]元素的值;排序后是这样子的

0 1 2 3 4 5 6
-9 9 -5 -2 40 8 15

L=0;
R=5;因为交换的不是数据不是pivot所以不用更新L 和 R值
现在就到了这样的一个状态,然后开始第二次while循环
判断发现当L =1 , R=3时需要交换数据;这里有一个注意点就是R=3下标也是pivot;相当于arr[L] 和pivot交换。交换之后arr[L]就是pivot值了。交换之后成这个样子

0 1 2 3 4 5 6
-9 -2 -5 9 40 8 15

因为arr[L] =pivot,所以呢R –;R=2;
然后是第三次while循环L =1, R=2状态下执行while循环,发现L =1 , R=2就满足交换数据条件;交换后

0 1 2 3 4 5 6
-9 -5 -2 9 40 8 15
//这时候因为arr[R] =pivot,所以呢L++, L=2,所以呢L=R=2,因为L<R不
//等。所以跳出while循环。然后执行下面的语句L++,R--;然后就是L=3,R=1;
//最后调用Sort(0,1,arr);Sort(3,6,arr);分别对子序列用快速排序法。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:270600次
    • 积分:5736
    • 等级:
    • 排名:第4501名
    • 原创:263篇
    • 转载:11篇
    • 译文:1篇
    • 评论:37条
    博客专栏
    最新评论