Java数据结构学习DAY7——排序
1. 概念
1.1 排序
排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。平时的上下文中,如果提到排序,通常指的是排升序(非降序)。通常意义上的排序,都是指的原地排序(in place sort)。
1.2 稳定性(重要)
两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
2. 七大基于比较的排序
2.1. 插入排序
2.1.1 直接插入排序
1、原理
整个区间被分为
- 有序区间
- 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
2、 实现
- 代码
package Java42_0321;
import java.util.Arrays;
public class MySort {
//1.插入排序是一个稳定排序,时间复杂度 O(N^2),空间复杂度O(1)。(只有三个额外的临时变量,bound, v, cur)
public static void insetSort(int [] arr) {
//这个循环控制代码进行N次插入过程
int bound = 1;
for (;bound < arr.length ; bound++) {
//循环内部实现插入一次的过程
//需要找到待排序区间的一个元素,放哪里合适,并且搬运
//已排序区间:【 0,bound)
//带排序区间【bound length)
//出的 v 就是第一个人元素。要插入的元素
int v = arr[bound];
int cur = bound-1;
for(; cur >= 0; cur--){
if (arr[cur] < v){ //改成小于,就是降序排序。(不加等于号,排序稳定,若加了等于号就不是稳定排序了)
//如果 cur 大于 v
//将 v 插入 arr[cur] 的前面
//将 arr[cur] 搬运位置
arr[cur+1] = arr[cur];
} else {
//此时找到了要排序的位置
break;
}
}
arr[cur +1] = v;
}
}
public static void main(String[] args) {
int[] arr={9,5,3,7,2,8};
insetSort(arr);
System.out.println(Arrays.toString(arr));
}
}
-结果
3、性能分析
插入排序是一个稳定排序
时间复杂度 O(N^2)
空间复杂度O(1)(只有三个额外的临时变量,bound, v, cur)
2.1.2 希尔排序
1、 原理
希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
2、 实现
- 代码实现
package Java42_0323;
import java.util.Arrays;
public class MySort {
//2.希尔排序,空间复杂度 O(1),不稳定排序。
public static void shellSort(int[] arr){
//现制定 gap 序列,此处使用希尔序列
int gap = arr.length/2;
while(gap >= 1) {
//通过这个辅助方法,进行分组插排
_shellSort(arr,gap);
gap = gap / 2;
}
}
public static void _shellSort(int[] arr,int gap ) {
//分组的时候,同组中的相邻元素的下表差就是 gap
//注意取的顺序,先取 0 组元素中的第 1 个元素,尝试往前插入排序
//注意取的顺序,先取 1 组元素中的第 1 个元素,尝试往前插入排序
//注意取的顺序,先取 2 组元素中的第 1 个元素,尝试往前插入排序
//....
//再取第 0 组元素中的 第2 个元素,尝试往前插入排序
for(int bound = gap; bound < arr.length; bound++) {
//循环内部完成比较搬运的 过程
//比较搬运的是组呢你的元素,不同组之间不能相互会影响
int v= arr[bound];
int cur = bound - gap ;
for(;cur >= 0; cur -= gap) {
if (arr[cur] > v) {
//需要进行搬运
//v 要插入到 arr[cur] 的前面
//就得把 、arr[cur] 往后搬运一个格子 给 v 腾出一个位置
arr[cur + gap] = arr[cur];
} else {
break;
}
}
//若发现 v 比 arr[cur] 大,就把 v 放到arr[cur]的后米N
//后面的位置不是 arr[cur]+1 而是 arr[cur]+ gap;
arr[cur +gap] = v;
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
希尔排序是一个不稳定排序
时间复杂度:
1.最好,O(n);
2.平均 O(n^1.3);
3.最坏O(n^2)
空间复杂度:O(1)
2.2. 选择排序
2.2.1 直接选择排序
1、原理
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。
2、 实现
- 代码实现
package Java42_0323;
import java.util.Arrays;
public class MySort {
//3.选择排序,核心思路:基于打擂台的思想,每次从待排序区间,取出最小值,
//放到擂台上(擂台就是待排序区间的最开始的位置)
//空间复杂度 O(1),不稳定排序
public static void selectSort(int[] arr) {
//[0,bound)已排序区间
//[bound arr,length) 带排序区间
int bound = 0;
for (; bound < arr.length; bound++) {
//里层循环打擂台
for(int cur = bound + 1; cur <arr.length; cur++) {
//擂台就是bound 位置的元素
//取cur位置和擂台进行比较
if (arr[cur] < arr[bound]) {
//新元素胜出了,就需要交换两个元素的位置
swap(arr, cur, bound);
}
}
}
}
public static void swap(int[] arr, int cur, int bound) {
int tem = arr[cur];
arr[cur] = arr[bound];
arr[bound] = tem;
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
直接选择排序是一个不稳定排序
时间复杂度:O(n^2)
空间复杂度: O(1)
2.2.2 堆排序
1、 原理
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意: 排升序要建大堆;排降序要建小堆。
2、实现
- 代码实现
package Java42_0323;
import java.util.Arrays;
import java.util.Stack;
public class MySort {
//4、堆排序
//堆排序可以理解为是一种选择排序的优化~
//直观上。如果是升序排序的话,我们可以建立一个小堆。每次取堆顶元素,循环取 N 次。就得到了一个有序排序
//建立一个大堆,把对顶元素(当前最大值),和数组最后一个元素交换(最大值来到了数组末尾)。
//前面的部分从 0 进行调整。
//1、建堆,
//2、先把根节点和最后一个元素进行交换,并且把最后一个元素从堆上删除。此时最后一个元素就是已排序区间,前面是待区间
//3、继续 2 步骤,若干次之后,排序完成
public static void heapSort(int[] arr) {
//1、先进行建堆
createHeap(arr);
//2. 循环进行交换堆顶元素和最后一个元素的过程,并且删除最后一个元素
int heapSize = arr.length;
for(int i = 0; i < arr.length; i++) {
swap(arr,0,heapSize-1);
//删除最后一个元素
heapSize--;
//从 0 这个元素进行向下调整
shiftDown(arr,heapSize,0);
}
}
public static void shiftDown(int[] arr, int size, int index) {
int parent = index;
int child = 2 * parent + 1;
while (child < size) {
//第一次比较,找出左右子树的最大值
if (arr[child + 1] < size && arr[child + 1] > arr[child]) {
child = child + 1;
}
//希望这次调整结束后,无论如何 child 都指向左右子树的最大值
//第二次比较,拿刚刚的 child 位置元素和parent 进行比较
//若不符合。则交换
if (arr[parent] < arr[child]) {
int tem = arr[parent];
arr[parent] = arr[child];
arr[child] = tem;
} else {
break;
}
//更新循环变量
parent =child;
child = 2* parent+1;
}
}
public static void createHeap(int[] arr) {
for (int i = (arr.length - 1-1) / 2; i >= 0; i--) {
shiftDown(arr, arr.length, i);
}
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
时间复杂度 :O(n * log(n))
空间复杂度:O(1)
堆排序是不稳定排序。
2.3 交换排序
2.3.1 冒泡排序
1、原理
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序。
2、实现
- 代码
package Java42_0323;
import java.util.Arrays;
public class MySort {
//5、冒泡泡排序
//时间复杂度O(N^2)
//空间复杂度O(1)
//稳定排序
//基本思路
//比较两个相邻的元素,看是否符合升序要求,不符合就交换,
//一趟遍历下来就能找到最大值(从前往后遍历)
//一趟遍历下来就能找到最小值(从后往前遍历)
public static void BubbleSort(int[] arr) {
//[0,bound)已排序区间
//[bound,length)待排序区间
int bound = 0;
for (; bound < arr.length; bound++) {
for(int cur = arr.length-1; cur > bound; cur--) {
if (arr[cur]<arr[cur-1]) {
//不符合升序,交换
swap(arr, cur, cur-1);
}
}
}
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
冒泡排序是稳定排序
时间复杂度为:O(N^2)
空间复杂度为:O(1)
2.4.2 快速排序
1、 原理
- 从待排序区间选择一个数,作为基准值(pivot);
- Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
- 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 = 1,代表已经有序,或者小区间的长度 = 0,代表没有数据。
2、实现
- 代码
package Java42_0323;
import java.util.Arrays;
public class MySort {
//6、快速排序
public static void quickSort(int[] arr) {
//使用一个附中方法进行递归
//辅助方法多多了两个参数,用来针对数组上的哪个区间
_quickSort(arr,0,arr.length-1);
}
public static void _quickSort(int[] arr, int left, int right) {
if(left >= right) {
return;
}
//使用 partition 方法来进行整理
//index是left和right的重合位置
int index= partition (arr,left, right);
_quickSort(arr,left,index -1);
_quickSort(arr,index+1,right);
}
public static int partition(int[] arr, int left, int right) {
//选取基准值
int v = arr[right];
int i = left;
int j = right;
//如果是先从左往右,后重右往左找,重合的时候 ij 指向的一定是比 ij 大的元素
//如果是先重右往左找,后从左往右 重合的时候 ij 指向的一定是比 ij 小的元素
while(i <j) {
//从做往右找一个比基准值大的元素
while(i < j &&arr[i] <= v) {
i++;
}
//从右往左找一个比基准值小的元素
while(i <j && arr[j] >= v) {
j--;
}
swap(arr, i, j);
}
//若 i 和 j 重叠了。此时需要把当前基准值元素和 i j重叠位置进行交换
//当 i 和 j 重合是的时候, i j 一定指向的元素是比基准值大的元素。!!!
swap(arr, i,right);
return i;
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
快速排序是不稳定的排序;
时间复杂度 :O(n * log(n))
空间复杂度:O(log(n))
2.4 归并排序
1、原理
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide
and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子
序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
2、实现
- 代码
package Java42_0323;
import java.util.Arrays;
public class MySort {
//7.归并排序
public static void mergeSort(int[] arr) {
//创建一个新的方法辅助递归,新方法中多了两个参数、
//表示是针对当前数组中的哪个部分进行排序
//也是一个前闭后开区间
_mergeSort(arr,0,arr.length);
}
//[left ,right) 前闭后开区间
//right - left 区间中的元素个数
public static void _mergeSort(int[] arr,int left, int right) {
if (right - left <= 1) {
//如果当前待排序区间里只有 1 个元素或者没有元素
//就直接返回。不需要任何排序动作
return;
}
//先把当前 [left,right) 区间一分为二
int mid = (left + right) / 2;
// 分成了两个区间
// [left, mid), [mid,right)
//当左侧区间的 _mergeSort 执行完毕后,
// 就认为 [left, mid)就已经是有序区间了
_mergeSort(arr, left, mid);
//当右侧区间的 _mergeSort 执行完毕后,
// 就认为 [mid, right)就已经是有序区间了
_mergeSort(arr,mid,right);
//接下来把两个有序数组合并到一起
merge(arr,left,mid,right);
}
// merge 方法本身功能是把两个有序数组合并成一个有序数组
//待合并的两个数组就分别是:
//[left,mid)、[mid,right)
public static void merge(int[] arr, int left, int mid, int right) {
//创建一个临时的数组,用来存放临时的合并结果。
//希望这个数组能存下合并后的结果 right - left
int[] tem = new int[right - left];
//当前要把新的元素放到 tem 数组的哪个下标(temSize)上
int temSize = 0;
int l = left;
int r = mid;
while (l <mid && r <right) {
//归并排序是稳定排序!!!
// 前提是,此处的判断条件不要写成 arr[l] < arr[r];
if (arr[l] <= arr[r]) {
//arr[l] 比较小,就把这个元素先插入到 tem 数组末尾
tem[temSize] = arr[l];
temSize++;
l++;
} else {
//arr[r] 比较小,就把这个元素插入到 tem 数组的末尾
tem[temSize] = arr[r];
temSize++;
r++;
}
}
//当其中一个数组遍历完了之后,就把另外一个数组的剩余部分都拷贝过来
while (l < mid) {
//剩下的是左半边数组
tem[temSize] = arr[l];
temSize++;
l++;
}
while (r < right) {
//剩下的是右半边数组
tem[temSize] = arr[r];
temSize++;
r++;
}
//最后一步,再把临时空间的内容都拷贝回参数数组中
//需要把 tem 中的内容拷贝回 arr 的 [left ,right) 这一段空间里
// [left ,right) 很可能不是从 0 开始的
for (int i = 0; i < tem.length; i++) {
arr[left +i] = tem[i];
}
}
public static void main(String[] args) {
int[] arr = {4,5,7,8,9,2,3};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 结果
3、性能分析
归并排序是稳定排序;
时间复杂度:O(NlogN);
空间复杂度:O(N).