1.冒泡排序
- 冒泡排序,总共需要MaxSize趟排序,第一趟排序完成后,最大的数一定在最后,所以下次排序不用参与,
故每一趟有(MaxSize-1)-i 次排序,时间复杂度为O( n^2) 。
package Sort;
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {3,9,-1,10,-2};
Sort(arr);
}
public static void Sort(int[] arr) {
int temp =0;
for (int i = 0; i < arr.length; i++) { //代表趟数,第一趟过后最大的一定在最后
for(int j = 0; j< arr.length-1-i; j++) {
if(arr[j]>arr[j+1]) { //后面的比前面的小,
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
System.out.print("第"+(i+1)+"趟排序的结果:");
System.out.println(Arrays.toString(arr));
}
}
}
- 冒泡排序的优化:
如发现在某一趟排序的过程中,没有发生一次交换,则我们认为该数组已经有序,可以提前结束;
package Sort;
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {3,9,-1,10,-2};
Sort(arr);
}
public static void Sort(int[] arr) {
int temp =0;
boolean flag = true; // 提前结束标志,必须是某一趟未发生一次交换
for (int i = 0; i < arr.length; i++) { //代表趟数,第一趟过后最大的一定在最后
flag = true; //重新归位
for(int j = 0; j< arr.length-1-i; j++) {
if(arr[j]>arr[j+1]) { //后面的比前面的小,
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
flag = false;
}
}
if(flag) {
break; //某一趟一次也没有交换,排序完成
}
System.out.print("第"+(i+1)+"趟排序的结果:");
System.out.println(Arrays.toString(arr));
}
}
}
- 结果对比:可以发现优化的冒泡排序比未优化的冒泡排序效率更高:
2.选择排序
- 代码:
package Sort;
//选择排序,每一次找出最小的一个,找出后记录最小的位置,与前面的进行交换,如arr[0],接下来就是找arr[1],
import java.util.Arrays;
public class SearchSort {
public static void main(String[] args) {
int arr[] = {3,9,-1,10,-2};
Sort(arr);
}
public static void Sort(int[] arr) {
int temp = 0; //用于交换的暂时变量
int min = 0; //记录索引的位置
for (int i = 0; i < arr.length-1; i++) {
min = i;
for(int j = min+1; j< arr.length; j++) {
if(arr[min]>arr[j]) { //后面的比前面的小,
min = j; //记录较小数的位置,每次都是与小的比较
}
}
//将小的放前面,arr[0] =min,arr[1]= minsecond,....
temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
System.out.print("第"+(i+1)+"趟排序的结果:");
System.out.println(Arrays.toString(arr));
}
}
public void show(int arr[]) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
}
- 结果:
选择排序和冒泡排序都是从小到大的排列,但是选择排序每次大循环中通过记录小元素的位置,从而每次只交换一次,效率比冒泡排序的效率要高一些,时间复杂度也是O(n^2)。
3.插入排序
- 插入排序类似于打扑克,取出未排序的一张牌插入到已排序的牌中,取出的一张牌是在已排序好的牌中从后向前查找,直到查找到比当前牌小的那个位置,然后插入。
- 源代码:将前面的元素固定,将后面的元素与前面的元素进行比较并插入到合适的位置
package Sort;
import java.util.Arrays;
//插入排序,每次从数组中挑一个插入到合适的位置{9,3,-1,10,-2}->{3,9,-1,10,-2}
//固定第一个位置,为第二个数插入到合适的位置,
public class InsertSort {
public static void main(String[] args) {
//int arr[] = {9,3,-1,10,-2};
int arr[] = {34,101,119,1,-1,2};
InSort(arr);
}
public static void InSort(int []arr) {
for (int i = 1; i < arr.length; i++) { //从第一个后面的元素开始遍历
int temp = arr[i]; //暂时保存要插入的数,方便位置移动
int j = i-1; //前面的位置起点
//temp是要插入的数,需要不断地与前面的元素比较,找到合适的位置
while(j>=0 && arr[j]>temp){ //寻找前面合适的位置,前面的比后面的大,
arr[j+1]= arr[j]; //将前面大的数复制到后面一位
j--; //继续遍历,
}
arr[j+1]= temp; //找到合适的位置放入该数,
System.out.print("第"+i+"轮的插入顺序:");
System.out.println(Arrays.toString(arr));
}
}
}
- 测试结果:
- 1.希尔排序是插入排序的一种改进,针对插入排序移动的数据元素过多,提出一种按间隔大小的增减来进行比较排序从而实现从小到大的排序。
插入排序:对头两个元素进行比较排序,将列表的第三个值插入到头两个值的恰当位置,如此第四个元素插入到前三个元素的恰当位置,依次下去进行排序。 - 1.1优点:希尔排序通过加大排序元素之间的间隔,并对这些间隔的元素进行插入排序,从而使得某元素可以进行大幅度的移动,当完成该间隔排序后,会通过缩小间隔进行再次的插入排序。
1.2 间隔的计算
通过h=h*3+1来循环计算,其中间隔值不大于数组长度,减小间隔通过h=(h-1)/3
1.3希尔排序代码的实现
package paixu;
import java.util.Scanner;
//希尔排序方法
public class Xier {
public static void main(String[] args) {
Scanner rde = new Scanner(System.in);
System.out.println("用逗号隔开");
String str = rde.next().toString();
String[] arr = str.split(",");
long[] brr = new long[arr.length];
for(int i=0;i<arr.length;i++) {
brr[i] = Integer.parseInt(arr[i]);
}
long[] crr = shellsort(brr);
showarray(crr);
}
//打印方法
public static void showarray(long[] arr) {
for(int i=0;i<arr.length;i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
//希尔排序实现
public static long[] shellsort(long[] arr) {
int step = 1;//初始化间隔值
while(step<arr.length/3) { //计算循环间隔值
step = step*3+1; //(1,4,13,40....)
}
while(step>0) {
//插入排序
for(int i=step;i<arr.length;i++) {
long tend = arr[i];
int j = i;
while (j>step-1 && arr[j-step]>=tend ) {
arr[j]=arr[j-step];
j-= step; //按间隔进行交换
}
arr[j]=tend;
}
//排序完成,减小间隔
step =(step-1)/3;
}
return arr;
}
}
4.希尔排序
- 源代码
package Sort;
import java.util.Arrays;
public class ShellSort {
//希尔排序,通过分组的方式进行小组排序,保持小组的相对位置不变,两两可以交换或者插入
public static void main(String[] args) {
int []arr = {8,9,1,7,2,3,5,4,6,0};
Sort(arr);
}
//两两交换的方式,
public static void Sort(int []arr) {
int length = arr.length/2; //定义分组的步长
int count = 0;
while(length>=1) {
int temp = 0;
//{8,9,1,7,2,3,5,4,6,0}-》{8,3},{9,5},{1,4},{7,6},{2,0}
for (int i = length; i < arr.length; i++) {
for (int j = i-length; j >= 0; j-=length) {
if(arr[j]>arr[j+length]) {
//采用交换的方式
temp = arr[j+length];
arr[j+length] = arr[j];
arr[j] = temp;
}
}
}
length = length/2; //重新计算步长
System.out.println("希尔排序的第"+(++count)+"轮排序结果:"+Arrays.toString(arr));
}
System.out.println(Arrays.toString(arr));
}
//采用与前面元素比较找到位置插入的方式,在分组的情形下采用插入的方式进行排序,
public static void Sort2(int []arr) {
int length = arr.length/2; //定义分组的步长
int count = 0;
while(length>=1) {
//{8,9,1,7,2,3,5,4,6,0}-》{8,3},{9,5},{1,4},{7,6},{2,0}
for (int i = length; i < arr.length; i++) {
int j = i;
int temp = arr[j]; //记录待插入的元素和其位置
//寻找要插入的位置,跟前面的元素比较
while(j-length>=0 && arr[j-length]>temp) { //前面元素比后面大
arr[j] = arr[j-length];
j -= length; //继续遍历
}
//退出后找到位置,插入到前面
arr[j] = temp;
}
length = length/2; //重新计算步长
System.out.println("希尔排序的第"+(++count)+"轮排序结果:"+Arrays.toString(arr));
}
}
}
- 测试结果,在分组完后,对元素进行排序时,采用插入方式(插入排序)的效率大于两两交换(冒泡排序)的效率
5.快速排序
- 快速排序的平均时间复杂度是 O(nlogn),最坏情况下的时间复杂度是 O(n^2)。
- 代码:这是选择了中间的数作为基准,移动两边的数使得左边的数小,右边的数大,然后再左右分别递归排序
package Sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int arr[] = {-9,78,0,23,-567,70};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right) {
int l = left ; //左下标
int r = right; //右下标
int prvoke = arr[(left+right)/2];
int temp = 0;
while(l<r) {
//找出要交换的位置下标,因为大的放右边,小的放左边
while(arr[r]>prvoke) { //寻找右边小于中轴数的下标
r -= 1;
}
while(arr[l]<prvoke) { //寻找左边大于中轴数的下标
l += 1;
}
//如果l>=r,就已经全部排行顺序了
if(l>=r) {
break;
}
//进行交换数据
temp = arr[l];
arr[l]= arr[r];
arr[r]= temp;
//如果交换完,发现arr[l] ==prvoke或者arr[r] ==prvoke,则需要移动
if(arr[l]==prvoke) {
r-=1; //相当于prvoke向前移动了,则后半部分也需要向前移动
}
if(arr[r]==prvoke) {
l+=1; //相当于prvoke向后移动了,则前半部分需要后移动
}
}
//防止排序数据栈溢出
if(l==r) {
l+=1; //相当于前面的元素往后移动一个位置
r-=1; //相当于后面的元素往前移动一个位置
}
//执行左边的数据进行排序,左递归
if(r>left) {
sort(arr,left,r);
}
//执行右边的数据进行排序,右递归
if(l<right) {
sort(arr,l,right);
}
}
}
- 代码:这是选择了第一个的数作为基准,用挖坑的意思来移动元素,退出循环时,最中间的坑是放标准数的
然后再左右分别递归排序(程序更容易实现)
package Sort;
import java.util.Arrays;
public class QuickSort2 {
public static void main(String[] args) {
int arr[] = {-9,78,0,23,-567,70};
quick_sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
//选择第一个数为标准线,将小的数放左边,大的数放右边
public static void quick_sort(int []arr, int left, int right)
{
if(left<right) { //保持边界条件
int l = left;
int r = right;
int temp = arr[l]; //这里先把第一个元素挖掉,暂时保存
while(l<r) {
while(l<r && arr[r]>=temp) { //找右边小的数放在前面坑里
r--;
}//退出便找到位置
if(l<r) {
//找右边小的数放在前面坑里
arr[l] = arr[r];
}
while(l<r && arr[l]<temp) { //找左边大的数放在后面坑里
l++;
}//退出便找到位置
if(l<r) {
//找右边小的数放在前面坑里
arr[r] = arr[l];
}
}//退出循环则,l==r
//将标准数移动到中间
arr[l] = temp;
//将左边的数递归排好
quick_sort(arr, left, l-1);
//将右边的数递归排好
quick_sort(arr, l+1, right);
}
}
}
6.归并排序
- 代码:
package Sort;
import java.util.Arrays;
//归并排序,先将数组元素拆分成多个元素的小组,在各自的小组进行排序后再进行合并
public class MergeSort {
public static void main(String[] args) {
int []arr = {8,4,5,7,1,3,6,2};
int [] temp = new int[arr.length]; //用于拷贝的数组
MergeSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
//分解的过程
public static void MergeSort(int[] arr,int left,int right,int []temp) {
if(left<right) {
int middle = (left+right)/2;
//左边拆分
MergeSort(arr,left,middle,temp);
//右边拆分
MergeSort(arr,middle+1,right,temp);
//合并
Merge(arr,left,middle,right,temp);
}
}
/**
* @param arr 待排序的数组
* @param left 左边有序初始下标
* @param mid 中间的下标
* @param right 右边的下标
* @param temp 用于存数据的暂时数组
*/
//合并的过程
public static void Merge(int[] arr,int left,int mid,int right,int []temp) {
int l = left; //左边元素的起始下标
int middle = mid; //中间的下标
int r = mid+1; //右边元素开始的下标
int t = 0; //temp元素指向的下标
while(l<=middle && r<=right) { //控制好两边分组元素的边界
if(arr[l]<=arr[r]) { //左边分组的元素比右边分组的元素小
temp[t++] = arr[l++];
}
else {
temp[t++] = arr[r++];
}
}
//添加剩余未添加的元素进去
while(l<=middle) {
temp[t++]=arr[l++];
}
while(r<=right) {
temp[t++]=arr[r++];
}
//拷贝到arr当中去,以便后面调用
t = 0;
int tempList = left; //从最左边开始拷贝
while(tempList<=right) {
arr[tempList++]=temp[t++];
}
}
}
7.基数排序
- 源代码:
package Sort;
import java.util.Arrays;
public class JishuSort {
//基数排序,每次根据各元素的位置个位,十位,百位的数字大小,分别放入十个桶中(二维数组),
//一轮一次然后copy回给原数组,直到最高位排好序并放入原数组,排序便完成
public static void main(String[] args) {
int []arr = {53,3,542,748,14,214};
Sort(arr);
}
public static void Sort(int []arr) {
//用于存放各自位置排序的元素列如53在第一轮,53%10=3,应该放入第三个桶
int [][]bukts = new int[10][arr.length];
//统计各个桶中的元素个数,下标对应桶个数,元素对应放的元素个数
int []elemcount = new int[10];
//计算最大元素的位数
int MaxSize = arr[0];
for (int i = 1; i < arr.length; i++) {
if(arr[i]>=MaxSize) {
MaxSize = arr[i];
}
}
int MaxSizeLength = (MaxSize+"").length(); //计算需要循环的周期
for (int k=0,n=1;k<MaxSizeLength;k++,n=n*10){
//将原数组的元素进行计算并放入桶中
for(int i=0;i<arr.length;i++) {
//第一轮n=1,第二轮n=10,...
int index = arr[i]/n%10;
//将元素放入桶中,index代表第几个桶,elemcount[index]代表该桶元素的个数
bukts[index][elemcount[index]] = arr[i];
elemcount[index]++;
}
int t = 0;
//将桶中的元素放入原数组
for (int i = 0; i < elemcount.length; i++) {
//判断某个桶中有数据
if(elemcount[i]!= 0) {
//执行数据添加操作
for(int j = 0; j < elemcount[i]; j++) {
arr[t++] = bukts[i][j];//依次从桶中取数据
}
}
//清空桶中的数据,方便下一轮继续放入
elemcount[i]= 0;
}
System.out.println("第"+(k+1)+"次基数排序的结果为"+Arrays.toString(arr));
}
}
}
- 结果:第一次个位数排序2->3->4->8,第二次十位数排序0->1->4->5,第三次百位数排序0->2->5->7
1.堆排序算法
- 排序原理:升序用大顶堆,降序用小顶堆,以数组【4 6 8 5 9】为例:
- 源代码
- 步骤一:根据arr.length/2-1,找到非叶子节点,建立出局部的大顶堆,直到所有非叶子节点均比较完毕
- 步骤二:将大顶堆的最大值交换到最后,并在下次建立时不考虑该值
- 步骤三:重复步骤一和步骤二,继续建立大顶堆和交换,直到最后一个节点
- 公式:非叶子节点的计算是arr.length/2-1,根节点i与左右子节点的关系为21+1 / 2i+2;
package First;
/*
* @Author sds
* @Description //堆排序,这里采用大顶堆来进行排序,并进行交换,使得数组升序排列
*/
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int [] arr={4,6,8,5,9};
//完成一次的大顶堆的构建,依次遍历非叶子节点,找到当前最大值
for (int i = arr.length/2-1;i>=0;i--) {
heapSort(arr,i,arr.length);
}
//将大顶堆的最大值换到最后的位置上,进行下一次大顶推建立(除最大值的节点)
int temp = 0;
for (int j =arr.length-1;j>0;j-- ) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
//进行下一次大顶堆的建立
heapSort(arr,0,j); //传入从当前的开始,并排除已找到的最大值
}
System.out.println(Arrays.toString(arr));
}
/*
* @Author sds
* @Description //堆排序,这里采用大顶堆来进行排序,每次传入的非叶子节点下标作为根节点进行排成大顶堆
* @Param arr:传入的数组,i:非叶子节点的下表,当前需要排序的长度
*/
public static void heapSort(int []arr,int i, int length){
int temp = arr[i];//将当前的非叶子根节点保存,以便找到更大的值交换
//从其左叶子节点开始遍历,下次遍历就进入下一层
for(int k =2*i+1;k<length;k=2*k+1){
if(k+1<length && arr[k]<arr[k+1]){ //左叶子节点小于右叶子节点
k++; //指向右叶子节点
}//这样便找到子节点最大一个
//子节点与根节点进行比较
if(arr[k]>temp){ //需要交换
arr[i] = arr[k];
i = k; ///记录已经到达的位置,方便下次遍历
}else{
break; //不满足的话结束这一循环,进入下一次
}
}
//退出循环,则最大值跑到该根节点,形成局部大顶堆
//记得把temp的值放入最后交换的位置,若未交换则还是原来的下标
arr[i] = temp;
}
}
- 时间复杂度比较