八大排序
插入排序 直接插入
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
// 直接插入排序
// 每次从无序区间中拿第一个值插入到已经排序区间的合适位置,直到整个数组有序
public static void insertionSort(int[] arr){
//已排序区间[0...i)
//未排序区间[i...n]
for (int i=1;i< arr.length;i++){
//待排序区间的第一个元素是arr[i]
//从待排序区间的第一个元素往前看,找到合适的插入位置
for (int j=i;j>0;j--){
//arr[j]是待排序区间的第一个元素,arr[j-1]是已排序区间的最后一个元素
//此时这两个元素有序,直接下次循环
if (arr[j]>=arr[j-1]){
break;
}else {
swap(arr,j,j-1);
}
}
}
}
插入排序 希尔排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
先选定一个整数gap gap一般选取数组长度的一半或三分之一,将待排序的数组先按gap分组 不同组之间内部使用插入排序 排序之后再将gap除以或gap除以三
public static void shellSort(int[] arr){
int gap= arr.length>>1;
while (gap>1){
//预处理
insertionSortByGap(arr,gap);
gap=gap>>1;
}
//此时gap=1全集合已经接近有序只需要全集合来一次插入排序
insertionSort(arr);
}
//按gap分组进行插入排序
private static void insertionSortByGap(int[] arr, int gap) {
for (int i=gap;i<arr.length;i++){
//不断向前扫描相同gap的元素
//j-gap>0从j位置向前gap还有元素
for (int j=i;j-gap>0&&arr[j]<arr[j-gap];j-=gap){
swap(arr,j,j-gap);
}
}
}
选择排序 基本选择排序
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
public static void selectionSort(int[] arr) {
//最开始无序区间从0到n[0....n]
for (int i=0;i< arr.length ;i++){
//定义一个变量min存储当前最小值
int min=i;
//默认第一个元素是最小值
//从剩下的元素中选择最小值
for (int j=i+1;j< arr.length;j++){
if (arr[j]<min){
min=j;
}
}
// min这个索引一定对应了当前无序区间中找到的最小值索引,换到无序区间最前面i
swap(arr,min,i);
}
}
选择排序 双向选择排序
简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:
public static void selectionSortOP(int[] arr){
int low=0;
int high= arr.length-1;
//low=high时即无序区间只剩下一个元素数组已经有序
while (low<=high){
int min=low;
int max=low;
for (int i=low+1;i<=high;i++) {
if (arr[i] < arr[min]) {
min = i;
}
if (arr[i] > arr[max]) {
max = i;
}
}
//此时min一定是当前无序区间的最小值索引,与low交换位置
swap(arr,low,min);
if (max==low){
//最大值已经被换到min这个位置
max=min;
}
swap(arr,max,high);
low+=1;
high-=1;
}
}
归并排序
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
private static void mergeSortInternal(int[] arr, int l, int r) {
if (r - l <= 15) {
// 2.小区间直接使用插入排序
insertionSort(arr,l,r);
return;
}
int mid = l + ((r - l) >> 1);
// 将原数组拆分为左右两个小区间,分别递归进行归并排序
// 走完这个函数之后 arr[l..mid]已经有序
mergeSortInternal(arr,l,mid);
// 走完这个函数之后 arr[mid + 1..r]已经有序
mergeSortInternal(arr,mid + 1,r);
// 1.只有左右两个子区间还有先后顺序不同时才merge
if (arr[mid] > arr[mid + 1]) {
merge(arr,l,mid,r);
}
}
public static void mergeSort(int[] arr) {
mergeSortInternal(arr,0,arr.length - 1);
}
private static void insertionSort(int[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
swap(arr,j,j - 1);
}
}
}
private static void merge(int[] arr, int l, int mid, int r) {
// 先创建一个新的临时数组aux
int[] aux = new int[r - l + 1];
// 将arr元素值拷贝到aux上
for (int i = 0; i < aux.length; i++) {
aux[i] = arr[i + l];
}
// i就是左侧小数组的开始索引
int i = l;
// j就是右侧小数组的开始索引
int j = mid + 1;
// k表示当前正在合并的原数组的索引下标
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[k] = aux[i - l];
i ++;
}else {
// 右侧区间的元素值较小
arr[k] = aux[j - l];
j ++;
}
}
}
冒泡排序
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean isSwaped = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr,j,j + 1);
isSwaped = true;
}
}
if (!isSwaped) {
break;
}
}
}