常用八大排序算法----Java版实现

前言

  • 此篇来介绍常用的排序算法Java版实现,JavaScript版请戳这里。其实网上的介绍讲解汗牛充栋,并且有很多优秀的文章,写此篇的目的是为了加深下自己的印象,还有融入点自己的想法。当笔记使啦~
  • 注:排序元素顺序以从小到大排序为主;

冒泡排序

  • 冒泡排序属于相对较简单的排序,过程如下:每次遍历【数组】一遍,确定出最大的那个数放在数组的最右边,当然确定的这个数就不在下次遍历【数组】内啦,依次确定【数组】中的每一个元素。(从左->右:小->大),代码实现如下
public class BubbleSort {

	public static void main(String[] args) {
		//定义无序数组
		int[] arr = {3,9,6,-4,12,5,1};
		bobbleSort(arr);
	}
	public static void bobbleSort(int[] arr) {
		for(int i=0;i<arr.length;i++) { //控制循环次数
			for(int j=0;j<arr.length-i-1;j++) { //用于每次确定一个最大的数
				if(arr[j+1] < arr[j]) {
					//定义中间变量,用于交换
					int temp = arr[j+1];
					arr[j+1] = arr[j];
					arr[j] = temp;
				}
			}
		}
		//打印结果
		//System.out.println(Arrays.toString(arr));
	}
}
  • 性能:十万数据在我机器上大约需要10秒。

选择排序

  • 选择排序相对于冒泡排序性能上有所提高,并且很可观。过程大致是这样:每次在循环区间找出最小/最大的数的索引【选择过程】,然后将此数放在指定位置【队头/队尾】,记住找到的是索引哦~!
  • 实现如下
public class SelectSort {

	public static void main(String[] args) {
		int[] arr = {3,9,6,-4,12,5,1};
		selectSort(arr);
	
	}
	public static void selectSort(int[] arr) {
		
		for(int i = 0;i<arr.length-1;i++) { //控制选择区间大小
			//定义中间变量记录最小值的索引
			int minIndex = i;
			for(int j=i+1;j<arr.length;j++) { //在选择区间中选择最小的数并记录其index
				if(arr[j]<arr[minIndex]) {
					minIndex = j;
				}
			}
			int temp = arr[i];
			arr[i] = arr[minIndex];
			arr[minIndex] = temp;
		}
		//System.out.println(Arrays.toString(arr));
	}
}
  • 性能上来看:十万数据在我机器上大约仅需要两秒钟~~

插入排序

  • 插入排序性能上略优于冒泡,它的过程是这样的,面对一段需要排序的数组,我们截取一段默认为它是有序的【从索引为0的元素开始,长度为1】,然后依次在剩下的无序数组中选取元素填充到我们的有序数组中【当然需要在适当的位置放入这个元素】,最后,有序数组的长度等于原数组的长度,这样,便实现了排序的效果。
  • 插入排序在寻找元素的适当位置时,寻找的是索引。
  • 插入的位置可以是“有序数组”的头部,内部,或是尾部。
  • 实现如下:
public class InsertSort {

	public static void main(String[] args) {
		//定义排序数组
		int[] arr = new int[] {10,9,6,-4,12,5,1};
		insertSort(arr);
		
	}
	public static void insertSort(int[] arr) {
		for(int i=0;i<arr.length-1;i++) {
			//定义插入索引
			int j = i+1;
			//定义中间变量
			int temp = arr[j];
			while (j >= 1 && arr[j-1]>temp) {
				arr[j] = arr[j-1];
				j--;
			}
			arr[j]=temp;
		}
		System.out.println(Arrays.toString(arr));
	}
}
  • 插入排序的性能:十万数据在我机器上大约需要3秒钟。

希尔排序

  • 希尔排序相对来说是比较难理解的一种排序算法,网上有很多单独讲解的优质文章,在这里提供几个关键点仅助理解记忆。
  • 铺垫:希尔排序是对插入排序的优化版排序算法【了解希尔排序之前建议掌握插入排序的思想与操作】。1、插入排序对基本有序的数组性能是非常高的,希尔排序首先是小构造基本有序的数组。2、插入排序元素只能移动一位,希尔排序可针对相距较远的元素进行交换。
  • 过程:希尔排序每次将数组的length/2作为步长,【步长的作用:对元素进行分组】对每组元素进行插入排序。
  • 注意:1、分组的规则是跳跃式的,根据步长跳跃。2、代码实现时,每次循环对多组同时进行插入排序,而不是一组插入排序进行完再进行第二组。3、分组是逻辑上的分组,不是实际将数组拆分开来。
  • 过程演示:
    希尔排序图片描述
  • 相关解释:粉色数组为目标数组/绿色数组是逻辑上的分组/浅红色数组是逻辑数组上经过插入排序后的结果。
  • 代码实现:
  • 交换方式上希尔排序/移位方式上的希尔排序:
public class ShellSort {

	public static void main(String[] args) {
		int[] arr = {8,9,7,2,4,1,0,5,3,6};
		shellSort(arr);
		shellSortT(arr);
	}
	private static void shellSort(int[] arr) {
		// 定义中间变量用于交换
		int temp = 0;
		int count = 0;
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			for (int i = gap; i < arr.length; i++) { // 用于在步长的基础上遍历元素
				for (int j = i - gap; j >= 0; j -= gap) { // 外层加,内层减
					if (arr[j] > arr[j + gap]) { // 控制排序
						temp = arr[j];
						arr[j] = arr[j + gap];
						arr[j + gap] = temp;
					}
				}
			}
			
		}
	}
	//对交换式的shell排序的优化->移位式
	private static void shellSortT(int[] arr) {
		//增量gap,逐步缩小增量
		for(int gap = arr.length/2;gap > 0;gap /= 2) {
			//从第gap个元素,逐个对其所在的组进行直接插入排序
			for(int i = gap; i < arr.length; i++) {
				//定义需要插入的索引
				int j = i;
				int temp = arr[j];
				while(j - gap >= 0 && temp < arr[j-gap]) {
					//移位
					arr[j] = arr[j-gap];
					j -= gap;
					}
					//当退出while循环后,给temp找到插入的位置
					arr[j] = temp;
				}
		}
		//System.out.println(Arrays.toString(arr));
	}
}

归并排序

  • 归并排序采用分治思想,先分后治,过程是这样的:我们逻辑上认为可以将数组分为很多个有序的子数组,然后我们依次两两将其合并,合并后依然使其有序,我们即可得到一个合并后的大数组,这时,此数组就是有序的。
  • 合并过程:遍历两个有序数组,按顺序分别挑出两个数组中的元素,其中元素较小的“入库”,随后再从挑出较小元素的数组中取出一个元素,与之比较,小者入库。依次往复。。。最终处理完所有的元素,将库中的元素拷贝到元素中中。
  • 实现代码如下:
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));

	}
	//分+合方法
	private static void mergeSort(int[] arr,int left,int right,int[] temp) {
		if(left < right) {
			int mid = (left+right) / 2;
			//向左递归分解
			mergeSort(arr, left, mid, temp);
			//向右递归分解
			mergeSort(arr, mid+1, right, temp);
			//合并
			merge(arr, left, mid, right, temp);
		}
	}
	/**
	 * @param arr 需要排序的目标数组
	 * @param left 左边有序序列的初始索引
	 * @param mid 中间索引
	 * @param right 右边索引
	 * @param temp 中间临时数组
	 * 
	 * */
	private static 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中
		//直到左右两边的有序序列,有一边处理完为止
		while(i <= mid && j<= right) {
			//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
			//即将左边的当前元素,填充到temp数组
			//然后t++,i++
			if(arr[i] <= arr[j]) {
				temp[t] = arr[i];
				t+=1;
				i+=1;
			}else { //反之,将右边元素填充到temp数组
				temp[t] = arr[j];
				t+=1;
				j+=1;
			}
		}
		//(二)
		//把所有剩余元素填充到temp
		//左
		while(i<= mid) {
			temp[t] = arr[i];
			t+=1;
			i+=1;
		}
		//右
		while(j<= right) {
			temp[t] = arr[j];
			t+=1;
			j+=1;
		}
		//(三)
		//将temp数组的元素拷贝到arr
		//注意,并不是每次都拷贝所有
		//tempLeft = 0,right = 1;tempLeft = 2,right = 3;
		//最后tempLeft = 0,right = 7;
		t = 0;
		int tempLeft = left;
		while(tempLeft <= right) {
			arr[tempLeft] = temp[t];
			t += 1;
			tempLeft += 1;
		}
	}
}

快速排序

  • 我的数据结构老师口中的快速排序如下:
    快速排序by数据结构老师

  • 而我印象中的过程是这样滴:在数组中找到一个标量,分别在数组的两头构造两个指针,使其向中间扫描,当然,两个指针相遇后就不再向下进行了。左指针遇到大于标量的时候停下,右指针遇到小于标量的时候停下。然后两指针指向的数字相互交换。最后将标量与两指针相遇的地方交换。

  • 实现如下:

