Java编程实现快速排序及优化代码详解

普通快速排序

找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base。再分为两个子数组的排序。如此递归下去。

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1);
	}
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right) {
		if (left >= right) return;
		int p = partition(arr, left, right);
		sort(arr, left, p - 1);
		sort(arr, p + 1, right);
	}
	private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
		T base = arr[left];
		int j = left;
		for (int i = left + 1; i <= right; i++) {
			if (base.compareTo(arr[i]) > 0) {
				j++;
				swap(arr, j, i);
			}
		}
		swap(arr, left, j);
		return j;
		//返回一躺排序后基准值的下角标
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

快速排序优化:随机选取基准值base

在数组几乎有序时,快排性能不好(因为每趟排序后,左右两个子递归规模相差悬殊,大的那部分最后很可能会达到O(n^2))。

解决:基准值随机地选取,而不是每次都取第一个数。这样就不会受“几乎有序的数组”的干扰了。但是对“几乎乱序的数组”的排序性能可能会稍微下降,至少多了排序前交换的那部分,乱序时这个交换没有意义...有很多“运气”成分..

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1);
	}
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right) {
		if (left >= right) return;
		int p = partition(arr, left, right);
		sort(arr, left, p - 1);
		sort(arr, p + 1, right);
	}
	private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
		//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
		//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
		swap(arr,left,(int)(Math.random()*(right - left + 1)+left));
		T base = arr[left];
		int j = left;
		for (int i = left + 1; i <= right; i++) {
			if (base.compareTo(arr[i]) > 0) {
				j++;
				swap(arr, j, i);
			}
		}
		swap(arr, left, j);
		return j;
		//返回一躺排序后,基准值的下角标
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

快速排序继续优化:配合着使用插入排序

快排是不断减小问题规模来解决子问题的,需要不断递归。但是递归到规模足够小时,如果继续采用这种 不稳定+递归 的方式执行下去,效率不见得会很好。

所以当问题规模较小时,近乎有序时,插入排序表现的很好。Java自带的Arrays.sort()里经常能看到这样的注释:“Use insertion sort on tiny arrays”,“Insertion sort on smallest arrays”

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1, 16);
	}
	/**
   * @param arr  待排序的数组
   * @param left 左闭
   * @param right 右闭
   * @param k   当快排递归到子问题的规模 <= k 时,采用插入排序优化
   * @param <T>  泛型,待排序可比较类型
   */
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
		// 规模小时采用插入排序
		if (right - left <= k) {
			insertionSort(arr, left, right);
			return;
		}
		int p = partition(arr, left, right);
		sort(arr, left, p - 1, k);
		sort(arr, p + 1, right, k);
	}
	public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
		for (int i = l + 1; i <= r; i++) {
			T cur = arr[i];
			int j = i - 1;
			for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
				arr[j + 1] = arr[j];
			}
			arr[j + 1] = cur;
		}
	}
	private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
		//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
		//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
		swap(arr, left, (int) (Math.random() * (right - left + 1) + left));
		T base = arr[left];
		int j = left;
		for (int i = left + 1; i <= right; i++) {
			if (base.compareTo(arr[i]) > 0) {
				j++;
				swap(arr, j, i);
			}
		}
		swap(arr, left, j);
		return j;
		//返回一躺排序后,基准值的下角标
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

快速排序继续优化:两路快排

在最开始的普通快速排序说过,让基准值base左边的都比base小,而base右边的都大于等于base。等于base的这些会聚集到右侧(或者稍微改改大小关系就会聚集到左侧)。总之就会聚集到一边。这样在数组中重复数字很多的时候,就又会导致两边子递归规模差距悬殊的情况。这时想把等于base的那些数分派到base两边,而不是让他们聚集到一起。

