第六章 排序(数组)
1、我们这章所说的排序是针对数组的排序。切记这一点。
2、被排序的对象必须属于Comparable类型,是可比较的。
https://www.cnblogs.com/onepixel/articles/7674659.html
我们介绍以下几种排序的方式:
选择排序、插入排序、归并排序、快速排序、堆排序
一、选择排序(O(n^2))
选择排序也是一种简单的排序方式。工作原理:从未排序的元素中找到最小值放到排序的起始位置。然后再从剩余未排序的元素中找到最小值,放到已排序的末尾。
1、算法描述
1)循环所有未排序的节点
2)找到未排序节点最小值的索引
3)将最小值的索引与未排序的首位置进行值交换。
2、动画演示
3、代码实现
public void sort(T[] arr) {
int size = arr.length;
for (int i = 0; i < size; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (arr[j].compareTo(arr[minIndex]) < 0) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
private void swap(T[] arr, int i, int j) {
T tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
二、插入排序(O(n^2))
插入排序是一种比较直观的排序算法。它的工作原理是通过构建有序序列,对于未排序的数据,在已排序的序列中从后向前扫描进行比较,直到找到相应的合适位置进行插入。
1、算法描述:
1)循环所有的元素
2)得到未排序的首元素位置
3)依次与它前一个位置进行比较,如果它小于它前一个位置的值,那么进行交换.
2、动画演示:
3、代码实现:
public void sort(T[] arr) {
int size = arr.length;
for (int i = 1; i < size; i++) {
for (int j = i; j > 0; j--) {
if (arr[j].compareTo(arr[j - 1]) < 0) {
swap(arr, j - 1, j); //交易它和它前面的值
} else {
break;
}
}
}
}
三、归并排序(O(nlogN))
1、算法描述:
1)先逐层分成一半,直至只有一个元素,到底
2)逐层向上进行归妆
最后得到的结果就是有序的:
3)这个实现过程我们需要额外的空间帮助我们进行归并。(缺点,空间复杂度高)
依靠三个指针同时进行操作:
2、动画演示:
3、代码实现:
public class MergeSort {
public static void sort(Comparable[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(Comparable[] arr, int start, int end) {
if (start >= end) {
return;
}
int mid = (start + end) / 2;
sort(arr, start, mid);
sort(arr, mid + 1, end);
merge(arr, start, mid, end);
}
private static void merge(Comparable[] arr, int l, int mid, int r) {
// inComparable[] aux = new int[r - l + 1];
// for (int i = l; i <= r; i++) {
// aux[i - l] = arr[i]; //临时空间初始化
// }
Comparable[] aux = Arrays.copyOfRange(arr, l, r + 1); //临时空间初始化
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
//如果i遍历结束
arr[k] = aux[j - l];
j++;
} else if (j > r) {
//如果j遍历结束
arr[k] = aux[i - l];
i++;
} else if (aux[i - l].compareTo(aux[j - l]) < 0) {
arr[k] = aux[i - l];
i++;
} else {
arr[k] = aux[j - l];
j++;
}
}
}
}
ps:这里的重点是使用临时空间进行比较,在真实空间进行赋值。
四、快速排序(Quick Sort)_一路快排 O(nlogN)
是一种递归的排序算法,从未排序的元素中随机找到一个元素(比如4),
然后想办法让这个元素(4)放到它应该在的位置。这样把数组一分为两。
然后按照此方法把大于4和小于4的数组段进行排序。
1、算法描述
Partition就是将数组一分为两的过程。
L是参照元素、j是分界点指向小于V的最后元素、i表示当前访问的元素
比较的过程分两种情况:
1)e>=v
这种情况比较简单,直接i++就可以,不用做其它操作。
2)e<v
这种情况下将j++位置的元素(大于V)与e进行值交换。i++
三步 1、j++ 2、swap 3、i++
private static int partition(Comparable[] arr, int l, int r) {
Comparable v = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i].compareTo(v) < 0) { //e<v的情况
j++;
swap(arr, j, i);
}
//对于大小和等于的情况不作处理
}
swap(arr, l, j);
return j;
}
2、动画演示:
3、代码实现
public class Quick1Sort {
public static void sort(Comparable[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(Comparable[] arr, int l, int r) {
if (l >= r) {
return;
}
int p = partition(arr, l, r); //空位分段过程
sort(arr, l, p - 1);
sort(arr, p + 1, r);
}
//定位
private static int partition(Comparable[] arr, int l, int r) {
Comparable v = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i].compareTo(v) < 0) {
j++;
swap(arr, j, i);
}
}
swap(arr, l, j);
return j;
}
private static void swap(Comparable[] arr, int i, int j) {
Comparable tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
Integer[] arr = {4, 2, 5, 6, 1, 2};
Quick1Sort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
4、局限性
1、对于近乎有序的数组,可能退代为O(n^2)(所以说对于近乎有序的数组,归并排序要优于一路快排)
优化:
选择参照元素时,不要用第一个元素,可以随机选择一个元素。然后和第一个元素进行交换
swap(arr, l, (int) Math.random() * (r - l + 1) + l);
2、对于有大量重复键值的情况。可能退代为O(n^2).这时也不如归并排序
我们发现当有大量重复,可能会分为极度不平衡的两段。
优化:这时我们可以使用两路快排。解决重复元素的分布情况。
四、快速排序(Quick Sort)_两路快排 O(nlogN)
解决有大量重复元素的数组进行一路快排时的效率问题。它优化的只是patition的方法。目的是分散==V元素的分布情况。
思路就是把小于V和大小V的元素放到两端。
当发现i的值>=V 停止,当j的值<=V也停止。然后将i和j的值进行交换。i++,j--继续进行
这里其实也有问题,也就是说 ==V的值被分到了两站。不再向单路快排那样集中而已。还是有问题。
V参照点
i左边比较元素
j右边比较元素
代码实现:
private static int partition(Comparable[] arr, int l, int r) {
Comparable v = arr[l];
int i = l + 1, j = r;
while (true) {
while (i <= r && arr[i].compareTo(v) < 0) {
i++;
}
while (j >= l + 1 && arr[j].compareTo(v) > 0) {
j--;
}
if (i > j) {
break;
}
swap(arr, i, j);
i++;
j--;
}
swap(arr, l, j);
return j;
}
待优化点:
1、解决了大量重复元素的分散问题。但对于重复元素我们还是要再次进行排序。
优化:对于重复元素一但发现和V相等,以后就不再进行排序。接下来引入我们的三路快排
四、快速排序(Quick Sort)_三路快排 O(nlogN)
解决的重复元素多次排序的问题。将数组分为三段,<小于、 =等于、 >大于 参数元素值。
1、算法描述
V参照点,这个不用多说
i当前元素
LT指向最后一个小于V的位置
GT指向第一个大小V的位置
1)它是一路快速的基础上发展出来的。也就是说一路快排可以直接作为成三路快排就可以,两路快排了解一下就行。
2)通过lt和gt将数组分成三段,也就是有两个标定点lt\gt和一路快排不同,一路快排只有一个p标定点
3)根据上图我们讨论三种情况
- e>v时
gt-1 的位置与e进行交换,gt--
由于gt--的位置元素没有进行比较过,所以i不变
if (arr[i].compareTo(v) > 0) {
swap(arr, i, gt - 1);
gt--;
}
- e<v时
e与 lt+1的元素进行交换然后 lt++。lt+1的元素肯定是>=V的且是比较过的。这没问题吧。
由于两个交换的元素都是比较过的所以i++继续进行。
if (arr[i].compareTo(v) < 0) {
swap(arr, i, lt + 1);
lt++;
i++;
}
- e==v时
这个最简单也不用改变lt和gt的位置,直接i++
i++;
2、代码如下:
public class Quick3Sort {
public static void sort(Comparable[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(Comparable[] arr, int l, int r) {
if (l >= r) {
return;
}
Map<String, Integer> p = patition(arr, l, r);
sort(arr, l, p.get("lt") - 1); //最后lt会放到=V的第一下值,因为刚才的交换swap(arr, l, lt);
sort(arr, p.get("gt"), r);
}
private static Map<String, Integer> patition(Comparable[] arr, int l, int r) {
int lt = l;
int gt = r + 1;
Comparable v = arr[l];
for (int i = l + 1; i < gt; ) {
if (arr[i].compareTo(v) < 0) {
swap(arr, i, lt + 1);
lt++;
i++;
} else if (arr[i].compareTo(v) > 0) {
swap(arr, i, gt - 1);
gt--;
} else {
i++;
}
}
swap(arr, l, lt);
Map res = new HashMap();
res.put("lt", lt);
res.put("gt", gt);
return res;
}
private static void swap(Comparable[] arr, int i, int j) {
Comparable tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
Integer[] arr = {4, 2, 5, 6, 1, 2};
Quick3Sort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、总结
它是对一路快排的优化,它的思路是将数组分为三段,==V的这一段,以后不再进行排序。
五、堆排序(O(nlogN))
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
1 算法描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
2 动图演示
3、代码实现
public static void sort_min(List list){
//java中默认是最小堆
PriorityQueue heap = new PriorityQueue(list);
int size = heap.size();
for (int i = size - 1; i >= 0; i--) {
list.set(i,heap.remove());
}
}
public static void sort_max(List list){
PriorityQueue heap = new PriorityQueue(list);
int size = heap.size();
for (int i = size - 1; i >= 0; i--) {
list.set(i,heap.remove());
}
Collections.reverse(list); //进行反转
}
最后对算法进行总结: