一、排序算法
常见的几类排序算法: 冒泡、选择、插入 快排、归并 桶、基数
二、考虑的因素
- 最好、最坏、平均时间复杂度。数据的有序度不同,对于排序算法有很大的区别。需要知道排序算法在不同数据下的性能表现。
- 时间复杂度的系数、常熟、低阶。因为排序算法对性能要求比较高,而它们的效率相差需要更细地考虑。并且在小规模数据时,在对比同一阶时间复杂度的排序算法性能对比时,要把系数、常数、低阶也考虑进来。
- 比较和交换的次数。
- 是否未原地排序:内存的消耗。即空间复杂度为O(1)为原地排序。
- 排序的稳定性:相同值的数据,排序前后相对顺序不变,即稳定
三、O(nlogn)的排序
归并
最好、最坏、平均都是O(nlogn)。不是原地排序,稳定。空间复杂度因为每次合并都创建数组,但合并完成后都释放了空间,为O(n)。
public class 归并排序 {
public int[] mergeSort(int[] arr) {
if (arr.length < 2) {
return arr;
}
int mid = arr.length / 2;
int[] left = Arrays.copyOfRange(arr, 0, mid);
int[] right = Arrays.copyOfRange(arr, mid, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
public int[] merge(int[] left, int[] right) {
int[] newArray = new int[left.length + right.length];
int lindex = 0;
int rindex = 0;
for (int i = 0; i < newArray.length; i++) {
if (lindex >= left.length) {
newArray[i] = right[rindex++];
} else if (rindex >= right.length) {
newArray[i] = right[rindex++];
} else if (left[lindex] < right[rindex]) {
newArray[i] = left[lindex++];
} else {
newArray[i] = right[rindex++];
}
}
return newArray;
}
}
快排
最好为nlogn,最坏-接近有序或有序时- O(n2)(可以通过合理选择pivot避免这种情况),原地排序 ,不稳定。 递推公式和递归shutdow树可以分析递归的时间复杂度
例子:求数组中第k大的数。
解:选择最后一个数作为pivot,将数组分成三部分,A[0…p-1],A[p],A[p+1…n-1]; 如果p+1=k,则A[p]就是要求的元素,如果k大于,就在右边找,小于在左边找。
第一次分区对大小为n进行分区操作,第二次对n/2的数组分区,依次执行。 代码:
public class 快排 {
public void quickSort(int[] arr, int begin, int end) {
if(arr.length <=1 || begin >=end){
return;
}
int pivotIndex = partition(arr,begin,end);
quickSort(arr,begin,pivotIndex-1);
quickSort(arr,pivotIndex+1,end);
}
private int partition(int[] arr, int begin, int end) {
int pivot = arr[end];
int pivotIndex = begin;
for(int i= begin;i< end;i++){
if(arr[i] < pivot){
if(i>pivotIndex){
swap(arr,i,pivotIndex);
}
pivotIndex++;
}
}
swap(arr,pivotIndex,end);
return pivotIndex;
}
private void swap(int[] arr,int i,int j){
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
@Test
public void testQuickSort(){
int[] array = new int[7];
array[0] = 5;
array[1] = 2;
array[2] = 6;
array[3] = 9;
array[4] = 0;
array[5] = 3;
array[6] = 4;
System.out.println(Arrays.toString(array));
quickSort(array,0,array.length-1);
System.out.println(Arrays.toString(array));
}
}
四、O(n)的排序
桶排序
要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。 最好情况是,n个数据分成m个桶,m接近于n。 最坏:数据都被分到一个桶里,退化为O(nlogn)
例子:10G订单数据按照订单金额排序。内存有限没法将所有数据都装进能内存。
public class 桶排序 {
public static List<Integer> bucketSort(List<Integer> array, int bucketSize) {
if (array == null || array.size() < 2 || bucketSize < 1) {
return array;
}
int max = array.get(0);
int min = array.get(0);
for (int i = 0; i < array.size(); i++) {
if (array.get(i) > max) {
max = array.get(i);
}
if (array.get(i) < min) {
min = array.get(i);
}
}
int bucketCount = (max - min) / bucketSize + 1;
List<List<Integer>> bucketList = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
bucketList.add(new ArrayList<Integer>());
}
for (int j = 0; j < array.size(); j++) {
int bucketIndex = (array.get(j) - min) / bucketSize;
bucketList.get(bucketIndex).add(array.get(j));
}
List<Integer> resultList = new ArrayList<>();
for (int j = 0; j < bucketList.size(); j++) {
List<Integer> everyBucket = bucketList.get(j);
if (everyBucket.size() > 0) {
if (bucketCount == 1) {
bucketSize--;
}
List<Integer> temp = bucketSort(everyBucket, bucketSize);
for (int i = 0; i < temp.size(); i++) {
resultList.add(temp.get(i));
}
}
}
return resultList;
}
@Test
public void testBucketSort() {
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(2);
list.add(2);
list.add(6);
list.add(9);
list.add(0);
list.add(3);
list.add(4);
System.out.println(list);
List<Integer> bucketSort = bucketSort(list, 2);
System.out.println(bucketSort);
}
}
计数排序
要排序的n个数据,所处的范围并不大。比如最大值是k,可以将数据分成k个桶,每个桶的数据值都是相同的,省掉了桶内排序。
要求:只能给非负整数排序。负数就+正数,小数就变整数
例子:50万考生的排名
基数排序
例子:十万个手机号码从小到大排序
要求:数据能分割出独立的位,位之间有递进关系,如果a数据的高位比b数据大,那剩下的地位就不用比较了。此外,每一位的数据范围不能太大,要可以用线性排序算法来排序。否则就不是O(n)了。
优化
快排优化
分区点选择:1.三数取中法 2.随机法
快速排序是用递归来实现的。我们在递归那一节讲过,递归要警惕堆栈溢出。为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,我们有两种解决办法:第一种是限制递归深度。一旦递归过深,超过了我们事先设定的阈值,就停止递归。第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,这样就没有了系统栈大小的限制
小规模数据
小规模数据时O(n2)时间复杂度的算法并不一定比O(nlogn)的算法 慢。 在C语言的qSort排序中,快排的要排序区间小于等于4时,就变成插入排序,不再继续用递归做快速排序
总结
可以看看java的源代码中集合排序使用的哪种排序
|