(注:测试代码的时候,最好把插入排序那部分注视掉,向我下面代码中那样...不然数据量小于k=16的时候执行的是插入排序.....)

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1, 16);
	}
	/**
   * @param arr  待排序的数组
   * @param left 左闭
   * @param right 右闭
   * @param k   当快排递归到子问题的规模 <= k 时,采用插入排序优化
   * @param <T>  泛型,待排序可比较类型
   */
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
		// 规模小时采用插入排序
		//    if (right - left <= k) {
		//      insertionSort(arr, left, right);
		//      return;
		//    }
		if (left >= right) return;
		int p = partition(arr, left, right);
		sort(arr, left, p - 1, k);
		sort(arr, p + 1, right, k);
	}
	public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
		for (int i = l + 1; i <= r; i++) {
			T cur = arr[i];
			int j = i - 1;
			for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
				arr[j + 1] = arr[j];
			}
			arr[j + 1] = cur;
		}
	}
	private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
		//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
		//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
		swap(arr, left, (int) (Math.random() * (right - left + 1) + left));
		T base = arr[left];
		//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序
		int i = left + 1;
		//对于上一行提到的[left+1.....right]区间,i表示 [left+1......i)左闭右开区间的值都小于等于base。
		int j = right;
		//对于上二行提到的[left+1.....right]区间,j表示 (j......right]左开右闭区间的值都大于等于base。
		while (true) {
			//从左到右扫描,扫描出第一个比base大的元素,然后i停在那里。
			while (i <= right && arr[i].compareTo(base) < 0) i++;
			//从右到左扫描,扫描出第一个比base小的元素,然后j停在那里。
			while (j >= left && arr[j].compareTo(base) > 0) j--;
			if (i > j) {
				//虽说是i>j,但其实都是以j=i-1为条件结束的
				break;
			}
			swap(arr, i++, j--);
		}
		swap(arr, left, j);
		return j;
		//返回一躺排序后,基准值的下角标
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

快速排序继续优化:两路快排 不用swap,用交换

上面的两路在找到大于base的值和小于base的值时,用的是swap()方法来进行交换。两数交换涉及到第三个变量temp的操作,多了读写操作。接下来用直接赋值的方法,把小于的放到右边,大于的放到左边,当i和j相遇时,那个位置就是base该放的地方。至此一趟完成。递归即可。

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1, 16);
	}
	/**
   * @param arr  待排序的数组
   * @param left 左闭
   * @param right 右闭
   * @param k   当快排递归到子问题的规模 <= k 时,采用插入排序优化
   * @param <T>  泛型,待排序可比较类型
   */
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
		// 规模小时采用插入排序
		//    if (right - left <= k) {
		//      insertionSort(arr, left, right);
		//      return;
		//    }
		if (left >= right) return;
		int p = partition(arr, left, right);
		sort(arr, left, p - 1, k);
		sort(arr, p + 1, right, k);
	}
	public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
		for (int i = l + 1; i <= r; i++) {
			T cur = arr[i];
			int j = i - 1;
			for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
				arr[j + 1] = arr[j];
			}
			arr[j + 1] = cur;
		}
	}
	private static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
		//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
		//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
		swap(arr, left, (int) (Math.random() * (right - left + 1) + left));
		T base = arr[left];
		//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序
		int i = left;
		//对于上一行提到的[left+1.....right]区间,i表示 [left+1......i)左闭右开区间的值都小于等于base。
		int j = right;
		//对于上二行提到的[left+1.....right]区间,j表示 (j......right]左开右闭区间的值都大于等于base。
		while (i < j) {
			//从右到左扫描,扫描出第一个比base小的元素,然后j停在那里。
			while (j > i && arr[j].compareTo(base) > 0) j--;
			arr[i] = arr[j];
			//从左到右扫描,扫描出第一个比base大的元素,然后i停在那里。
			while (i < j && arr[i].compareTo(base) < 0) i++;
			arr[j] = arr[i];
		}
		arr[j] = base;
		return j;
		//返回一躺排序后,基准值的下角标
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

快速排序继续优化:当大量数据,且重复数多时,用三路快排

把数组分为三路,第一路都比base小,第二路都等于base,第三路都大于base。

用指针从前到后扫描,如果:

1.cur指向的数小于base,那么:交换arr[cur]和arr[i]的值,然后i++,cur++。

2.cur指向的数等于base,那么:cur++

3.cur指向的数大于base,那么:交换arr[cur]和arr[j]的值,然后j--。

当cur>j的时候说明三路都已经完成。

