参考文章:十大经典排序算法总结(Java语言实现)_java中前两项相加等于下一项的排序-CSDN博客
参考视频:
排序算法:快速排序【图解+代码】_哔哩哔哩_bilibili
冒泡排序
人如其名,就是像冒泡泡一样的排序。那么泡泡以何种方式、何种条件冒上去呢
数据对象一般是针对一个数组,相邻两个数两两比较。
从第一个数开始,设定若第一个数大于第二个数 那么将第一个数放在第二个数后面(两数交换位置)。然后再用第二个数(前两数中的大者)比较第三个数,若第二个数大于第三个数,那么将两者换位置。这样两两比较过一轮(轮完每一个数后),放在最后的那个数自然成了最大的。
但是这样一轮下来,虽然最后一位数肯定大于倒数第二位数,但是前面的数之间的关系是没有的,因为每一轮比较只产生一个max。
所以还需要从第一个数开始继续进行下一轮两两比较,但是第二轮不需要最后一位数参与比较 。
依次类推,需要比较的轮数就是n个数两两比较的轮数就是n-1轮。
写代码时注意:
两层for循环处,一定要理解外层是循环轮数,内层是每轮的遍历两两比较,且内层循环的次数在减少,每当外层完成一轮循环,对应内层则应该少遍历一次。
这里我菜的坑是 外层for(int i ;i<arr.length;i++)循环 n-1次无错,将内层循环了for(int j=1;j<arr.length-i;j++) n-i-1次也无错,但这意味着我的arr[j]可以遍历到最后一位,其实这也不伤大雅,但是内层循环的处理却没有考虑到arr[j+1]这个值不存在,下标溢出了,这时的内层循环应该内层从arr[1]开始,采用arr[j-1]与arr[j]来比较,这样就不会导致下标溢出。也可以内层for(int j=0;j<arr.length-i-1;j++),内层从arr[0]开始采用arr[j+1]与arr[j]比较。
代码如下:
public class maopao {
public static int[] maopao_sort(int[] arr) {
int max=0;
if(arr.length==0) {
return arr;
}
System.out.println("length:"+arr.length);
for(int i=0;i<arr.length-1;i++) {
// 第一层for循环,循环的是轮数,n-1轮,第i轮
for(int j=1;j<arr.length-i;j++) {
// 第二层for循环,是遍历每一轮的两两比较,设定大者放后面
if(arr[j-1]>arr[j]) {
max=arr[j-1];
arr[j-1]=arr[j];
arr[j]=max;
}
}
}
return arr;
}
}
选择排序
选择排序,选出来排序
基本原理:在数组中选出最大(小)值,放在数组最前(后)面,再在剩下的数组中选出最大值,放在数组最前(后)面
分析:每次只能找出一个最大值,而找一次最大值需要循环一次,所以需要外层循环n-1次
每次找到的最大值,记住最大值的index下标,然后将arr[index]与数组最前面/后面交换置。
代码如下:
public class selection {
public static int[] selection_sort(int[] arr) {
if(arr.length==0) {
return arr;
}
for(int i=0;i<arr.length-1;i++) {
int min=arr[i];
int index=i;
for(int j=i;j<arr.length;j++) {
// 这里设定将最小值放前面
if(min>arr[j]) {
min=arr[j];
index=j;
}
// 找到最小值
}
int temp_min=min;
arr[index]=arr[i];
arr[i]=temp_min;
}
return arr;
}
}
在复现选择排序过程中,需要注意的是,内层循环一定要遍历到最后一位数,才能找出最小值。
插入排序
一组元素中设定前一段是有序的,取后面的元素插入到有序元素中。从有序元素的后面往前面查找,只要找到了自己的位置,立即停下。
在数组中先将前面的下标为0-index的元素排好序,再每次循环取index+1的数去插入到前面。
插入时,先将index+1的数存入current中以免丢失,将index存入preindex中
开始内部循环从后往前遍历,比current大的preindex数往后挪,每挪一次,current的preindex都应该-1,preindex用来确定current插入的地方。
挪完以后再将current插入preindex+1处
代码如下:
public class insert {
public static int[] insertionSort(int[] array) {
if (array.length == 0)
return array;
int current;
for (int i = 0; i < array.length - 1; i++) {
current = array[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < array[preIndex]) {
array[preIndex + 1] = array[preIndex];
preIndex--;
}
array[preIndex + 1] = current;
}
return array;
}
}
希尔排序
是直接插入排序的进阶,分组插入排序,也称之为缩小增量排序
实质是分组插入排序,先将数据分组,相隔gap的元素为一组
然后在组内进行插入排序,把第一个按gap分组的所有组排序完了之后,再缩小gap的值,进行第二次分组,进行组内插入排序。
相信大家学习希尔排序时都会产生这样一个疑问,既然要对分组进行插入排序,那为什么直接对原数组进行插入排序呢?
这是因为当第一次分组插入排序之后的每次分组组内元素相当于是“基本有序的”,而以后的分组在用插入排序时,插入排序对于基本有序的数组的时间复杂度是接近O(n)的。
这当然大大节约了时间 提高了效率,特别是当数据特别大的时候,比如数组长度为10000时,使用直接插入排序的比较次数和交换次数差不多是希尔排序的一百倍。
还有一个问题是为什么组内要使用直接插入排序不使用冒泡排序呢?
因为插入排序有一个提前终止的性质,当要插入的元素从已有序数组中从后往前找位置时,一当找到了自己插入的位置就不会往前看了。
如果是使用冒泡排序,没有提前终止,它的效率不及插入排序,当然可以对冒泡排序进行优化,当冒泡的时有一轮没有发生交换,就需要立刻终止
代码:
public class shell {
int gap=arr.length/2;
int current=0;
int preIndex=0;
while(gap>0) {
for(int i=gap;i<arr.length;i++) {
current=arr[i];
preIndex=i-gap;
while(preIndex>0&¤t<arr[preIndex]) {
arr[preIndex+gap]=arr[preIndex];
preIndex-=gap;
}
arr[preIndex+gap]=current;
}
gap=gap/2;
}
return arr;
}
/*
* 也可以提前设置好间隔gap
* while (gap < len / 3) { // 动态定义间隔序列
gap = gap * 3 + 1;
}
* */
}
归并排序
分治思想
递归
先分:将数组分成左右两子序列再将左右两子序列分别分成左右两 子序列直到分到左右两子序列为一个元素
合并:从左右两序列中的第一个元素开始看,若左边第一个元素大于右边第一个元素,那么将右边第一个元素放入辅助数组,
再让左边第一个元素与右边第二个元素比较,小的放入辅助数组,对应序列index向后移动一位。
直到把左(右边)边的元素全部放进辅助数组,而右(左边)边子序列还有元素时,则全部放进辅助函数。
代码:
public class merge {
//分而治之的处理:两子序列之间的比较后,放入辅助数组,返回这个辅助数组,将两最底层子序列合并
public static int[] merge(int[]left,int[]right) {
int[] arr= new int[left.length+right.length];
for(int index=0,i=0,j=0;index<arr.length;index++) {
// index是arr下标,i是左子序列下标,j是右子序列下标
if(i>=left.length) {
// 先判断左右子序列是否已经为空(是否已经遍历完),若左子序列为空,则将右子序列剩下的元素放入辅助数组
arr[index]=right[j++];
}
else if(j>=right.length) {
arr[index]=left[i++];
}
else if(left[i]>right[j]) {
arr[index]=right[j++];
}
else if(left[i]<right[j]) {
arr[index]=left[i++];
}
}
return arr;
}
// 分 并 采用递归实现
public static int[] mergeSort(int[]arr) {
if(arr.length<2)
{
return arr;
}
int mid=arr.length/2;
int[] left=Arrays.copyOfRange(arr, 0, mid);
int[] right=Arrays.copyOfRange(arr, mid, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
}
快速排序
分治思想
设定基准,遍历数组。找出所有比基准小的数,放在前面,那么剩下 的所有比基准大的数自然在后面了,这样遍历一次下来,这个基准就在他自己正确的位置了。再在基准前面的元素中 设定基准,遍历数组重复以上步骤,在基准后面的元素中也重复以上步骤。这样一直用基准划分 直到被划分后的两侧是一个元素为止,一个元素是有序的。
其实每次遍历就是在找比基准小的数,放在基准前面。
实现:
遍历一遍数组,遇到比基准小的元素,放入arr[index],放入后index++;比基准小的数的个数为:index,意味着数组下标为0到index-1的元素都是小于基准的数,直到遍历完成,此时数组下标为index的数是一个不小于基准的数,应该放在数组后面,所以将下标为index的数和基准(arr[high])交换位置,至此完成第一遍划分。此后再将被划分的两部分,递归重复该过程。
代码:
public class quick {
// 排序,小的放前面
public static int partition(int[]arr,int low,int high) {
int index=0;//index用来计比基准小的元素的下标
int i=0;//i遍历所有元素
int pivot=(int)(low+Math.random()*(high-low+1));//随机生成基准下标
swap(arr, high, pivot);//将基准放在最后一位
for(i=0;i<high;i++) {
if(arr[i]<arr[high]) {
swap(arr, i, index);
index++;
}
}
// for循环结束说明所有比基准小的数都放在了数组前面部分,index记录了比基准小的数的位置,
// 那么现在所有比基准小的数都找到了,所以index就是基准正确的位置
swap(arr, index, high);
return index;
}
// 递归的划分,
public static void quickSort(int[]arr,int low,int high){
if(low<high) {
int mid=partition(arr,low,high);
quickSort(arr, mid+1, high);
quickSort(arr, low, mid-1);
}
}
// 交换函数
public static int[] swap(int arr[],int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
return arr;
}
}
计数排序
计数排序是一种线性时间非比较类排序
核心思想是,找出原数组中每个元素的出现次数存入计数数组,原数组元素作为计数数组的index,出现次数作为计数数组的元素值。累加计数数组的元素值,累加以后的元素值表示小于等于对应index的数出现的次数。再排序输出结果数组,累加后的值-1是对应index(原数组元素)的下标。
值得一提的是,再排序输出时,最好倒序遍历原数组,这样可以保持稳定性。如果正序遍历原数组会导致相同元素的相对位置发生变化,不稳定。
代码如下:
public class count {
public static int[]countSort(int[]arr){
// 找出原数组的最大元素,用原数组元素作累计数组下标
if(arr.length==0)return arr;
int max=arr[0];
int min=arr[0];
for(int i=1;i<arr.length;i++) {
if(max<arr[i]) max=arr[i];
if(min>arr[i]) min=arr[i];
}
// 计数数组
int[] countArr=new int[max+1];
for(int i=min;i<max+1;i++) {
int flag=0;
for(int j=0;j<arr.length;j++) {
if(i==arr[j]) flag++;
}
countArr[i]=flag;
}
// 累加数组
for(int i=1;i<countArr.length;i++) {
countArr[i]=countArr[i-1]+countArr[i];
}
// 将原数组按照累加数组排序:
int[] output=new int[arr.length];
for(int i=arr.length-1;i>=0;i--) {
int index=countArr[arr[i]]-1;
countArr[arr[i]]--;
output[index]=arr[i];
}
return output;
}
}