public class QuickSort {
	/**
	 * 测试
	 * @param args
	 */
	public static void main(String[] args) {
		//49,33,61,82,75,12,25,58,29
		int[] num = {49,33,61,82,75,12,25,58,29};
		QuickSort(num,0,num.length-1);
	}
	/**
	 * 快速排序
	 * @param num	排序的数组
	 * @param left	数组的前针
	 * @param right 数组后针
	 */
	private static void QuickSort(int[] num, int left, int right) {
		//如果left等于right,即数组只有一个元素,直接返回
		if(left>=right) {
			return;
		}
		//设置最左边的元素为基准值
		int key=num[left];
		//数组中比key小的放在左边,比key大的放在右边,key值下标为i
		int i=left;
		int j=right;
		while(i<j){
			//j向左移,直到遇到比key小的值
			while(num[j]>=key && i<j){
				j--;
			}
			//i向右移,直到遇到比key大的值
			while(num[i]<=key && i<j){
				i++;
			}
			//i和j指向的元素交换
			if(i<j){
				int temp=num[i];
				num[i]=num[j];
				num[j]=temp;
			}
		}
		num[left]=num[i];
		num[i]=key;
		QuickSort(num,left,i-1);
		QuickSort(num,i+1,right);
	}
}

基数排序

  • 基数排序详解地址 以前用JavaScript写过一次,并且个人感觉还挺详细,这里直接上代码啦:
public class RadixSort {

	public static void main(String[] args) {
		//测试
		int arr[] = {23,6,189,45,9,287,56,1,798,34,65,652,5};
		radixSort(arr);
		System.out.println(Arrays.toString(arr));

	}
	private static void radixSort(int arr[]) {
		//创建一个栈数组
		List<Queue<Integer>> list = Arrays.asList(new Queue[10]);
		//取出最大的数
		int max = 0;
		for(int i=0;i<arr.length;i++) {
			if(arr[i]>max) {
				max = arr[i];
			}
		}
		//取位数
		int length = String.valueOf(max).length();
		//System.out.println(length);
		//通过位数进行遍历
		for(int i=0,n=1;i<length;i++,n=n*10) {
			//填入桶中
			for(int k = 0;k<arr.length;k++) {
				//取余数【10进制】
				int ys = arr[k]/n%10;
				if(list.get(ys) == null) {
					list.set(ys, new LinkedList<Integer>());
				}
				list.get(ys).add(arr[k]);
			}
			//将数字取出
			int index = 0;
			for(int j=0;j<list.size();j++) {
				if(list.get(j)!=null) {
				while(!list.get(j).isEmpty()) {
					arr[index] = list.get(j).poll();
					index++;
				}
			}
			}
		}
	}
}

堆排序

  • 堆排序利用了二叉树的性质,首先要构建一个大顶堆或者小顶堆,将堆顶元素取出,将剩下元素重新构建顶堆结构。这样往复循环,便可得到一个有序数组。
  • 实现代码如下
public class HeapSort {

	public static void main(String[] args) {
        int[] arr = {3,4,8,6,9,11,7,1,14,12};
        System.out.println("排序前"+Arrays.toString(arr));
        heapSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    //编写一个堆排序算法
    public static void heapSort(int[] arr){
        int temp = 0;
        System.out.println("堆排序");
        //将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            adjustHeap(arr,i,arr.length);
        }
        /**
         * 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
         * 重新调整结构,使其满足定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤
         * 直到整个序列有序
         * */
        for(int j=arr.length-1;j>0;j--){
            //交换
            temp=arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            adjustHeap(arr,0,j);
        }

    }
    /**
     * @param arr 待调整的数组
     * @param i 表示非叶子节点在数组中的索引
     * @param length 表示对多少个元素继续调整,length是在逐渐减少
     * */
    public static void adjustHeap(int arr[],int i,int length){
        //先取出当前节点的值
        int temp = arr[i];
        //开始调整 k*2+1 是i的左子节点
        for(int k=2*i+1;k<length;k=2*k+1){
            if(k+1 < length && arr[k+1]>arr[k]){ //说明右子节点的值大于左子节点的值
                k++;
            }
            if(arr[k] > temp){ //交换子节点与父节点的位置
                arr[i] = arr[k];
                i=k;
            } else {
                break;
            }
        }
        //当for循环结束后,我们已经将以 i 为父节点的树最大值,放在了最顶(局部)
        arr[i] = temp; //将temp值放到调整后的位置
    }
}

结语

  • 完结撒花~
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值