public class QuickSort {
	public static <T extends Comparable<? super T>> void sort(T[] arr) {
		sort(arr, 0, arr.length - 1, 16);
	}
	/**
   * @param arr  待排序的数组
   * @param left 左闭
   * @param right 右闭
   * @param k   当快排递归到子问题的规模 <= k 时,采用插入排序优化
   * @param <T>  泛型,待排序可比较类型
   */
	public static <T extends Comparable<? super T>> void sort(T[] arr, int left, int right, int k) {
		// 规模小时采用插入排序
		//    if (right - left <= k) {
		//      insertionSort(arr, left, right);
		//      return;
		//    }
		if (left >= right) return;
		int[] ret = partition(arr, left, right);
		sort(arr, left, ret[0], k);
		sort(arr, ret[1], right, k);
	}
	public static <T extends Comparable<? super T>> void insertionSort(T[] arr, int l, int r) {
		for (int i = l + 1; i <= r; i++) {
			T cur = arr[i];
			int j = i - 1;
			for (; j >= 0 && cur.compareTo(arr[j]) < 0; j--) {
				arr[j + 1] = arr[j];
			}
			arr[j + 1] = cur;
		}
	}
	/**
   * @param arr  待排序的数组
   * @param left 待排序数组的左边界
   * @param right 待排序数组的右边界
   * @param <T>  泛型
   * @return
   */
	private static <T extends Comparable<? super T>> int[] partition(T[] arr, int left, int right) {
		//排序前,先让基准值和随机的一个数进行交换。这样,基准值就有随机性。
		//就不至于在数组相对有序时,导致左右两边的递归规模不一致,产生最坏时间复杂度
		swap(arr, left, (int) (Math.random() * (right - left + 1) + left));
		T base = arr[left];
		//基准值,每次都把这个基准值抛出去,看成[left+1.....right]左闭右闭区间的排序
		//三路快排分为下面这三个路(区间)
		int i = left;
		// left表示,[lleft...left) 左闭右开区间里的数都比base小
		int j = right;
		// left表示,(rright...right] 左开右闭区间里的数都比base大
		int cur = i;
		//用cur来遍历数组。[left...cur)左闭右开区间里的数都等于base
		while (cur <= j) {
			if (arr[cur].compareTo(base) == 0) {
				cur++;
			} else if (arr[cur].compareTo(base) < 0) {
				swap(arr, cur++, i++);
			} else {
				swap(arr, cur, j--);
			}
		}
		return new int[]{i - 1, j + 1};
		//[i...j]都等于base,子问题就只需要解决i左边和j右边就行了
	}
	public static void swap(Object[] arr, int i, int j) {
		if (i != j) {
			Object temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	private static void printArr(Object[] arr) {
		for (Object o : arr) {
			System.out.print(o);
			System.out.print("\t");
		}
		System.out.println();
	}
	public static void main(String args[]) {
		Integer[] arr = {3, 5, 1, 7, 2, 9, 8, 0, 4, 6};
		printArr(arr);
		//3  5  1  7  2  9  8  0  4  6
		sort(arr);
		printArr(arr);
		//0  1  2  3  4  5  6  7  8  9
	}
}

总结

以上就是本文关于Java编程实现快速排序及优化代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

(0)

相关推荐

  • Java编程中快速排序算法的实现及相关算法优化

    时间复杂度 平均情况:O(nlgn) 最坏情况:O(n*n),发生在当数据已经是排序状态时 快排算法的基本原理 1.从数据中选取一个值a[i]作为参考 2.以a[i] 为参考,将数据分成2部分:P1.P2,P1中的数据全部≤a[i],P2中的数据全部>a[i],数据变为{{P1}{a[i]}{P2}} 3.将P1.P2重复上述步骤,直到各部分中只剩1个数据 4.数据完成升序排列 基本示例: 原始数据: {3,9,8,5,2,1,6} 第1步:选取第1个数据:3 第2步:将数据分成2部分,左边≤3

  • Java编程实现快速排序及优化代码详解

    普通快速排序 找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base.再分为两个子数组的排序.如此递归下去. public class QuickSort { public static <T extends Comparable<? super T>> void sort(T[] arr) { sort(arr, 0, arr.length - 1); } public static <T extends Comparabl

  • Java编程多线程之共享数据代码详解

    本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保证各个线程的数据不交叉:一是多个线程间如何共享数据,保证数据的一致性. 线程范围内共享数据 自己实现的话,是定义一个Map,线程为键,数据为值,表中的每一项即是为每个线程准备的数据,这样在一个线程中数据是一致的. 例子 package com.iot.thread; import java.util.HashMap; import java.util.Map; import java.util.Random; /*

  • Java编程Webservice指定超时时间代码详解

    WebService是一种跨编程语言和跨操作系统平台的远程调用技术 所谓远程调用,就是一台计算机a上的一个程序可以调用到另外一台计算机b上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统(采用交互提问的方式来加深大家对此技术的理解). 远程调用技术有什么用呢?商场的POS机转账调用的转账方法的代码是在银行服务器上,还是在商场的pos机上呢?什么情况下可能用到远程调用技术呢?例如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第

  • Java编程访问权限的控制代码详解

    本文研究的主要是Java编程访问权限的控制的相关内容,具体介绍如下. 之前没去注意的修饰符,一般变量前面没添加,一个是不知道有什么用,一个是懒,后面遇到项目的时候就会发现私有和公有区别还是很大的. (1)首先是包名 使用一个类的时候,例如集合类,就需要引入这个包,然后再使用该包下面的类.如: package com.myown.iaiti; public class Print { static void print(String s){ System.out.println(s); } } 自

  • Java编程思想对象的容纳实例详解

    Java提供了容纳对象(或者对象的句柄)的多种方式,接下来我们具体看看都有哪些方式. 有两方面的问题将数组与其他集合类型区分开来:效率和类型.对于Java来说,为保存和访问一系列对象(实际是对象的句柄)数组,最有效的方法莫过于数组.数组实际代表一个简单的线性序列,它使得元素的访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在那个数组对象的"存在时间"内发生改变.可创建特定大小的一个数组,然后假如用光了存储空间,就再创建一个新数组,将所有句柄从

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

  • Java版超大整数阶乘算法代码详解-10,0000级

    当计算超过20以上的阶乘时,阶乘的结果值往往会很大.一个很小的数字的阶乘结果就可能超过目前个人计算机的整数范围.如果需求很大的阶乘,比如1000以上完全无法用简单的递归方式去解决.在网上我看到很多用C.C++和C#写的一些关于大整数阶乘的算法,其中不乏经典但也有很多粗糙的文章.数组越界,一眼就可以看出程序本身无法运行.转载他人文章的时候,代码倒是仔细看看啊.唉,粗糙.过年了,在家闲来蛋疼,仔细分析分析,用Java实现了一个程序计算超大整数阶乘.思想取自网上,由我个人优化和改进. 这个方法采用"数

  • Java编程中的HashSet和BitSet详解

    Java编程中的HashSet和BitSet详解 我在Apache的开发邮件列表中发现一件很有趣的事,Apache Commons包的ArrayUtils类的removeElements方法,原先使用的HashSet现在换成了BitSet. HashSet<Integer> toRemove = new HashSet<Integer>(); for (Map.Entry<Character, MutableInt> e : occurrences.entrySet()

  • Java编程构造方法与对象的创建详解

    java构造方法与对象的创建 可以用类来声明对象,声明对象后必须创建对象 1构造方法 首先,我们来谈谈什么叫构造方法,既然都说了这是一个构造方法,那么很显然,它本质上就是一个方法. 那么,既然作为一个方法,它应该有方法的样子吧.它除了回调一个Class();之后,也没见它有其他的定义方法的代码呀?这是因为,在未对类自定义构造方法的情况下,编译器会自动在编译期为其添加默认的构造方法 (1)程序用类创建对象时,需要使用该类的构造方法 (2)类中构造方法的名字必须和类名完全相同,而且没有类型 (3)允

  • Java动态代理(设计模式)代码详解

    基础:需要具备面向对象设计思想,多态的思想,反射的思想: Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架.通过阅读本文,读者将会对Java动态代理机制有更加深入的理解.本文首先从Java动态代理的运行机制和特点出发,对其代码进行了分析,推演了动态生成类的内部实现. 代理模

随机推荐