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 + " ");
}
}
}
其主要思想是:
- 先构造一个堆有序的数组,并使最大元素位于数组的开头
- 第二步是将堆中最大的元素删除,然后放入堆缩小后数组中空出的位置。这个过程与选择排序类似,每次取出一个最大的或者最小的元素,但相对选择排序,所需要的比较次数要少得多,因为他寻找最大元素更加的高效。
插入排序
在数组元素较少,且部分有序的情况,效率极高。
下面这张图能很好的理解:
代码:
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;
}
}
}
}
冒泡排序
主要思想:
- 每次冒泡排序操作
仅比较
相邻的两个元素 - 每一轮冒泡排序完成后都必将至少排定其中的最大元素
这是第一层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的后面;
**内排序:**所有排序操作都在内存中完成;
**外排序:**由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大小。