单单有数据还不够,对于数据的展现,经常要按照一定的顺序进行排列,越高要求的排序越复杂,这篇只介绍三种大小的简单排序。
这篇文章出现好多错误,i
1)冒泡排序
前面写错了,已改正,给大家带来不便,不好意思。
模拟:有数组,1,4,2,5,7,3。
冒泡相当于一个个泡往上冒,由物理学可知轻的是冒在最上面的。
(1)首先从1开始冒泡,1比4小,与1交换位置,往上冒,1小于2,冒,与2交换位置,1比5小,冒,1与5交换位置,1比3,冒,与5交换位置,这样一直冒,结果:4,2,5,7,3,1。
(2)接下来从4开始,因为最后一个泡是最轻的,所以不用冒了,所以需要处理的是4,2,5,7,3。
4比2重,不冒,2比5轻,冒上去。重复该过程。结果:4,5,7,3,2,1
(3)倒数两位为最轻,不需要冒,这样4又冒到前面去了,结果,5,7,4,3,2,1。
(4)重复该过程,轻的一直冒,结果为7,5,4,3,2,1
代码:
public class BubbleSort{ public static void main(String[] args) { int a[] = {1,4,2,5,7,3 }; for (int i = 0; i < a.length-1; i++) { for (int j = 0; j < a.length-i-1; j++) { if(a[j]<a[j+1]){ int temp = a[j+1]; a[j+1] = a[j]; a[j]=temp; } } } System.out.println(Arrays.toString(a)); } }
复杂度:
第一次9次比较,第二次8次,最后1次,比较总数9+8+......+1(我这里用了阶乘表示写出9!,真是囧了,其实这是个等差数组,多谢网友提醒)。
如果N个数据,那比较就是总数M=N*(N-1)/2,交换次数大概为比较的一半,N*(N-1)/4,最坏的时候和比较次数一样。
去掉常数,时间复杂度O(N^2)。
2)选择排序
选择排序改进了冒泡排序,交换的次数,看清楚是交换的次数O(N^2)减少到了O(N)。为什么?
模拟:有数组,1,4,2,5,7,3。
(1)从第一个元素开始,假设1为数组的最小值,min=1(min存放的是最小元素的位置),用1开始和后面元素比较,如果还有比1小的值x,将x元素所在的位置赋值给min,比较之后发现1最小。这个时候,最小的值已经放在最左边了,进入(2)
(2)从第二个值4开始,假设min=2,与2对比,2更小,min=3,2再与5对比,还是2小,与7对比,2小,与3比,2小,最后min=3,同时将2和4替换位置。数组为:1,2,4,5,7,3。
(3)从第三个值4开始(上一步交换了),这次min=3,与4换位。数组为:1,2,3,4,5,7。
(4)从第四个值4开始,这次min是4了,以此类推。最后:1,2,3,4,5,7。
代码:
第一次敲的:
public class SelectSort{ public static void main(String[] args) { int a[] = {1,2,3,4,5,6,3,1,2,3 }; for (int i = 0; i < a.length-1; i++) { int min = i; int j =0; for ( j= i+1; j < a.length-1; j++) { if(a[i]>a[j]&&a[i]!=a[j]){ min = j; } } int temp = a[i]; a[i] = a[min]; a[j] = temp; } System.out.println(Arrays.toString(a)); } }错漏百出。
第二次正确:
public class SelectSort{ public static void main(String[] args) { int a[] = {1,2,3,4,5,6,3,1,2,3 }; for (int i = 0; i < a.length-1; i++) { int min = i; for ( int j= i+1; j < a.length; j++) { if(a[j]<a[min]&&a[j]!=a[min]){ min = j; } } int temp = a[i]; a[i] = a[min]; a[min] = temp; } System.out.println(Arrays.toString(a)); } }交换值那里和比较那里我写错了,本身比较的时候我们已经把min设定成i开始,那么是将min与j比,如果j更小,则min=j。然后,交换值的时候,和j也是没关系的,既然锁定了最小值的位置,只要和i交换即可。
算法有个可以优化地方,就是两者相等的时候是不用互换位置的。减少了一次赋值。
复杂度:
其实比较次数也是和冒泡一样——阶乘——N!=N*(N-1)/2。(N个元素)
但是交换的次数是少于N的,所以选择排序比冒泡快,当然元素达到一定数量级的时候,速度就体现出来了。
3)插入排序
在简单的排序这三种中最快,时间复杂度仍然为O(N)
这个写完代码再解释模拟过程:
代码:
public class InsertSort {
public static void main(String[] args) {
int[] a = {1,3,2,1,4,2,5,7,3};
int mark,compare;
for(mark = 1;mark < a.length;mark++ ){
int temp = a[mark];
compare = mark;
while(compare>0&&a[compare-1]>temp){
a[compare] = a[compare-1];
compare--;
}
a[compare] =temp;
}
System.out.println(Arrays.toString(a));
}
}
(1)首先mark指向插入的位置,从数组第二个位置开始,temp值等于mark位置的元素值,往左比较,3大于1,while循环,a[mark]=temp,即3没有变化,下个for循环。
(2)mark=2,指向2,temp=2,往左,3大于temp,所以2的值替换3,compare
的值减一,即将1和temp比较,1小,跳出while循环,a[mark]=temp,即3的值变成2。
(3)数组现在为,1,2,3,1,4,2,5,7,3,mark=3,指向1,temp=1,往左,3大于1,a[compare],即a[3]=a[2]=3,再往左,compare减一,compare=2,2大于1,所以a[compare],a[2]=a[1]=2,此时为1,2,2,3,4,2,5,7,3。在往左,compare减一,a[0]=1,不移动,最后a[1]=temp=1,变成1,1,2,3,4,2,5,7,3。
(4)mark+1,继续循环,每次就是以mark为标志,向左比较大小,不停移动。直至最后。
复杂度:
比较次数看起来也是阶乘——N!=N*(N-1)/2,但其实每次插入点之后,插入点前面的数据就是有序的,所以,真正比较的只有一半左右——N!=N*(N-1)/4,
复制和比较的次数大致相等。虽然复杂度也是O(N^2)。
但是如果数组是1,2,3,4,5,6,7,8,7的话,也就是前面基本有序,那只有当mark等于9的时候才会比较,而且,就只和8交换而已。那这样的话时间复杂度只有O(N)。
所以说插入比冒泡快一倍,比选择排序快一些。
4)题外——计数排序。
上面引用了另外一个博客链接,简单的三种排序复杂度都到了O(N^2),即使后面高级一些的排序也是要O(NlogN)。前段时间看题目发现竟然有O(N)复杂度的排序:现有n个小于100000的整数,写一个算法将这些数从小到大排序,要求时间复杂度O(n),空间复杂度O(1)。
原来就是用的计数排序。发现原来是算法导论里有的,果断翻书。
public class SelectSort{ public static void main(String[] args) { int a[] = {1,2,3,4,4,4,3,1,2,3 }; int c[] = new int[5];//c是用来存放每个数字出现次数的数组 for (int i = 0; i < c.length; i++) { for (int j = 0; j < a.length; j++) { if(i == a[j]) c[i]++; } } System.out.println(Arrays.toString(c)); //[0, 2, 2, 3, 2] for (int i = 1; i < c.length; i++) { c[i] = c[i] +c[i-1]; } System.out.println(Arrays.toString(c)); //[0, 2, 4, 7, 9] int[] b = new int[a.length]; for (int i = 0; i < a.length; i++) { b[c[a[i]]-1] = a[i]; c[a[i]]--; } System.out.println(Arrays.toString(b)); } }算法设计得太巧妙了。
c数组为存储a数组中数字出现的个数,即c[0]表示a中0出现的个数,也正因为这样,所以c数组的长度要为a数组中最大元素+1。
然后:
for (int i = 1; i < c.length; i++) { c[i] = c[i] +c[i-1]; }其实就是次数的累加,比如c[1]=c[1]+c[0],那么c[1]存的就是a数组出现0和1的个数,以此类推,c[2]存的就是小于等于2的个数。
for (int i = 0; i < a.length; i++) { b[c[a[i]]-1] = a[i]; c[a[i]]--; }这里才是算法最美妙的地方。
i=0,a[0]=1,c[1]就是1以及0出现次数的地方,1以及0出现两次,既然没有0,那么1就是占据了第一位和第二位的位置(请仔细读这句话,这句话读懂了,整个算法就理解了)。然后我们就把其中的一个1放在1的第二位b[1]的位置,同时c[1]-1,因为我们已经排好了一个1了。
接下来,i=1,a[1]=2,c[2]是小于等于2的数字出现的个数,c[2]=4,那么要把它排在第四位,即b[3]的位置,同时c[2]-1=3,因为一个4已经排好序了,那么下次读到4的时候,他就是老三的位置了。
接下来就是不停的循环,刚开始看不懂算法设计者的用意。
其实次序与大小出现的次数之间的关系竟然是如此美妙。
如果要深入学习算法——《算法导论》是一本很好的书籍。