排序的实现过程,这个比任何理论都好懂,如果要定义的话,直接百科搜就是了
1、冒泡排序:
34,8,64,51,32,21
34与8比较:8,34,64,51,32,21
34与64比较:8,34,64,51,32,21
64与51比较:8,34,51,64,32,21
64与32比较:8,34,51,32,64,21
64与21比较:8,34,51,32,21,64(这样一趟结束了,需要判断本趟是否有进行交换,如果有,则进入下一趟冒泡)
8与34比较:8,34,51,32,21,64
34与51比较:8,34,51,32,21,64
51与32比较:8,34,32,51,21,64
51与21比较:8,34,32,21,51,64
51与64比较:8,34,32,21,51,64(这样一趟结束了,需要判断本趟是否有进行交换,如果有,则进入下一趟冒泡)
8与34比较:8,34,32,21,51,64
34与32比较:8,32,34,21,51,64
34与21比较:8,32,21,34,51,64
34与51比较:8,32,21,34,51,64
51与64比较:8,32,21,34,51,64(这样一趟结束了,需要判断本趟是否有进行交换,如果有,则进入下一趟冒泡)
8与32比较:8,32,21,34,51,64
32与21比较:8,21,32,34,51,64
后面还需要完成本趟的比较,因为没有变化了,所以没写;然后还要进行一躺的比较,因为本趟比较有进行交换,只有下一趟没有交换,才能让标志位判定排序结束
时间复杂度:O(n^2)
空间复杂度:O(1)
是否稳定:是
因为每次都是相邻两个值比较,如果遇到相等的值就不会进行交换,就不会造成相同值之间顺序先后的变化,所以是稳定的算法
/** * 冒泡排序 * */ public class TestBubble { //注意算法的终止条件,优化终止条件,可以提供算法效率,此算法没有考虑到没有发生比较时,便停止排序 public static void bubleSort(int a[]) { boolean flag=true; for (int i = 0; i < a.length - 1&&flag; i++) { flag=false; for (int j = 0; j < a.length-1 - i; j += 1) { if (a[j] > a[j + 1]) { swap(a, j, j+1); //每趟发生交换才进行下一趟排序,所以标示 flag=true; } } } } private static void swap(int[] number, int i, int j) { int t; t = number[i]; number[i] = number[j]; number[j] = t; } public static void main(String[] args) { int[] a = new int[] {26, 3, 42, 13, 8, 0, 122 }; bubleSort(a); for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } } }
2、(直接)插入排序:
34,8,64,51,32,21
34与8比较:8,34,64,51,32,21
64与8,34比较:8,34,64,51,32,21
51与8,34,64比较:8,34,51,64,32,21
32与8,34,51,64比较:8,32,34,51,64,21
21与8,32,34,51,64比较:8,21,32,34,51,64
需要注意的是:每次i位置上的值是与前面有序队列从左到有进行比较,也就是从小到大的比较;由于这样的比较只需要一趟,所以需要有个标志位来判定有序队列和初始化数列的长度是否一致,如果一直则结束排序
时间复杂度:O(n^2)
控件复杂度:O(1)
是否稳定:否
由于每次排序时,都已经是有序队列,如果Ai=Aj,且Ai在Aj前面;也就意味着Ai比Aj先排。而插入的条件是要插入的值小于那个位置的值,然后就插入到这个值的前面,所以所以,当遇到需要插入的Aj等于被比较的Ai值时,将会跳过,进行下一个位置的比较。所以,进行排序后,这样的位置顺序是不变的
3、选择排序:
34,8,64,51,32,21
从序列选出最小值8与第一个位置的值交换:8,34,64,51,32,21
从第一个位置右边的序列选出最小值21与第二个位置的值交换:8,21,64,51,32,34
从第二个位置右边的序列选出最小值32与第第三个位置的值交换:8,21,32,51,64,34
从第三个位置右边的序列选出最小值34与第四个位置的值交换:8,21,32,34,64,51
从第四个位置右边的序列选出最小值51与第五个位置的值交换:8,21,64,51,51,64
需要注意的是:这里的零界条件应该是:当原始队列中的值为空时,说明初始队列已经都被选择完了,也就排完了。选择排序可以把每次选的条件进行变化,可以是最小值,也可以是最大值,也可以最小的质数之类的,所以选择排序很灵活
时间复杂度:O(n^2)
控件复杂度:O(1)
是否稳定:否
因为每次的选择最小的,都需要进行一次位置上的交换。设Ai=Aj,且Ai在Aj的前面,但后面的最小值要与Ai交换,且这个最小值又在Aj的右边(这种情况相当合理的),此时交换,就把Ai换到了Aj的右边,虽然有几率再次换回来,那也仅仅是有可能,所以,这是不稳定的算法
4、快速排序(所有内部排序方法中最高好的,一大多数况下是最好的):
34,8,64,51,32,21
选择一个基准数,通常是第一个数 X = 34,产生两个指针,i=0,j为数组的长度-1,j=6-1=5
我们要做的是将比X小于等于的数放到X的左边,大于的数放到右边
之所以说是分治,是因为这种做法先是从右边开始找小于等于X的数,再从左边开始找大于X的数,当i=j的时候,就说明排序完毕
从j位置往左找一个小于等于X的数32将这个值复制到第一个位置:21,8,64,51,32,X;i =0j=5
从i位置往右找一个大于X的数64将这个值复制到上一次赋值的地方,21,8,X,51,32,64;i=2 j=5
从j位置往左找一个小于等于X的数32将这个值复制到上一次赋值的地方,21,8,32,51,X,64;i=2 j=4
从i位置往右找一个大于X的数51将这个数复制到上一次赋值的地方,21,8,32,X,51,64; i=3 j=4
由于,i<y是满足条件,所以当j自减一次后,就达到零界,21,8,32,51,51,64,i=3 j=3
最后将i位置上的值赋为X:21,8,32,34,51,64
这样就得到两个子集,{21,8,32},{34},{51,64}
对这两个子集进行上述相同的操作,直到序列的左边界大于等于右边的边界
需要注意的是,每次的分治,条件应该是相对的,可以是从右开始往左找大的数,从左开始往右找小于等于的数,也可以从反过来
时间复杂度:平均为O(nlog2n),最坏的情况为O(n^2)
空间复杂度:O(nlog2n)
是否稳定:否
因为,每次的赋值,很容就将原本是顺序的两个相同的值变成逆序的,如小于等于X的Ai=Aj,且Ai在Aj之前,那么第一次从从右往左找小于等于的值,极有可能将Ai移到前面。所以,不稳定
//快速排序 void quick_sort(int s[], int l, int r) { if (l < r) { <span style="white-space:pre"> </span>//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1 int i = l, j = r, x = s[l]; while (i < j) { while(i < j && s[j] >= x) // 从右向左找第一个小于x的数 <span style="white-space:pre"> </span>j--; if(i < j) <span style="white-space:pre"> </span>s[i++] = s[j]; <span style="white-space:pre"> </span> while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数 <span style="white-space:pre"> </span>i++; if(i < j) <span style="white-space:pre"> </span>s[j--] = s[i]; } s[i] = x; quick_sort(s, l, i - 1); // 递归调用 quick_sort(s, i + 1, r); } }
5、归并排序:
针对的是两个已有序的序列,进行合并
A{8,34,64}
B{21,32,51}
将序A的第一个元素和序列B的第一个元素进行比较,将较小值放入另一个队列中:C{8},A{34,64},B{21,32,51}
重复上面的步骤,直到有一个序列为空,然后将另一个非空序列直接添加到序列C中,即为完成排序
需要注意的是,递归算法的空间复杂度一般都为O(nlog2n),但是那是指空间不释放的情况下,如快速排序,空间资源是不释放的,而归并排序,序列A和序列B的空间是随时释放的而C的空间是不断增加的
时间复杂度:O(nlog2n)
控件复杂度:O(n)
是否稳定:是
因为,在上述操作过程中,没有改变相同的值的位置的可能,所以稳定
6、堆排序
先将其转化成优先序列(堆),然后重复将根值输出,并把最后一个树叶的值移到根节点,从新下滤操作,得到一个优先序列(堆)。重复以上操作,直到最后一个
需要注意的是,下滤操作的过程,就是保证根节点的值比两个子节点的值都小,而且下滤过程中,优先选择子节点中最小的值
时间复杂度:O(nlog2n)
空间复杂度:O(1)
是否稳定:否
因为,在堆的左右子树中,有可能存在相同大小的值,而这个顺序是不可能控的,所以不稳定
7、希尔排序:
34,8,64,51,32,21
3排:{8,34},{51,64},{21,32}
1排:{8,21,32,34,51,64}
需要注意的是,n排中,n的值为数组长度整除2的值
时间复杂度:O(nlog2n)
空间复杂度:O(1)
是否稳定:否
因为,当一个队列中存在相同值且在不同的模块中,那么正在合并的时候,可能改变先后顺序,所以不稳定
算法分类图:
算法总结:
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) |
B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) |
O(ns) 1 |
不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |