可视化的快速排序(JAVA)

可视化的快速排序(JAVA)

看了《算法第四版》后,想要熟练下自己对排序算法的理解,就选择用快速排序来练手,并运用了《算法第四版》提供的API,以实现排序算法的可视化。
在众多排序算法中,快速排序是其中有很高效率的一种,同时也是较为复杂的排序算法。

快速排序的思想:

快速排序是一种分治的排序算法。其思想是将一段无序的数据进行切分后,在分别对切分后的两段数据进行排序,对这两段数据都进行再一次的切分后分别排序,不断循环至完全排序完成,这个过程可通过递归实现。

基础函数编程:

先实现较为基础的函数方法(比较大小,交换数据,是否排序成功):

/*比较大小*/
public boolean less(Comparable x,Comparable y){
		return x.compareTo(y)<0;
}

/*数据交换*/
public void change(Comparable[] a,int i,int j){
		Comparable temp = a[i];
		a[i] = a[j];
		a[j] = temp;
}

/*判断是否完成排序*/
public boolean isSort(Comparable[] a){
	for(int i=0;i<a.length-1;i++){
		if(less(a[i+1],a[i]))
			return false;
	}
	return true;
}

切分函数编程及分析

基础函数写好后,进行核心代码编程。由快速排序算法的性质可知,需要有一个切分方法对数据进行切分。(思路:既然要进行切分,就需要找一个进行比较的数据,这里我用每段数据的最后一个数据作为比较的临时数据,切分的主要目的是将比该临时数据小的数据放置其前面,将比该临时数据大的数据放置其后面,因此需要循环来实现。该切分方法还需要返回该临时数据的位置数字,才能将其传给下一次切分作为其切分的临界点)

public int partition(Comparable[] a,int lo, int hi){
		int i=lo-1,j=hi;
		Comparable temp = a[hi];
		while(true){
			//从左到右遍历
			while(less(a[++i],temp))//当大于或等于时都退出循环
				if(i==hi) break;//防止越界
			//右到左
			while(less(temp,a[--j]))
				if(j==lo) break;
			if(i>=j) break;
			change(a,i,j);
		}
		change(a,hi,i);//i位置的值比较大,temp值是在最后,故将i位置的值交换至最后位置
		return i;
	}

切分( partition)方法有几个需要注意的点,都在注释中提醒了,由于快速排序的主要缺点是非常脆弱,在实现时要非常小心才能避免低劣的性能。
1.
在从左往右遍历数据是,当大于或等于临时数据时都要将其进行位置交换操作,因为这样可以避免算法的运行时间变为平方级别(在含有大量重复值时,切分效率及其低效。PS:《算法第四版》练习2.3.11),从右往左遍历同理。

while(less(a[++i],temp))

2.
在遍历的过程中,要注意终止条件,如用判断语句:

if(i==hi)

判断是否越界,防止访问到该数据段以外的数据,导致排序出错。
3.
判断左右遍历是否已经有交汇处以终止循环,使用判断语句:

f(i>=j)

至此,排序算法的核心代码已经分析完毕。

排序代码:

public void sort(Comparable[] a){
		/*打乱原有数据*/
		StdRandom.shuffle(a);
		sort(a,0,a.length-1);
		if(isSort(a))
			System.out.println("success!");
	}
	
	public void sort(Comparable[] a,int lo,int hi){
		if(lo>=hi)
			return;
		int j=partition(a,lo,hi);//切开
		sort(a,lo,j-1);
		sort(a,j+1,hi);
	}

排序(sort)代码运用了递归的思想以实现不断地切分,并通过切分(partition)函数返回的值传递给下一次排序函数。由于快速排序存在一个潜在的缺点:在切分不平衡时可能会非常低效。例如,如果第一次从最小元素切分,第二次从第二小元素切分,如此这般,每次调用只会移除一个元素,这会导致一个大数组需要切分很多次。因此需要在排序前将数据打乱以防止这种可能性。

StdRandom.shuffle(a);

(改代码使用了《算法第四版》提供的API)

排序过程可视化

为了将排序的过程可视化,使用了《算法第四版》API写出将数据转化为条形统计图的方法。(思路:一个将画板指定条形数据清空的方法(toWhite)和一个将指定条形数据重新画在画板上的方法(toBlack))

public static void toWhite(Comparable[] a,int i,int j){
	StdDraw.setPenColor(StdDraw.WHITE);
	double x1 = 1.0*i/a.length;
	double y1 = (double)a[i]/2.0;

	double x2 = 1.0*j/a.length;
	double y2 = (double)a[j]/2.0;
	
	double rw = 0.5/a.length;
	StdDraw.filledRectangle(x1, y1, rw, 1);/*通过画白色的新条形体将原有条形体掩盖*/
	StdDraw.filledRectangle(x2, y2, rw, 1);
	
	/*延时*/
	try {
		Thread.sleep(200);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}


public static void toBlack(Comparable[] a,int i,int j){
	StdDraw.setPenColor(StdDraw.BLACK);
	double x1 = 1.0*i/a.length;
	double y1 = (double)a[i]/2.0;
	double rh1 = (double)a[i]/2.0;
	
	double x2 = 1.0*j/a.length;
	double y2 = (double)a[j]/2.0;
	double rh2 = (double)a[j]/2.0;
	double rw = 0.5/a.length;
	StdDraw.filledRectangle(x1, y1, rw, rh1);
	StdDraw.filledRectangle(x2, y2, rw, rh2);
	
	try {
		Thread.sleep(200);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

由于需要在排序过程中一边改变条形图,故需要在快速排序代码中进行绘图代码的插入,主要是在切分函数进行修改:

public int partition(Comparable[] a,int lo, int hi){
		int i=lo-1,j=hi;
		Comparable temp = a[hi];
		while(true){
			while(less(a[++i],temp))/
				if(i==hi) break;
			while(less(temp,a[--j]))
				if(j==lo) break;
			if(i>=j) break;
			Draw.toWhite(a, i, j);/*数据交换前清除对于的条形数据*/
			change(a,i,j);
			Draw.toBlack(a, i, j);/*画上交换后的条形数据*/
		}
		Draw.toWhite(a, i, hi);/*数据交换前清除对于的条形数据*/
		change(a,hi,i);
		Draw.toBlack(a, i, hi);/*画上交换后的条形数据*/
		return i;
	}

生成随机数据方法:

public class Random {
	private Comparable[] num;
	
	public Comparable[] output(int n){
		num = new Comparable[n];
		for(int i=0;i<n;i++){
			num[i] = StdRandom.random();/*运用到算法API*/
		}
		return num;
	}

主函数代码编写:

public static void main(String[] args) {
		int N=50;
		Random r = new Random();
		Comparable[] num = r.output(N);
		
		for(int i=0;i<num.length;i++){
			double x=1.0*i/N;
			double y = (double)num[i]/2.0;
			double rw = 0.5/N;
			double rh = (double)num[i]/2.0;
			StdDraw.filledRectangle(x, y, rw, rh);
		}
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//排序
		Quick q = new Quick();
		q.sort(num);
		
	}

至此,所有代码已经变现完成,运行main函数查看其效果。
在这里插入图片描述

PS:不知是否绘画函数插入在切分函数位置有所不妥,在某些情况下绘画的排序过程会出现一两个错误的条形数据,但排序后的数据是没有错误的!(故可以排除排序算法错误)只是绘画过程有错误,希望有能够有网友有指出其原因,万分感谢。(错误情况如下)
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41797857/article/details/83451231
今日推荐