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

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

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

直接给出一种实现代码:

        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);
		}
	}
}

 

posted on 2018-04-15 15:26  Sempron2800+  阅读(192)  评论(0编辑  收藏  举报