排序(中)
1. 简单插入排序
1.1 基本介绍
对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
1.2 设计思路
- 首先把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素。(有序表在前,无序表在后)
- 排序过程中每次从无序表中取出第一个元素,把它依次与有序表元素进行比较,然后将它插入到有序表中的适当位置,使之成为新的有序表。
1.3 代码实现
public class Demo18 extends TimeTemplate {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, 20};
new Demo18().sendTime(arr, "插入排序");
System.out.println(Arrays.toString(arr));
// 性能测试
int[] arr2 = new int[80000];
for (int i = 0; i < 80000; i++) {
arr2[i] = (int) (Math.random() * 8000000);
}
new Demo18().sendTime(arr2, "插入排序");
}
/**
* 插入排序 时间复杂度:O(N^2)
*/
@Override
public void code(int[] arr) {
int tempArr;
int tempIndex;
for (int i = 1; i < arr.length; i++) {
// 存放无序数组第一个数
tempArr = arr[i];
// 存放有序数组最后一个数字的坐标
tempIndex = i - 1;
/*
循环条件:
1. tempIndex >= 0 判断坐标的临界值
2. tempArr <= arr[tempIndex] 判断无序数组第一个数字是否小于等于有序数组
如果两个条件成立,有序数组就后移
*/
while (tempIndex >= 0 && tempArr < arr[tempIndex]) {
arr[tempIndex + 1] = arr[tempIndex];
tempIndex--;
}
// 如果经过while循环,则需要把tempIndex--这步复原,否则不动
if (tempIndex + 1 != i) {
arr[tempIndex + 1] = tempArr;
}
}
}
}
2. 希尔排序
2.1 基本介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
2.2 设计思路
引入步长(
gap
)的概念,通过步长把原始数组(长度为n)分为(n/gap)个小组。
- 第一次分组的步长
gap = n/2
,把小组内的数元素进行排序- 缩小步长
gap = gap/2
,再进行组内排序…- 直到整体为一组的时候进行最终排序
2.3 代码实现(交换法)
public class Demo19 extends TimeTemplate {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
new Demo19().sendTime(arr, "希尔排序(交换法)");
// 性能测试
int[] arr2 = new int[80000];
for (int i = 0; i < 80000; i++) {
arr2[i] = (int) (Math.random() * 8000000);
}
new Demo19().sendTime(arr2, "希尔排序(交换法)");
}
/**
* 希尔排序 时间复杂度:O(N*log2N)
*/
@Override
public void code(int[] arr) {
int temp;
// 定义步长每次都是之前的一半
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 通过步长把数组进行分组,在组内进行排序
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
// 如果小组内前面的元素比较大则进行交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
}
2.4 代码实现(移位法)
通过上面的测试,我们发现使用交换法的希尔排序甚至比简单插入排序更慢,主要是因为我们每次发现需要排序的数字就进行交换,这里需要大量的时间开销。因此我们尝试通过移位的方式,把待交换数据直接放在最终的位置。
public class Demo20 extends TimeTemplate {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
new Demo20().sendTime(arr, "希尔排序(移位法)");
int[] arr2 = new int[80000];
for (int i = 0; i < 80000; i++) {
arr2[i] = (int) (Math.random() * 8000000);
}
new Demo20().sendTime(arr2, "希尔排序(移位法)");
}
/**
* 希尔排序 时间复杂度:O(N*log2N)
*/
@Override
public void code(int[] arr) {
int temp, j;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 可以参考直接插入排序
// 保存待插入的索引
j = i;
// 保存待插入的索引的值
temp = arr[j];
// 只有 当前数 小于 前面的数 才进入循环
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
// 前面的数在组内后移,因为已经提前把待插入数据放到temp,所以不用担心被覆盖
arr[j] = arr[j - gap];
// 继续往前探
j -= gap;
}
// 等比当前数大的数都后移了之后,再把当前数放在最终的位置
arr[j] = temp;
}
}
}
}
}
3. 快速排序
3.1 基本介绍
快速排序(Quicksort)是对冒泡排序的一种改进。因为老韩讲得不太行,所以这里基本是自己的思路,如有不妥之处还请大佬们指出。
3.2 设计思路
- 设置最左的位置为基准值
- 分别从左到右、从右到左扫描整个数组,进行排序,从而将数组分成两部分,一部分比基准值小,一部分比基准值大,
- 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
3.3 代码实现(交换法)
交换法是使用了冒泡的思想
- 首先设置最左的位置为基准值
- 循环查找要交换的位置:
2.1 从左到右扫描数组,直到找到比基准值大的数组下标
2.2 从右到左扫描数组,直到找到比基准值小的数组下标
2.3 如果left小于right说明left下标的数应该比right下标的数小,进行交换
2.4 否则就是 left>=right ,说明以当前基准值比较的数组已经将数组根据left(right)将数组分成了比基准值小和比基准值大两个部分。[start,left-1] 这部分比基准值小; [right+1,end] 这部分比基准值大
- 跳出循环之后,right指向的是比基准值小的数,所以将基准值与right进行交换
- 递归调用
public class Demo21 {
/**
* 交换方法
*/
private static void swap(int[] data, int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
/**
* 快速排序,交换法
*/
public static void quickSort2(int[] data, int start, int end) {
if (start < end) {
// 以最左边的数为基准值
int base = data[start];
// 设置最左下标
int left = start + 1;
// 设置最右下标
int right = end;
// 循环查找要交换的位置
while (true) {
// 从左到右扫描数组,直到找到比基准值大的数组下标
while (left < end && data[left] <= base) {
left++;
}
// 从右到左扫描数组,直到找到比基准值小的数组下标
while (right > start && data[right] >= base) {
right--;
}
// 如果left小于right说明left下标的数应该比right下标的数小,进行交换
if (left < right) {
swap(data, left, right);
} else {
// left>=right 说明以当前基准值比较的数组已经将数组根据left(right)
// 将数组分成了比基准值小和比基准值大两个部分
// [start,left-1] 这部分比基准值小; [right+1,end] 这部分比基准值大
break;
}
}
// 因为跳出循环之后,right指向的是比基准值小的数,所以将基准值与right进行交换
swap(data, start, right);
//递归调用
quickSort2(data, start, right - 1);
quickSort2(data, right + 1, end);
}
}
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
quickSort2(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
3.4 代码实现(插值法)
交换法和希尔排序一样,也会造成多次交换,浪费时间,这里借鉴插入法的思想,用插值来代替交换。
- 首先设置最左的位置为基准值
- 循环查找数组元素进行插值
2.1 从右到左扫描数组,直到找到比基准值小的数组下标
2.2 如果left<right 将右边比中枢值小的数放到左边,并将left的下标后移,保证right后面的数比基准值大
2.3 从左到右扫描数组,直到找到比基准值大的数组下标
2.4 将左边比中枢值大的数放到右边,并将right的下标前移- 当left == right的时候,将一开始取出的中枢值放入
- 递归调用
示例:
a[0] a[1] a[2] a[3] a[4] a[5]
(30) 40 60 10 20 50 初始状态,pivot = 30
'30l' 40 60 10 (20r) 50 第一趟,从右往左找到小于pivot的数,将值放到left的位置,并把left的下标++ right = 4, left = 1
20 (40l) 60 10 '20r' 50 第二趟,从左往右找到大于pivot的数,将值放到right的位置,并把right的下标-- right = 3, left = 1
20 '40l' 60 (10r) 40 50 第三趟,右往左找到小于pivot的数,将值放到left的位置,并把left的下标++ right = 3, left = 2
20 10 (60l)'10r' 40 50 第四趟,从左往右找到大于pivot的数,将值放到right的位置,并把right的下标-- right = 2, left = 2
20 10 (60lr)60 40 50 第五趟,right == left 把pivot值放入到当前位置(因为当前的值已经让入到其他位置)
20 10 30(p)60 40 50 保证左边比pivot小,右边比pivot大,然后左右分别递归
public class Demo21 {
/**
* 快速排序,插值法
*/
public static void quickSort(int[] a, int l, int r) {
if (l < r) {
int left, right, pivot;
left = l;
right = r;
// 中枢位置的值为第一个 这个比较简单
pivot = a[left];
while (left < right) {
// 从右到左扫描数组,直到找到比基准值小的数组下标
while (left < right && a[right] > pivot) {
right--;
}
// 将右边比中枢值小的数放到左边,并将left的下标后移
if (left < right) {
a[left++] = a[right];
}
// 从左到右扫描数组,直到找到比基准值大的数组下标
while (left < right && a[left] < pivot) {
left++;
}
// 将左边比中枢值大的数放到右边,并将right的下标前移
if (left < right) {
a[right--] = a[left];
}
}
// 这里是当left == right的时候,将一开始取出的中枢值放入
a[left] = pivot;
// 递归调用
quickSort(a, l, left - 1);
quickSort(a, left + 1, r);
}
}
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
3.5 代码实现(中值法)
这个方法需要考虑的情况太多,所以不太推荐这个方法来实现快排。
具体的思路都在代码注释中,偷个懒就不整理到外面来了,思路和上面的交换法差不多,只是基准值的位置变了。
public class Demo21 {
/**
* 快速排序 中值交换法
*/
public static void code(int[] arr, int left, int right) {
if (left < right) {
// 左下标
int l = left;
// 右下标
int r = right;
// pivot 基准值,这里设置为中轴
int pivot = arr[(right + left) / 2];
// 左下标始终小于右下标
while (l < r) {
// 从左往右遍历数组,直到找到大于等于pivot的值
while (arr[l] < pivot) {
l++;
}
// 从右往左遍历数组,直到找到小于等于pivot的值
while (arr[r] > pivot) {
r--;
}
// 如果l<r,则左边大于pivot的数应该和右边小于pivot的数进行交换,否则直接跳出循环
if (l < r) {
swap(arr, l, r);
} else {
break;
}
// 若交换后发现arr[l] = arr[pivot](说明下一个循环l不会--),r需要前移,
// 因为不前移的话如果r的前一个为pivot值或者小于pivot的值就会陷入死循环
if (arr[l] == pivot) {
r--;
}
// 同理,若r前移后发现:arr[r] = arr[pivot](说明下一个循环r不会--)则后移l,
// 否则会陷入死循环
if (arr[r] == pivot) {
l++;
}
}
// 若 l==r,这种情况说明[left,l]和[r,right]已经按照规则分为了两个部分
// 但是这时必须将l和r错开,缩减下面递归[left,r]和[l,right]的范围,
// 否则会递归过程中会死循环导致栈溢出。
// 这里可能不好理解,建议打断点自己多看几次
if (l == r) {
l++;
r--;
}
// 向左递归
code(arr, left, r);
// 向右递归
code(arr, l, right);
}
}
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
code(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
上一篇 | 总目录 | 下一篇 |
---|---|---|
五、排序(上:冒泡、简单选择) | 数据结构篇(Java)目录 | 七、排序(下:归并、基数) |