Java基础——十大排序算法汇总

十大排序算法

快速排序

第一种经典排序

切分元素X,并且在此过程中排定该位元素X的位置。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Quick {
	public static void sort(Comparable[] a) {
		StdRandom.shuffle(a);//打乱数组,消除对输入的依赖
		sort(a, 0, a.length - 1);
	}

	public static void sort(Comparable[] a, int lo, int hi) {
		if (hi <= lo) return;
		int j = partition(a, lo, hi);
		sort(a, lo, j - 1);
		sort(a, j + 1, hi);
	}

	public static int partition(Comparable[] a, int lo, int hi) {
		int i = lo, j = hi + 1;  
	//左右两个指针分别进行扫描,注意这边j=hi+1可以保证a[--j]从a[hi]这个元素开始比较
	//而a[lo]这个元素作为基准值,所以可以从a[++i]这个元素开始
		Comparable v = a[lo];
		while (true) { //这个循环不会停,除非发现i>=j了
			//各自扫描
			while (less(a[++i], v)) { //从左往右,遇到大于等于v的元素就停下来
				if (i == hi) break;
			}
			while (less(v, a[--j])) { //从右往左遇到小于等于v的元素就停下来
				if (j == lo) break;
			}
			if (i >= j) break;
			exch(a,i,j);  //当左右两个指针都停下来后就交换,但前提是i<j
		}
		exch(a,lo,j);
	//最外层的while循环结束也就意味着都排好序了,注意exch(a,v,j)是错的!虽然v=a[lo],但v不是数组里的元素!
		return j;
	}

	public static void exch(Comparable[] a, int m, int n) {
		Comparable temp = a[m];
		a[m] = a[n];
		a[n] = temp;
	}

	public static boolean less(Comparable m, Comparable n) {
		return m.compareTo(n) < 0;
	}
}

注意事项

等号或者大于等于号的选择原因:

  • 1:
//各自扫描
   		while (less(a[++i], v)) {
    //从左往右,遇到大于等于v的元素就停下来
   			if (i == hi) break;
   		}
   		while (less(v, a[--j])) { 
   //从右往左遇到小于等于v的元素就停下来
   			if (j == lo) break;
   		}

这边lo和hi都是固定的数值,而i与j两个指针都是一步步移动的;而且我每次while循环执行条件满足时,会进行if语句的检查。因此,i与 j两个指针每次移动都会受到检查,不可能出现偷偷移动了好几步的情况。

  • 2:
	if (i >= j) break;
   		exch(a,i,j);  
   //当左右两个指针都停下来后就交换,但前提是i<j

这个条件是while循环结束的唯一办法。
i>j时,循环停止无需多言。
i=j时,为啥也要停止呢?
0 1 2
E C A
此时,E作为基准点,less(a[++i], v)从C开始与E比较,最后,i一直移动到了2这个位置停下( if (i == hi) break;
less( v, less[- -j] )却在2处就停下了,因为A就比E小。所以j指针也指向了2.
此时i与j互换值毫无意义,而且i==j代表了,i的左边都遍历过了,j的右边也都遍历过了,因此可以结束遍历了。
还有比如: E A B C E F G M也是
i会停在E处,j也会停在E处。
小细节

		while (less(a[++i], v)) {
		 //从左往右,遇到大于等于v的元素就停下来
   			if (i== hi) break;
   		}

如果一出门就发现该while循环的循环条件不满足,请问i初始值若为0的话,现在为几?
答:为1,若我不++,那a[++i]怎么和v比较发现不满足呢?

if (hi <= lo) return;

如果hi=lo,显然就不需要排序了,一个元素已经有序。关键是hi<lo,小于号取得到吗?
0 1 2
A C E
此时,i指针停在1处,j指针停在0处,于是return j,j=0;
接着调用sort(a, lo, j-1);此时lo=0,hi=0-1=-1,hi<lo。于是结束该sort方法。

while循环直接改成i<j,乍一看好像可以,但细细琢磨是不行的。排序结果有错。

 public static int partition(int[] a, int lo, int hi) {
			int i = lo, j = hi + 1;  
		//左右两个指针分别进行扫描,注意这边j=hi+1可以保证a[--j]从a[hi]这个元素开始比较
		//而a[lo]这个元素作为基准值,所以可以从a[++i]这个元素开始
			int v = a[lo];
			while (i<j) { //这个循环不会停,除非发现i>=j了
				//各自扫描
				while (less(a[++i], v)) { //从左往右,遇到大于等于v的元素就停下来
					if (i == hi) break;
				}
				while (less(v, a[--j])) { //从右往左遇到小于等于v的元素就停下来
					if (j == lo) break;
				}
//				if (i >= j) break;
				exch(a,i,j);  //当左右两个指针都停下来后就交换,但前提是i<j
			}
			exch(a,lo,j);
		//最外层的while循环结束也就意味着都排好序了,注意exch(a,v,j)是错的!虽然v=a[lo],但v不是数组里的元素!
			return j;
		}

第二种 三向切分的快速排序

基本原理:维护三个指针,lt、gt和 i,形成三个区间,分别是:
在这里插入图片描述

public class Quick3way {
	public static void sort(Comparable[] a) {
		StdRandom.shuffle(a);//打乱数组,消除对输入的依赖
		sort(a, 0, a.length - 1);
	}

	public static void sort(Comparable[] a, int lo, int hi) {
		if(hi<=lo) return;//如果少了这个条件,会发生StackOverFlowError.
		int lt=lo,i=lo+1,gt=hi;
		Comparable v=a[lo];
		while(i<=gt) {
			int cnt=a[i].compareTo(v);
			if(cnt<0) exch(a,i++,lt++);//发现a[i]比v小,而lt指向的是=v的元素,因此a[i]与a[lt]交换后,i指针指向的值与v相等了,
//	因此这个i处的值无须再拿出来比较,所以i++。同时,lt指向的值小于v了,所以lt++。
			else if(cnt>0) exch(a,i,gt--);//这里i指针在交换值后为什么不++呢?因为i指针左边的数的大小都是检查过的,即有序状态。
//	而这边呢exch与上面不同的是,我这里的目的仅仅是为了把这个>V的元素扔到后面去,同时把后面gt对应的一个元素放到当前i指针指向的位置,但我
//	并不知道调换到i指针所指位置的值的大小如何,i到gt,包括gt指向的值的大小是不知道的,这个区间处于未开发状态,因此i不能++,需要再对i所指向的值进行判断
			else i++;
		}
		sort(a,lo,lt-1);//中间lt到gt这一部分的值均与v相等,不会再被递归排序了,效率更高
		sort(a,gt+1,hi);
	}

在这里插入图片描述
注意事项
1.其实,这边else i++;这一步保证了,lt永远处于指向与V相等的元素所处区间的第一个位置,我其实还可以这么做:

		while(i<=gt) {
			int cnt=a[i].compareTo(v);
			if(cnt<=0) exch(a,i++,lt++);
			else if(cnt>0) exch(a,i,gt--);
		}

把cnt=0合并进去,这样也可以,但是lt的指向就不是一直指向与V相等的元素所处区间的第一个位置了,而且这样做没有意义,虽然代码少了一行,但显然多了交换的次数。
2.这边while循环的条件为什么可以取到i==gt呢?
因为上面说过,虽然我现在i指针跑到了gt那里,但gt指针所指向的元素的值的大小是未知的,所以还得进行判断。如果该元素的值判断发现小于或者等于V,那么i++,此时i=gt+1,就不满足while循环条件了。

归并排序

自顶向下的归并排序

在这里插入图片描述
在这里插入图片描述
主要思想:左边取尽取右边,右边取尽取左边,右边元素小于左边元素取右边,右边元素大于等于左边元素取左边(元素相等时,取左取右都可以完成任务,没区别)。
代码:

public class Merge {
	//private static Comparable[] aux; 当作参数传入更实用
	// 归并所需要的辅助数组,仅在次创建了这么一次,之后再无创建数组

	public static void sort(Comparable[] a) {
		Comparable aux = new Comparable[a.length];
		sort(a, aux, 0, a.length - 1);
	}

	public static void sort(Comparable[] a,Comparable[] aux, int lo, int hi) {
		if (lo >= hi) return;
		int mid = lo + (hi - lo) / 2;
		sort(a, aux, lo, mid);// 左半边排序
		sort(a, aux, mid + 1, hi);// 右半边排序
		merge(a, aux, lo, mid, hi);//左右两边都排序完后归并结果
	}

	public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
		int i = lo, j = mid + 1;
		for (int k = lo; k <= hi; k++) { //这里k<= hi不等价于k<a.length
			aux[k] = a[k];               //这里并未创建数组
		}
		for (int k = lo; k <= hi; k++) { //a数组里可能有值,aux可能会将a数组里的值给覆盖掉。
			if (i > mid) 			   a[k] = aux[j++];//这里为什么是i>mid,因为j=mid+1,即如果mid=4,那么4和5之间会有分界线!
													   //mid显然在左边分区,j指针是从右边分区第一个开始扫描的(j=mid+1)
			else if (j > hi)				   a[k] = aux[i++];
			else if (less(aux[i], aux[j]))  a[k] = aux[i++];//注意这边less里面是aux数组,不是a数组
			else 					   a[k] = aux[j++];
		}
	}

	public static boolean less(Comparable m, Comparable n) {
		return m.compareTo(n) < 0;
	}
}

注意点

  • 关于merge方法中,a[k]=aux[j++]等可能会将a数组里面的值覆盖掉的问题
    其实,不用担心,假如我在merge(a,lo,mid,hi)中,将a={1,3,7,5}这个数组传入了,不管这四个元素属于整个数组的哪一部分。我首先会将这四个元素复制到aux数组中(
 for (int k = lo; k <= hi; k++) {  			
   	 aux[k] = a[k];               
 }

)中,其次,再根据大小将aux中的数值赋值给a[k],此时,a数组中这四个元素的顺序大小就确定了,而且还是1,3,7,5这四个元素。不仅如此,这四个元素在整个数组中所占的区域也没有发生转移变动之类的,且由于我只改动了这四个元素,所以不会影响到其他元素的顺序和值的情况(局部有序)。

  • 不要和快排的每次排定一个元素混起来
        int mid=lo+(hi-lo)/2;
  		sort(a,lo,mid,aux);//这边的mid容易写成mid-1,那mid就不需要排序了?显然是错的。
  		sort(a,mid+1,hi,aux); 		
  		merge(a,lo,mid,hi,aux);
  • 需要注意的是,自顶向下的归并排序中,我传入的a数组一直都是同一个长度的数组,其元素可能会发生变化。主要是通过lo以及hi这两个限定了我对该数组的修改范围。如下图所示,这也就是为什么k<a.length是不对的,而应该k<=hi才对,因为很可能会扩大对数组的修改范围。因此可以看到,merge方法中,数组的复制以及遍历赋值都是从lo开始,hi结束。
    for(int k=lo;k<a.length;k++) {
   		if(i>mid) 					 a[j++]=aux[k];
   		else if(j>hi) 				 a[i++]=aux[k];
   		else if(less(a[i],a[j])) a[i++]=aux[k];
   		else 						 a[j++]=aux[k];
   	}
  • 如上图所示,a[j++]=aux[k]都与正确答案反过来了,稍微思考一下,就知道,肯定得是a[k]=aux[X++]这种,因为我目的是为了给a数组排序啊。其次需要注意的是,这里用到了三个参数(一个K,两个指针i,j)k是为了遍历数组给a数组赋值,i,j两个指针负责扫描判断。
  • merge方法中的第二个for循环中,不管出现何种情况,四个if 语句必定会执行一个,因此 一定要加上else if

自底向上的归并排序

相比自顶向下,自底向上的代码虽然更精简,但要难以理解一些。了解即可。两者的原理都还挺好理解。
在这里插入图片描述
在这里插入图片描述
不得不说,下面的代码有点难以理解,但可以取具体情况来加强理解和记忆。如sz=1和sz=2时的情况、mid取何值等。

public class MergeBu {
	private static Comparable[] aux;

	public static void sort(Comparable[] a) {
		int N = a.length;
		aux = new Comparable[N];
		for (int sz = 1; sz < N; sz *=2) {// 确实子数组的大小,是几几合并。如一一合并就是两个两个元素排序,二二合并就是四个元素四个元素排序
			for (int lo = 0; lo < N - sz; lo += sz + sz) { // lo子数组的索引
				int mid = lo + sz - 1;
				merge(a, lo, mid, Math.min(mid + sz, N - 1));
			}
		}
	}

	public static void merge(Comparable[] a, int mid, int lo, int hi) {
		int i = lo, j = mid + 1;
		for (int k = lo; k <= hi; k++) {
			aux[k] = a[k];
		}

		for (int k = lo; k <= hi; k++) {
			if (i > mid)
				a[k] = aux[j++];
			else if (j > hi)
				a[k] = aux[i++];
			else if (less(aux[i], aux[j]))
				a[k] = aux[i++];
			else
				a[k] = aux[j++];
		}
	}

	public static boolean less(Comparable m, Comparable n) {
		return m.compareTo(n) < 0;
	}
}

两种归并算法原理的图解

自顶向下的归并排序
在这里插入图片描述
自底向上的归并排序
在这里插入图片描述
参考链接:[https://www.cnblogs.com/nullzx/p/5968170.html]

堆排序

特点:排序时可以将需要排序的数组本身作为堆,因此无需任何额外空间。

细节:

		for (int k = n/2; k >= 1; k--)
            sink(pq, k, n);

这里为什么k=n/2,而不是k==n?
原因:见sink方法

	private static void sink(Comparable[] pq, int k, int n) {
        while (2*k <= n) { //若2*k>n,则说明,k所在结点无子节点
            int j = 2*k;
            if (j < n && less(pq, j, j+1)) j++;
            if (!less(pq, k, j)) break;
            exch(pq, k, j);
            k = j;
        }
    }

我在sink方法中传入了k,随后有j=2k,即会用子节点来和k所在的结点进行比较。如果我传入的k=n,那我2k>n肯定,该方法无法执行,直到2*k<=n,即k所在结点有子节点为止,显然,这前面一段遍历肯定无法进行,既然如此不如直接让k=n/2开始遍历,既不会浪费时间,也不会漏掉对子结点的判断。

堆排序图解:
在这里插入图片描述
完整代码:

public class HeapSort {
	public static void sort(Comparable[] pq) {
		int N = pq.length;// 如果不利用pq[0]这个元素的话,这里应该是pq.length+1,
//		而且得新建长度+1的数组,就打破了堆排序无需任何额外空间的优势
		for (int k = N / 2; k >= 1; k--) // 这边k不是数组的索引,而是堆中元素的“位置”
			sink(pq, k, N);
		while (N > 1) {
			exch(pq, 1, N--);
			sink(pq, 1, N);
		}
	}

	private static void sink(Comparable[] pq, int k, int N) {
		while (2 * k <= N) { // 为了保证有子节点才执行循环,若k所在位置没有子节点,那么2*k>n
			int j = 2 * k;
//			这边这个j<N是为了保证less(pq,j,j+1)可以执行、j+1不会发生越界
//			比如N=10,即传入的数组长度为10,k=N/2=5,所以j=10,下面的if语句判断j<N不成立,因此不执行。
			if (j < N && less(pq, j, j + 1)) 
				j++;
			if (!less(pq, k, j)) break;
			exch(pq, k, j);
			k = j;
		}
	}

	private static boolean less(Comparable[] pq, int i, int j) {
		// 以前的less方法,都是传入两个元素,然后比较。如果要传入索引的话,就必须同时传入相应的数组
		//这边索引-1,因为堆中的a[1~a.length]对应数组中的a[0~a.length-1],故需要将索引减一
		return pq[i - 1].compareTo(pq[j - 1]) < 0;
	}

	private static void exch(Object[] pq, int i, int j) {
//		这边索引-1的原因同less函数
		Object swap = pq[i - 1];
		pq[i - 1] = pq[j - 1];
		pq[j - 1] = swap;
	}

	public static void main(String[] args) {
		String[] a = new String[] { "S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E" };// 11个元素
		HeapSort.sort(a);
		for (String i : a) {
			System.out.print(i + " ");
		}
	}
}

主要思想是:

  1. 先构造一个堆有序的数组,并使最大元素位于数组的开头
  2. 第二步是将堆中最大的元素删除,然后放入堆缩小后数组中空出的位置。这个过程与选择排序类似,每次取出一个最大的或者最小的元素,但相对选择排序,所需要的比较次数要少得多,因为他寻找最大元素更加的高效。

插入排序

在数组元素较少,且部分有序的情况,效率极高。
在这里插入图片描述
下面这张图能很好的理解:
在这里插入图片描述
代码:

public class Insertion {
	public static void sort(Comparable[] a) {
		//将a[]按升序排列;
		int len = a.length;
		for (int i = 1; i < len; i++) {
			for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {//注意,这边j>0,而不是j<N
				exch(a, j, j - 1);
			}
		}
	}
}

该算法一路遍历,从左往右一路升序,遍历到第X时,X-1以及前面的元素均是从低到高已排好序。

希尔排序

原理:对于大规模的乱序数组,普通的插入排序很慢,如一个最小的元素在数组的最右端,她挪到最前面就需要N-1次移动,非常慢,慢的主要原因就是元素只能一点一点地移动,不能大范围移动。因此,产生了希尔排序,他可以交换不相邻的元素,使得任意间隔为h的元素都是有序的。所以希尔排序实质上就是插入排序的代码中将移动距离从1改为了h。

在这里插入图片描述
注意,h=4的话,两个元素之间只相隔了3个元素,而不是4个。
在这里插入图片描述
我的笔记
在这里插入图片描述
代码:

public class Shell {
	public static void sort(Comparable[] a) {

		int N = a.length;
		int h = 1;
		//首先分间隔
		while (h < N / 3)  h = 3 * h + 1;//h=1, 4, 13, 40, 121, 364, 1093... 注意这边是while,不是if
		// 这样保证了h不可能是3的倍数,且都比3的倍数大1,这样做能够保证下面h=h/3最终能够取到1.
		
		//然后插入排序
		while (h >= 1) {
		for (int i = h; i < N; i++) {
			for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) {
				exch(a, j, j - h);
			}
		}
		h = h / 3;// 上面可以取h=h*4+1,但这边要取成h=h/4,为了保证我所选择的h,在/4以后最后一位肯定能取到1.
				  // 最后一轮必须得是h=1来进行扫荡。 
		}
	}
}

易错点:
在这里插入图片描述
两层for循环外面少了一个while循环,导致的结果是,如果一开始h=13,那么循环完之后,h=h/3=4,因为初始值i=13,所以接着i=14开始。。。这显然完全紊乱了。
因此需要注意
希尔排序的两层for循环外面需要有一个while循环来控制间隔h,然后,h=h/3也要在两层for循环外面,while循环里面。

桶排序

基数排序

基数排序分为最低位优先法(LSD)和最高位优先法(MSD),最高位优先法相对复杂的多,下面仅介绍最低位优先法。
最低位优先法:按照个位-十位-百位的顺序排序
最高位优先法:按照百位-十位-各位的顺序排序
主要原理,一看就懂:
在这里插入图片描述
在这里插入图片描述
代码:

public class RadixSort {
	/**
	 * 从小到大排序
	 * 
	 * @param data 待排序数组
	 * @param d    表示最大的数有多少位
	 */
	public static void sort(int[] data, int d) {
		int n = 1;
		// 数组的第一维表示可能的余数0-9,第二位表示可能的最大长度,有可能我所有的数个位都是0。
		int[][] bask = new int[10][data.length];
		// 数组index[i]用来表示该位(个、十、百.......)是 i的数  的个数,即用来计数
		int[] index = new int[10];
		for (int i = 0; i < d; i++) {
			for (int j = 0; j < data.length; j++) { //遍历原数组
				int lsd = (data[j] / n) % 10;     //求得第n位所对应的数。
				bask[lsd][index[lsd]++] = data[j];
			}
				//index[lsd]++=index[lsd]+1;不过如果index[0]++是先求出index【0】再++.
//		类似于	int[] arr = { 1, 2, 3 };
//				for (int i = 0; i < arr.length; i++) {
//					if (i == 0) {
//						System.out.println("arr[i++]="+arr[i++]);//arr[i++]=1
//						System.out.println("i="+i); //i=1
//					}
//				}

			int pos = 0;
			for (int j = 0; j < 10; j++) { //这边j < 10,因为长度就是从0~9
				for (int k = 0; k < index[j]; k++) {
					data[pos++] = bask[j][k];
				}
				index[j] = 0; //把元素搬到data数组之后,把计数器对应的 位 清空
				//你可能会纳闷,为什么index数组清空了,而bask数组没清空,元素可都在里面呢,下面解释
			}
			n *= 10;
		}
	}

	public static void print(int array[]) {
		for (int j = 0; j < array.length; j++) {
			System.out.print(array[j] + " ");
		}
	}
	
	public static void main(String[] args) {
		int[] data = { 51, 944, 1, 9, 57, 366, 79, 6, 1, 345 };// 待排序数组
		sort(data, 3);
		print(data);
	}
}

注:

			for (int j = 0; j < 10; j++) { //这边j < 10,因为长度就是从0~9
				for (int k = 0; k < index[j]; k++) {
					data[pos++] = bask[j][k];
				}
				index[j] = 0; //把元素搬到data数组之后,把计数器对应的 位 清空
				//你可能会纳闷,为什么index数组清空了,而bask数组没清空,元素可都在里面呢,下面解释
			}

注:

  • index里面的变量是j,而j无论你遍历它的个位还是十位还是其他任何位,j的取值范围都是0~9
    所以你遍历完它的个位不清零的话,其他位会受到影响,因为
bask [lsd][index[lsd]++] = data[j];

,他会在原先基础上++
而bask数组元素不用清零。同样因为

bask [lsd][index[lsd]++] = data[j];

Index数组会重新计数,而且会覆盖原来的值。如果第一次按照个位排序的话,0位对应了 10,0,1000三个数,其次按照十位排序的话,0位对应了0,1000。10这个数对应了1位(按照十位排序的话)。那么此时0,1000两个数就覆盖了原来的10,0这两位数,而Index计数0位这个位置只有两个,因此遍历的时候根据index数组元素的值只会遍历两次,根本取不到原先位置的第三个值1000.

计数排序

思想:
计数排序是一种不基于比较的排序算法,主要思想是计算出待排序序列的最大值 maxValue 与 最小值 minValue,开辟一个长度为 maxValue - minValue + 1 的额外空间,统计待排序序列中每个元素的数量,记录在额外空间中,最后遍历一遍额外空间,按照顺序把每个元素赋值到原始序列中。
代码:

public class CountSort {
	public static void sort(int[] a) {
		int[] buckets=new int[n];//这边n的取值要注意一下,应该是a数组中的最大值-a数组中的最小值+1。
		//如0,1,2这个数组,那么我n就要取3(2-0+1)这边n没有传入,可以通过遍历数组找到数组的最大最小值从而求出n。
		for(int i:a) {
			buckets[i]++;
		}
		int k=0;
		for(int i=0;i<buckets.length;i++) { //遍历这个桶
			for(int j=0;j<buckets[i];j++) { //根据每个桶对应该元素的数量进行遍历
				a[k++]=i;
			}
		}
	}
}

冒泡排序

主要思想:

  1. 每次冒泡排序操作仅比较相邻的两个元素
  2. 每一轮冒泡排序完成后都必将至少排定其中的最大元素

这是第一层for循环的结果:
在这里插入图片描述
这是第二层for循环的结果:
在这里插入图片描述
代码:

public class BubbleSort {
	public static void sort(int[] arr) {
		int N = arr.length;		
		
		if (N <= 1) return ; // 如果只有一个元素就不用排序了
		 		//第一层for循环决定了有几轮冒泡排序
		for (int i = 0; i < N-1; ++i) {  //最坏的情况是要冒泡N-1轮,但可能不需要这么多次,数组就有序了
		//这边i<N-1也可以改成N,但没必要,N-1次就可以完成排序了。6个元素排了5次后,5个元素顺序确定了,那最后一个元素其实也就确定了
			// 因此添设了一个提前退出冒泡循环的标志位,即一次比较中没有交换任何元素,这个数组就已经是有序的了
			boolean flag = false;
			//第二层for循环决定了每次冒泡排序过程中需要比较的次数
			for (int j = 0; j < N - i - 1; ++j) { // 此处你可能会疑问的j<n-i-1,因为冒泡是把每轮循环中较大的数飘到后面,
				if (arr[j] > arr[j + 1]) { // 即这两个相邻的数是逆序的,交换
					exch(arr,j,j+1);
					flag = true;
				}
			}
			if (flag==false) break;// 第二个for循环中没有数据交换,表明整个数组已经有序,退出最外层的排序
		}
	}

	public static void exch(int[] arr,int m, int n) {
		int temp = arr[m];
		arr[m] = arr[n];
		arr[n] = temp;
	}

	public static void main(String[] args) {
		int arr[] = { 2, 4, 7, 6, 8, 5, 9 };
		sort(arr);
		for(int i:arr) {
			System.out.print(i+" ");
		}
	}
}

注:对于第二层for循环中 j < N - i - 1的解释。从上述图片中不难看出,长度为6的数组进行排序时,第一轮冒泡排序比较的次数仅需5次,即N-1。等到第二轮冒泡排序时,相当于数组长度变成5了,因为最大元素已经在最后,无需管他,那么这一轮的比较次数就是4次。。。。最后总结可知,j < N - i - 1

选择排序

最简单的排序算法,每轮遍历都将剩余元素中的第一个元素设为最小值,然后将其与元素与之比较。
该算法所需的交换次数最少。
基本思想
将a[i]和a[i+1…N]中最小的元素交换。
第一次遍历,要判断N-1次,找出最小的值,第二次遍历,判断N-2次,找出剩余元素中最小的值。。。。。。

在这里插入图片描述

public class Selection{
	public static void sort(Comparable[] a) {
		int N=a.length;
		for(int i=0;i<N;i++) {
			int min=i;//由于基本思想就是每次取for循环中的第一个元素最为最小值,其余元素与之比较。因此,min是个变量,且随着i变化。
			for(int j=i+1;j<N;j++) {
				if(less(a[j],a[min])) {//一路遍历,遇到更小的值就把该值对应的索引赋给min变量
					min=j;
				}
			}
			exch(a,i,min);
		}
		
	}
	public static boolean less(Comparable v, Comparable w) {
		return v.compareTo(w)<0;//因为调用的是元素的compareTo方法,因此传入的肯定是元素值而不是数字索引
	}
	public static void exch(Comparable[] a,int i,int j) {
		Comparable t=a[i];
		a[i]=a[j];
		a[j]=t;
	}
}

易错点:
容易把min定义在外层for循环外面,且令min=0。

十大排序之间的比较

**稳定:**如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
**不稳定:**如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

**内排序:**所有排序操作都在内存中完成;
**外排序:**由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大小。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值