排序方法可以分为两种:内部排序 和 外部排序
内部排序的方法很多,常用的大致可以分为:
交换排序
1.冒泡排序
基本操作:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
Java代码:
public class BuddleSort {
public static int[] sort(int[] s){
for(int i = 0; i < s.length - 1; i++){//进行 n-1 趟排序
for (int j = 0; j < s.length - i - 1; j++) {
if(s[j] > s[j + 1]){
int temp = s[j];
s[j] = s[j + 1];
s[j + 1] = temp;
}
}
}
return s;
}
}
效率分析:
空间:辅助空间O(1)
时间:按上述代码执行,即使是正序排列的数列,也要 n-1 趟排序,需要进行 n(n-1)/2 次比较,时间复杂度为O(n^2)。
然而当序列为正序时,冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最好的复杂度降低到O(n)。
改进后的代码:
public class BuddleSort {
public static int[] sort(int[] s){
boolean flag = true;//
for(int i = 0; i < s.length - 1 && flag; i++){//判断的条件需要加上对旗标的判断
flag = false;//旗标先置为false,若下列交换不发生,证明已经排序好,则退出循环
for (int j = 0; j < s.length - i - 1; j++) {
if(s[j] > s[j + 1]){
int temp = s[j];
s[j] = s[j + 1];
s[j + 1] = temp;
flag= true;
}
}
}
return s;
}
}
稳定性:true.因为每次只比较交换相邻两数据,当两数相等时不做调整,故为稳定的。
若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。
2.鸡尾酒排序
示例:
Java代码:
public class CocktailSort {
public static int[] sort(int[] s) {
int start = 0, end = s.length - 1;
int i, temp;
while (start < end) {
flag = false;
for (i = start; i < end; i++) {
if (s[i] > s[i + 1]) {
temp = s[i];
s[i] = s[i + 1];
s[i + 1] = temp;
}
}
end--;
for (i = end; i > start; i--) {
if (s[i - 1] > s[i]) {
temp = s[i];
s[i] = s[i - 1];
s[i - 1] = temp;
}
}
start++;
}
return s;
}
}
以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问一次序列就可以完成排序,但如果使用冒泡排序则需要四次。但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。
3.快速排序
基本思想:使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 设两个指针 low 和 high ,设枢轴值pivotkey为数列中的某一个数,一般设为初始low指针指向的数(位于当前数列的左部,左部存放小于枢轴值的数);
- 从 high 所指的起 向前搜索找到第一个值小于pivotkey的值,置于枢轴值的位置(即当前low指向的位置);
- 接着从low所指的位置起向后搜索,找到第一个大于pivotkey的值,置于之前high所指向的位置;
- 重复2,3步,直到low = high,将枢轴值赋予该处,此时,枢轴值左边都比其小,右边都比它大;
- 对左右两部分递归实现排序
示例:
代码:(更详细的说明,请转到-->白话经典算法系列之六 快速排序 快速搞定)
public class QuickSort {
public static int[] sort(int[] s, int l, int h){
if (l < h) {//判断左右指针的关系,作为递归的出口
int low = l;//左指針
int high = h;//右指針
int pivotKey = s[l];
while(low < high){
while (low < high && s[high] > pivotKey) {
high--;
}
if (low < high) {
s[low++] = s[high];
}
while (low < high && s[low] < pivotKey) {
low++;
}
if (low < high) {
s[high--] = s[low];
}
}
s[low] = pivotKey;
sort(s, l, low - 1);
sort(s, low + 1, h);
}
return s;
}
}
效率分析:
空间:快速排序需要一个栈空间来实现递归,最好情况是O(nlog n)和最坏情况是O(n^2*log n)的空间需求
时间:最差时间复杂度O(n^2)最优时间复杂度O(nlog n)平均时间复杂度O(nlog n)
稳定性:false.在比较过程中,若遇到与枢轴值相等的元素,那么排序后出现在其左侧和右侧都有可能,故是不稳定的