排序
1概念
排序就是使一串记录,按照其中的某个或者某些关键字的大小,递增或递减的排列起来的操作。
2.稳定性
3内部排序与外部排序
七大排序
1.堆排序
public static void heapSort(int[] arr) {
// 1.先将arr进行heapify调整为最大堆
// 从最后一个非叶子节点开始进行siftDown操作
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
siftDown(arr,i,arr.length);
}
// 此时arr就被我调整为最大堆
for (int i = arr.length - 1; i > 0; i--) {
// arr[0] 堆顶元素,就是当前堆的最大值
swap(arr,0,i);
siftDown(arr,0,i);
}
}
/**
* 元素下沉操作
* @param arr
* @param i 当前要下沉的索引
* @param length 数组长度
*/
private static void siftDown(int[] arr, int i, int length) {
while (2 * i + 1 < length) {
int j = (i << 1) + 1;
if (j + 1 < length && arr[j + 1] > arr[j]) {
j = j + 1;
}
// j就是左右子树的最大值
if (arr[i] > arr[j]) {
// 下沉结束
break;
}else {
swap(arr,i,j);
i = j;
}
}
}
2.冒泡排序
概念:.从当前元素起,向后依次比较每一对相邻元素,若逆序则交换
2.对所有元素均重复以上步骤,直至最后一个元素
动图:
//冒泡排序
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {//i是记录循环次数的,与数组元素无关,不要带入
boolean isSwaped=false;
for (int j = 0; j < arr.length-1-i; j++) {//此处为什么是length-1-i:经过第i次排序后已经有i个较大值依次排在了最后面
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
isSwaped=true;
}
}
if(!isSwaped){
break;
}
}
}
}
3.选择排序
3.1直接选择排序
在无需区间中选择一个最大/小值,放在无序区间的最前面(此处与原本在无需区间最前面的值进行位置的调换,而不是顺次直接移动)
动图:
public static void selectionSort(int arr[]){
//最开始,无需区间[0..n],有序区间[]
for (int i = 0; i < arr.length-1; i++) {
//min为当前最小变量
int min=i;
//从剩下的元素选择最小元素
for (int j = i+1; j < arr.length; j++) {
if(arr[j]<arr[min]){
min=j;
}
}
//min索引一定对应现在无需区间中找到的最小值,排在无序区间的最前面(与原本乱序排在无需区间最前面的值进行位置交换)
swap(arr,min,i);
}
}
private static void swap(int[] arr, int i, int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
3.2双向选择排序
概念:同时选出组大值和最小值,放在无需区间的最前面和最后面
注意此时:原本找出的max值就排在第一位(low的位置),min先和low交换位置之后,原本的max就换到了原本min的位置上,所以此时将max=min;
public static void selectionOP(int[] arr){
int low=0;
int high=arr.length-1;
//数组合理的情况(等于的情况为数组只有一个元素)
while(low<=high){
int max=low;
int min=low;
for (int i = low+1; i <=high ; i++) {
if(arr[i]<arr[min]){
min=i;
}
if(arr[i]>arr[max]){
max=i;
}
}
//此时min索引对应的值为该数组最小的值
swap(arr,low,min);
if(max==low){
//不论交换与否,这时的情况为原来判断出的max值排在第一位,此时max==low
max=min;//前面的min已经和真正的max换了位置,所以此时换位之后,max=min
}
swap(arr,max,high);
low++;
high--;
}
}
4.插入排序(非常稳定)
4.1直接插入排序
概念:每次从待排序的区间中将第一个元素插入到已排序区间的合适位置,知道整个数组有序。
代码过程描述举例:
public static void insertionSort(int[] arr){
//已排序[0,i]
//未排序[i...n]
for (int i = 1; i < arr.length; i++) {
//默认arr[0]为有序,从i=1开始
for (int j = i; j >=1 && arr[j]<arr[j-1] ; j--) {
swap(arr,j,j-1);
}
}
}
4.2折半插入排序
概念:在插入排序中,都是在有序区间中选择插入位置,使用二分法来定位元素的插入位置。
举例:
public static void insertionSortBS(int[] arr){
for (int i = 1; i <arr.length ; i++) {
int val=arr[i];
int left=0;
int right=i;
while(left < right){
int mid=left+((right-left) >> 1);
if(val < arr[mid]){
right = mid;
}else{
left = mid+1;
}
}
//将left至i的值,依次向后搬移一位
for (int j =i; j > left ; j--) {
arr[j]=arr[j-1];
}
//腾出left的位置,即为插入的位置
arr[left]=val;
}
}
5.希尔排序
代码:
public static void shellSort(int[] arr){
int gap=arr.length >> 1;
while(gap > 1){
insertionSortByGap(arr,gap);
gap=gap >> 1;
}
if(gap==1) {
insertionSort(arr);
}
}
private static void insertionSortByGap(int[] arr, int gap) {
for (int i = gap; i <arr.length ; i++) {
//不断向前扫描相同gap的元素
//j-gap从j位置喀什向前还有相同步数的元素
for (int j = i; j - gap >= 0 && arr[j] < arr[j-gap] ; j-=gap) {
swap(arr,j,j-gap);
}
}
}
6.归并排序
概念:归并排序是采用分治法的典型应用。将已有序的子序列合并,得到完全有序的序列。
时间复杂度:O(nlogN)
归并排序是一个稳定的nlogN排序算法
此处的稳定指的是时间复杂度稳定并且归并排序也是一个稳定性排序算法。即,无论集合中的元素如何变化,归并排序的时间复杂度一直都是nlogN,不会退为O(N^2).
代码:
public static void mergeSort(int[] arr){
mergeSortInternal(arr,0,arr.length-1);
}
/**
* 在arr[l...r]上进行归并排序
* @param arr
* @param l
* @param r
*/
public static void mergeSortInternal(int[] arr,int l,int r){
if(l >= r){
//数组中没有元素或者只有一个元素,直接输出
return;
}
int mid = l + ((r-l)>>1);
//将原数组拆分为左右两个小区间,直至每个区间只有一个元素,归过程结束
mergeSortInternal(arr,l,mid);
mergeSortInternal(arr,mid+1,r);
//分完之后,进行并的操作
merge(arr,l,mid,r);
}
/**
* 合并两个子数组arr[l...mid] arr[mid+1...r]为一个大数组arr[l...r]
* @param arr
* @param l
* @param mid
* @param r
*/
private static void merge(int[] arr, int l, int mid, int r) {
//创建一个临时数组
int[] aux=new int[r-l+1];
for (int i = 0; i <aux.length ; i++) {
aux[i]=arr[i+l];
}
//i为左侧小数组的开始索引,这里的i与j都是arr中的索引,aux的索引要在i基础上减l
int i=l;
//j为右侧小数组的开始索引
int j=mid+1;
//k表示当前正在合并的原数组arr的索引下标
for (int k = l; k <= r ; k++) {
//首先考虑 左侧两侧区间任意一个处理完毕的情况
if(i > mid){
//左侧区间处理完毕
arr[k]=aux[j-l];
j++;
}else if(j > r){
//右侧区间处理完毕
arr[k] = aux[i-l];
i++;
}else if(aux[i-l] <= aux[j-l]){
//左侧区间元素小于右边区间元素,则将左侧区间元素直接赋给arr
arr[k] = aux[i-l];
i++;
}else {
//右侧区间元素小于左侧区间元素,则将右侧区间元素直接赋给arr
arr[k] = aux[j-l];
j++;
}
}
}
优化:
若arr[mid] < arr[mid+1] (即左侧区间的最大值,已经小于了右侧区间的最小值),此时整个区间就已经有序了,不需要再进行merge操作。
在小区间上,直接使用插入排序来优化,没必要元素一直拆分到1的位置。
r-l <= 15,使用插入排序
不使用递归的归并排序(merge部分相同,不同的是 分组 即归的过程)
public static void mergeSortNonRecursion(int[] arr) {
// 最外层循环表示每次合并的子数组的元素个数
for (int sz = 1; sz <= arr.length; sz += sz) {
// 内层循环的变量i表示每次合并的开始索引
// i + sz 就是右区间的开始索引,i + sz < arr.length说明还存在右区间
for (int i = 0; i + sz < arr.length ; i += sz + sz) {
merge(arr,i,i + sz - 1,Math.min(i + sz + sz - 1,arr.length - 1));
}
}
}
海量数据处理方法: