• 用分解的方式学算法005——快速排序


    如果在各种排序算法中,只学一种,那这种算法就是快速排序。

    正如它的名字,快速排序很快速,性能很好。而它也被评为“二十世纪十大最伟大的算法”之一。

    下面就来学习一下这个算法到底是怎么样的吧。

    直接给出一种实现代码:

            private int Division(int[] list, int left, int right)
            {
                // 以最左边的数(left)为基准
                int bs = list[left];
                while (left < right)
                {
                    // 从序列右端开始,向左遍历,直到找到小于base的数
                    while (left < right && list[right] >= bs)
                        right--;
                    // 找到了比base小的元素,将这个元素放到最左边的位置
                    list[left] = list[right];
    
                    // 从序列左端开始,向右遍历,直到找到大于base的数
                    while (left < right && list[left] <= bs)
                        left++;
                    // 找到了比base大的元素,将这个元素放到最右边的位置
                    list[right] = list[left];
                }
    
                // 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
                // 而left位置的右侧数值应该都比left大。
                list[left] = bs;
                return left;
            }
    
            public void QuickSort(int[] list, int left, int right)
            {
    
                // 左下标一定小于右下标,否则就越界了
                if (left < right)
                {
                    // 对数组进行分割,取出下次分割的基准标号
                    int bs = Division(list, left, right);
    
                    // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                    QuickSort(list, left, bs - 1);
    
                    // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                    QuickSort(list, bs + 1, right);
                }
            }
    

     我们的学习,并不是看过了“标准实现”代码就结束了,而是从这个时候才开始。把知识转化成自己理解的版本,才叫学会。

    首先看到这个代码,我的感觉就是,不知道怎么调用。

    下面给出调用的代码:

            public static void Main(string[] args)
            {
                //Console.WriteLine("Hello World!");
                int[] a={ 5, 1, 4, 8, 3, 9, 0, 2, 7, 6, 1, 2, 5 };
                SortClass sc = new SortClass();
                sc.QuickSort(a, 0, a.Length - 1);
                for (int i = 0; i < a.Length;i++)
                {
                    Console.Write(a[i] + " ");
                }
            }
    

     很明显,这个QuickSort是主干方法,也是递归方法。

    这个方法要表达什么呢?递归方法两要素:1递归(终止)条件,2递归方式。

    那这个递归方法的递归条件是什么呢?就是这个if语句,改写成更明显的方式:

            public void QuickSort(int[] list, int left, int right)
            {
                // 左下标一定小于右下标,否则就越界了
                if(left>=right)
                {
                    return;
                }
    
                // 对数组进行分割,取出下次分割的基准标号
                int bs = Division(list, left, right);
    
                // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                QuickSort(list, left, bs - 1);
    
                // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                QuickSort(list, bs + 1, right);
            }
    

     但是这个输入参数太丑了,把它隐藏起来,使用重载方式:

            public void QuickSort(int[] list)
            {
                int left = 0;
                int right = list.Length - 1;
                QuickSort(list, left, right);
            }
    
            public void QuickSort(int[] list, int left, int right)
            {
                // 左下标一定小于右下标,否则就越界了
                if(left>=right)
                {
                    return;
                }
    
                // 对数组进行分割,取出下次分割的基准标号
                int bs = Division(list, left, right);
    
                // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                QuickSort(list, left, bs - 1);
    
                // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
                QuickSort(list, bs + 1, right);
            }
    

     这样调用的时候,就不用写第二个和第三个参数了,看起来舒服很多。

            public static void Main(string[] args)
            {
                //Console.WriteLine("Hello World!");
                int[] a={ 5, 1, 4, 8, 3, 9, 0, 2, 7, 6, 1, 2, 5 };
                SortClass sc = new SortClass();
                //sc.QuickSort(a, 0, a.Length - 1);
                sc.QuickSort(a);
                for (int i = 0; i < a.Length;i++)
                {
                    Console.Write(a[i] + " ");
                }
            }
    

    下面来分析核心的部分Division,这个方法是返回数组的一个元素的下标。这个元素左边的所有元素都不大于它;而这个元素的右边的元素都不小于它。也就是说这个元素的位置就是最终排好序后的最终位置。

            private int Division(int[] list, int left, int right)
            {
                // 以最左边的数(left)为基准
                int bs = list[left];
                while (left < right)
                {
                    // 从序列右端开始,向左遍历,直到找到小于base的数
                    while (left < right && list[right] >= bs)
                        right--;
                    // 找到了比base小的元素,将这个元素放到最左边的位置
                    list[left] = list[right];
    
                    // 从序列左端开始,向右遍历,直到找到大于base的数
                    while (left < right && list[left] <= bs)
                        left++;
                    // 找到了比base大的元素,将这个元素放到最右边的位置
                    list[right] = list[left];
                }
    
                // 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
                // 而left位置的右侧数值应该都比left大。
                list[left] = bs;
                return left;
            }
    

     快速排序的过程就是一边比较一边移动,

    第一步:选定left索引的元素为基准值,从数组的右边向左边遍历,依次取出各值与基准值进行比较,如果发现某个值小于基准值,则停止遍历。将这个值放到数组left索引处。

    第二步:再从left索引处(值已经更新),向右进行遍历,依次取出各值与基准值进行比较,如果发现某个值大于基准值,则停止遍历。将这个值放到数组right索引处。

    然后重复进行这两步,一直到不满足left<right条件则停止。这时候left==right,而这个left和right的值,就是基准值最终应该排的位置。并且,在确定这个位置的同时,获得了两个子序列。它左侧的值都不大于它,它右侧的值都不小于它。

    得到这个基准位置后,将其左右两侧的子序列,用递归的方式进行再切分。

    多次递归调用,最终会将所有的元素都排好序。这时,算法也就完成了。

    总结:快速排序是通过递归的方式实现的,它通过寻找切分点,同时完成了确定位置和移动元素两件事,效率是非常高的。

    这个算法需要重点掌握。

    我想,学习算法关键的是学习思想。通过学习快速排序,我觉得有两方面思想可以借鉴:

    一是通过切分点的思想,将当前已排好序的元素从原问题中“剔除”,

    二是“链式”跳转的这种感觉,对于处理类似的问题可以借鉴快速排序的写法。

    最后给出完整代码:

    package asen.yang;
    
    public class quickSort {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		int[] x = { 9, 7, 5, 3, 1, 8, 6, 4, 2, 0 };
    		QuickSort(x);
    		for (int i = 0; i < x.length; i++) {
    			System.out.print(x[i] + " ");
    		}
    	}
    
    	private static int Division(int[] list, int left, int right) {
    		// 以最左边的数(left)为基准
    		int bs = list[left];
    		while (left < right) {
    			// 从序列右端开始,向左遍历,直到找到小于base的数
    			while (left < right && list[right] >= bs)
    				right--;
    			// 找到了比base小的元素,将这个元素放到最左边的位置
    			list[left] = list[right];
    
    			// 从序列左端开始,向右遍历,直到找到大于base的数
    			while (left < right && list[left] <= bs)
    				left++;
    			// 找到了比base大的元素,将这个元素放到最右边的位置
    			list[right] = list[left];
    		}
    
    		// 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
    		// 而left位置的右侧数值应该都比left大。
    		list[left] = bs;
    		return left;
    	}
    
    	public static void QuickSort(int[] list) {
    		int left = 0;
    		int right = list.length - 1;
    		QuickSort(list, left, right);
    	}
    
    	public static void QuickSort(int[] list, int left, int right) {
    
    		// 左下标一定小于右下标,否则就越界了
    		if (left < right) {
    			// 对数组进行分割,取出下次分割的基准标号
    			int bs = Division(list, left, right);
    
    			// 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
    			QuickSort(list, left, bs - 1);
    
    			// 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
    			QuickSort(list, bs + 1, right);
    		}
    	}
    }
    
  • 相关阅读:
    JS搞基指南----延迟对象入门提高资料整理
    JavaScript使用自定义事件实现简单的模块化开发
    nodeJS+bootstarp+mongodb整一个TODO小例子
    nodeJS+express+Jade写一个局域网聊天应用(node基础)
    jQ1.5源码注释以及解读RE
    jQ1.5中的事件系统(低版本的事件系统)
    JS中的事件类型和事件属性的基础知识
    [转][mysql]创建函数失败(1418错误)mysql双主模式导致的问题
    MySQL数据库导入错误:ERROR 1064 (42000) 和 ERROR at line xx:
    Vmware由于centos升级内核不可运行(C header files matching your running kernel were not found)的解决方案
  • 原文地址:https://www.cnblogs.com/asenyang/p/8832825.html
Copyright © 2020-2023  润新知