一、排序算法
1、冒泡排序
/*
* 冒泡排序规则
* (1) 一共进行 (数组的大小 - 1) 次大的循环
* (2)每一趟排序的次数在逐渐的减少
* (3) 如果我们发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序。这个就是优化
*/
public void sort(int[] arr){
for(int i = 0;i < arr.length-1;i++){
boolean flag = false;
for(int j = 0;j < arr.length - i - 1;j++){
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if(!flag){
return;
}
}
}
2、插入排序
public void sort(int[] arr){
for(int i = 1;i < arr.length;i++){
int insertVal = arr[i]; //待插入的值
int insertIndex = i-1; //插入索引
//寻找插入位置,从有序表的最后一个位置从后向前遍历比较,如果插入值小于遍历的值,则遍历的值赋给遍历值的后一位
if(insertVal < arr[insertIndex]){
while(insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//跳出while循环表示已经找到插入位置,将插入值赋给索引为(插入索引+1)的位置
arr[insertIndex+1] = insertVal;
}
}
}
3、选择排序
/*
* 说明:
* 1. 选择排序一共有 (数组大小 - 1) 轮排序
* 2. 每1轮排序,又是一个循环, 循环的规则:
* 2.1 先假定当前这个数是最小数
* 2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
* 2.3 当遍历到数组的最后时,就得到本轮最小数和下标
* 2.4 交换
*/
public void sort(int[] arr){
for(int i = 0;i < arr.length-1;i++){
int min = arr[i]; //假定arr[i]为最小数
int index_min = i; //记录最小数的索引
//从第i+1个数开始寻找最小数,如果找到了,则将找到的数记录为最小数以及记录最小数的索引
for(int j = i+1;j < arr.length;j++){
if(arr[j] < min){
min = arr[j];
index_min = j;
}
}
//判断如果最小数的索引不等于i,表示i后面的数有比假定的最小数更小的,则进行交换
if(index_min!=i){
arr[index_min] = arr[i];
arr[i] = min;
}
}
}
4、希尔排序
/*
希尔排序是插入排序的优化,可以参考插入排序
希尔排序是将数组分类gap组,gap从arr.length/2开始,到1结束
对gap组的每一组进行插入排序
*/
public void sort(int[] arr){
//将数据分成gap组
for(int gap = arr.length/2;gap > 0;gap /=2){
//遍历各组中所有的元素
for(int i = gap;i < arr.length;i++){
int insertVal = arr[i];
int insertIndex = i - gap;
//将该元素插入到它所在组中的正确的位置(使用插入排序)
if(insertVal < arr[insertIndex]){
while(insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + gap] = arr[insertIndex];
insertIndex-=gap;
}
arr[insertIndex + gap] = insertVal;
}
}
}
}
5、归并排序
public void sort(int[] arr,int left,int right,int[] temp){
if(left < right) {
int mid = (left + right) / 2; //中间索引
//向左递归进行分解
sort(arr, left, mid, temp);
//向右递归进行分解
sort(arr, mid + 1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//1
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while(i <= mid && j <= right){
if(arr[i] <= arr[j]){
temp[t] = arr[i];
i++;
t++;
}else{
temp[t] = arr[j];
j++;
t++;
}
}
//2
//把有剩余数据的一边的数据依次全部填充到temp
while(i <= mid){
temp[t] = arr[i];
i++;
t++;
}
while(j <= right){
temp[t] = arr[j];
j++;
t++;
}
//3
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left;
while(tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
6、快速排序
public void sort(int[] arr,int left, int right) {
//如果right <= left则表示以及排序完毕
if(right <= left){
return;
}
//选择数组中索引值为left的值作为基准值
//最终会将数组调整为基准值左边都是小于等于基准值的数,右边都是大于等于基准值的数
int chooseVal = arr[left];
//创建两个遍历指针
int l =left;
int r =right;
while(l < r){
//从数组遍历右边的指针位置向前遍历
//如果遇到小于基准值的数就将该值放在遍历左边的指针位置,然后遍历左边的指针向后移动一位
while(l < r && arr[r] >= chooseVal){
r--;
}
if(l < r){
arr[l] = arr[r];
l++;
}
//从数组遍历左边的指针位置向后遍历
//如果遇到大于基准值的数就将该值放在遍历右边的指针位置,然后遍历右边的指针向前移动一位
while(l < r && arr[l] <= chooseVal){
l++;
}
if(l < r){
arr[r] = arr[l];
r--;
}
}
//跳出循环后,表示左右指针进行了重合,将基准值放在该索引位置
arr[l] = chooseVal;
//将基准值的左边作为新的数组进行快排
sort(arr, left, l-1);
//将基准值的右边作为新的数组进行快排
sort(arr, l+1, right);
}
7、堆排序
public void sort(int[] arr){
//构造一个临时变量存储中间数据
int temp = 0;//1、先将无序序列构建成一个堆
for(int i = arr.length/2-1;i >= 0;i--){
adjustHeap1(arr,i,arr.length);
}
//2、将堆顶元素与末尾元素交换,将最大(最小)元素换到数组末端
//3、重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
for(int i = arr.length - 1;i > 0;i--){
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
adjustHeap1(arr,0,i);
}
}
/**
* 将一个数组二叉树(可以是完整的二叉树,也可以是子二叉树)调整为大顶堆
* @param arr 待调整的完整的数组
* @param i 在数组中索引值为i的数,调整的是以该数为根节点的二叉树,该数所在的二叉树的结点为非叶子结点
* @param length 需要调整的数组的长度
*/
public void adjustHeap1(int[] arr,int i,int length){
//记录在数组中索引值为i的数
int temp = arr[i];
//利用for循环开始调整
//k = 2*i+1是i结点的左子结点
for(int k = 2*i+1;k < length;k = k*2+1){
//判断i结点的左结点大还是右结点大,取较大的结点
if(k+1 < length && arr[k] < arr[k+1]){
k++;
}
//判断子结点的值是否大于父结点的值,如果大于,则将子结点的值赋给父结点,否则就调整完毕
if(arr[k] > temp){
arr[i] = arr[k];
//由于我们的调整是从树的最下面开始向上调整,害怕交换完子父结点的值之后,破坏了以该子结点为父结点的子二叉树
//的结构,因此将k赋给i继续检查子二叉树的结构
i = k;
}else{
//由于我们的调整是从树的最下面开始向上调整,因此这里如果子结点的值都小于父结点,表示该二叉树已经调整为大顶堆
break;
}
}
//将temp值赋给arr[i],注意,这里可能已经将k的值赋给了i,因此是i表示子结点的位置了
arr[i] = temp;
}
/**
* 将一个数组二叉树(可以是完整的二叉树,也可以是子二叉树)调整为小顶堆
* @param arr 待调整的完整的数组
* @param i 在数组中索引值为i的数,调整的是以该数为根节点的二叉树,该数所在的二叉树的结点为非叶子结点
* @param length 需要调整的数组的长度
*/
public void adjustHeap2(int[] arr,int i,int length){
//记录在数组中索引值为i的数
int temp = arr[i];
//利用for循环开始调整
//k = 2*i+1是i结点的左子结点
for(int k = 2*i+1;k < length;k = k*2+1){
//判断i结点的左结点小还是右结点小,取较小的结点
if(k+1 < length && arr[k] > arr[k+1]){
k++;
}
//判断子结点的值是否小于父结点的值,如果小于,则将子结点的值赋给父结点,否则就调整完毕
if(arr[k] < temp){
arr[i] = arr[k];
//由于我们的调整是从树的最下面开始向上调整,害怕交换完子父结点的值之后,破坏了以该子结点为父结点的子二叉树
//的结构,因此将k赋给i继续检查子二叉树的结构
i = k;
}else{
//由于我们的调整是从树的最下面开始向上调整,因此这里如果子结点的值都小于父结点,表示该二叉树已经调整为大顶堆
break;
}
}
//将temp值赋给arr[i],注意,这里可能已经将k的值赋给了i,因此是i表示子结点的位置了
arr[i] = temp;
}
二、测试算法
public static void main(String[] args) {
int[] arr = {4,5,8,9,5,10,11,15,13,-2,-99,101};
sort(arr);
System.out.println(Arrays.toString(arr));
}