JavaSE-数组八大排序

冒泡排序

原理

  1. 数组元素两两比较,交换位置,大元素往后放。
  2. 每比较一轮少一次两两比较。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BogVqPLb-1653485641091)(imgclip.png "imgclip.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {24, 69, 80, 57, 13};
		int temp = 0;
		for(int i = 0; i < arr.length-1; i++){
			// arr.length-1轮,第一轮执行arr.length-1次,每一轮减少一次
			for(int j = 0; j < arr.length-1-i; j++){
				if(arr[j] > arr[j+1]){
					temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;				
				}
			}
		}
		System.out.print(Arrays.toString(arr));  //=> [13, 24, 57, 69, 80]
	} 
}

选择排序

原理

  1. 从0开始索引,依次和后面的元素进行比较,小的元素往前放,进过一轮比较后最小的元素出现在最小索引处。
  2. 每比较一轮少一次两两比较。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwcLbg3b-1653485673180)(imgclip.png "imgclip.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {24,69,80,57,13};
		int temp = 0;
		for(int i = 0; i < arr.length-1; i++){
			for(int j = i+1; j < arr.length; j++){
				if(arr[i] > arr[j]){
					temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;				
				}
			}
		}
		 System.out.print(Arrays.toString(arr));  //=> [13, 24, 57, 69, 80]
	}
}

直接插入排序

原理

  1. 从 1 索引处开始,将一个记录插入到一个长度为 m 的 有序集合 中,使之仍然保持有序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3PvCI31-1653485718821)(imgclip.png "imgclip.png")]

public class Main{
	public static void main (String[] args){
		int[] arr = {24,69,80,57,13};
		int temp = 0;
		for (int i = 1; i < arr.length; i++){
			for (int j = i; j > 0; j--) {
				if (arr[j] < arr[j-1]) {
				    temp = arr[j];
				    arr[j] = arr[j-1];
				    arr[j-1] = temp;
				}	
			}
		}
		System.out.println(Arrays.toString(arr));
		// 转换成 while 循环
		int[] arr2 = toWhile(arr);
		System.out.println(Arrays.toString(arr2));
	}

	public static int[] toWhile(int[] arr){
		int temp = 0;
		// 循环次数
		for (int i = 1; i < arr.length; i++) {
			int j = i;			
			while (j>0&&arr[j] < arr[j-1]) {
				temp = arr[j];
				arr[j] = arr[j-1];
				arr[j-1] = temp;
				j--
			}
		}
		return arr;
	}
}

希尔排序

原理

实现演示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFJ4eBZe-1653485738176)(imgclip_2.png "imgclip_2.png")]

  1. 先将原表按 增量 ht 分组,每个字文件按照直接插入法排序。同样,用下一个 增量 ht/2 将文件再分成子文件,再进行 直接插入法排序。直到 ht = 1 时整个文件排好序。
  2. 关键在于选择合适的增量,经过一轮排序后,就会然序列大致有序。然后不断缩小增量直至增量为 1。
  3. 基于插入排序。直接插入排序法的优化,效率更高。
  4. 直接插入排序其实就是增量为 1 的希尔排序。
  5. 使用 knuth 序列或者选择数组长度的一半作为增量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tAgOIeV4-1653485738183)(imgclip.png "imgclip.png")]

推导过程

public class Main{
	public static void main(String[] args){
		int[] arr = {24,69,80,57,13};
		int l = 2;
		for (int i = l; i < arr.length; i++){
			// 情况:步长为1,j 必须大于 0 
			for(int j = i; j > l-1; j -= l){
                // 如果后面比前面大
				if(arr[j-l] > arr[j]){
					change(arr, j, j-l);
				}
			}
		}
		// 实际上就是直接插入排序 ...
		l = 1;
		for (int i = l; i < arr.length; i++){
			// 情况:步长为1,j 必须大于 0 
			for(int j = i; j > l-1; j -= l){
				if(arr[j-l] > arr[j]){
					change(arr, j, j-l);
				}
			}
		}
		System.out.println(Arrays.toString(arr));
	}
	public static void change(int[] arr,int j,int t){
		int temp = arr[j];
        		arr[j] = arr[t];
        		arr[t] = temp;
	}
}

优化

  1. 希尔排序的思想就是合理的选取增量。所以就引出了 克努特序列 Knuth
public class Main{
	public static void main(String[] args){
		int[] arr = {24,69,80,57,13};
		for(int l = arr.length / 2; l > 0; l/=2){
			for (int i = l; i < arr.length; i++){
				// 情况:步长为1,j 必须大于 0 
				for(int j = i; j > l-1; j -= l){
					if(arr[j-l] > arr[j]){
						change(arr, j, j-l);
					}
				}
			}
		}
		System.out.println(Arrays.toString(arr));
	}
	public static void change(int[] arr,int j,int t){
		int temp = arr[j];
        arr[j] = arr[t];
        arr[t] = temp;
	}
}
  1. 克努特序列
int h = 1;
h = h*3 - 1;

增量:1,4,13,40,121,364,...

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zdjAGest-1653485738186)(imgclip_1.png "imgclip_1.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {24,69,80,57,13};
		int a = 1;
		while(a<=arr.length/3){
			a = a*3+1;
		}
		for(int l = a; l > 0; l = ( l - 1 ) / 3){
			for (int i = l; i < arr.length; i++){
				// 情况:步长为1,j 必须大于 0 
				for(int j = i; j > l-1; j -= l){
					if(arr[j-l] > arr[j]){
						change(arr, j, j-l);
					}
				}
			}
		}
		System.out.println(Arrays.toString(arr));
	}
	public static void change(int[] arr,int j,int t){
		int temp = arr[j];
        arr[j] = arr[t];
        arr[t] = temp;
	}
}

快速排序

原理

  1. 先定义一个元素作为基准数,一般会把数组中最左边的数作为基准数。
  2. 从两边可是检索,先从右边检索比基准数小的。再从左边检索比基准数大的,如果检索成功就交换两个元素。重复检索。
  3. 直至两边的检索位置重合停止检索,将该处检索位置对应的元素与基准数进行交换。检索完成后基准数归位,获得左右分区进行递归调用。
  4. 进行递归调用,更改基准数再进行检索。重复 1 2 3 步骤。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HS2H3gkn-1653485780222)(imgclip.png "imgclip.png")]

public class Main{
	public static void main(String[] args){
		// int [] arr = {6,1,2,7,9,3,4,5,10,8};
		// quickSort(arr, 0, arr.length-1);
		// System.out.println(Arrays.toString(arr));  //=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

		// 进行速度检测
		int[] arr = new int[100_0000];
		Random r = new Random();
		for(int i = 0; i < arr.length; i++){
			int num =  r.nextInt();
			arr[i] = num;
		}
		long starttime = System.currentTimeMillis();
		quickSort(arr, 0, arr.length-1);
		long endtime = System.currentTimeMillis();
		System.out.println(endtime-starttime);   //=> 205
	}

	public static void quickSort(int[] arr, int left, int right){
		// 左边索引不能大于右边索引
		if(left > right){
			return;		
		}
		// 定义基准数与最左最右索引
		int base = arr[left];
		int i = left;
		int j = right;
		// 如果i和j相遇则停止检索
		while( i != j ){
			// 如果右索引索引元素比基准数小停止检索
			while(arr[j] >= base && j > i){
				j--;
			}
			// 如果左索引索引元素比基准数大停止检索
			while(arr[i] <= base && j > i){
				i++;
			}
			// 停止检索跳出循环交换位置
			int temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
		// 相遇时当前位置元素与基准数进行交换
		arr[left] = arr[i];
		arr[i] = base;
		// 递归进行重复检索,控制范围
		// 基准数左边开始排,再右边
		quickSort(arr, left, i-1);
		quickSort(arr, j+1, right);
	}
}

归并排序

原理

  1. 归并排序的思想就是先递归分解数组,再合并数组。
  2. 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
  3. 使用临时数组进行数组保存。

分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkUh7BBX-1653485793977)(20180607002713740.png "20180607002713740.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {2,3,5,1,23,6,78,34,23,4,5,78,34,65,32,65,76,32,76,1,9};
		// 拆分
		sort(arr, 0, arr.length-1);
		System.out.println(Arrays.toString(arr));
	}

	// 拆分成最小单元
	public static void sort(int[] arr, int startIndex, int endIndex){
		// 计算中间索引
		int center = (startIndex + endIndex) / 2;
		// 递归拆分,一直进行对半拆分,直到startIndex与endIndex重合,表示仅剩余一个元素。
		if(startIndex < endIndex){
			sort(arr, startIndex, center);
			sort(arr, center+1, endIndex);
			// 进行归并
			merge(arr, startIndex, center, endIndex);
		}
	}

	// 归并
	public static void merge(int[] arr, int startIndex, int centerIndex, int endIndex){
		// 定义一个临时数组
		int[] newArr = new int[endIndex-startIndex+1];
		// 定义左右两边的初始索引
		int i = startIndex;
		int j = centerIndex+1;
		// 定义临时数组的初始索引
		int k = 0;
		while(i<=centerIndex&&j<=endIndex){
			if(arr[i]<=arr[j]){
				// 将小的元素放到新的数组中
				newArr[k] = arr[i];
				i++;
			} else {
				newArr[k] = arr[j];
				j++;
			}
			k++;
		}
		// 处理剩余的元素
		while(i<=centerIndex){
			newArr[k] = arr[i];
			i++;
			k++;
		}
		while(j<=endIndex){
			newArr[k] = arr[j];
			j++;
			k++;
		}
		// 将临时数组取到原数组中
		for(int l = 0; l < newArr.length; l++){
			arr[l+startIndex] = newArr[l];
		}
	}
}

基数排序

原理

  1. 不需要进行比较。
  2. 根据元素位数只需要对关键字进行分配和收集即可。
  3. 最大位数是几位就循环几次。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ox1pJOhi-1653485805780)(imgclip.png "imgclip.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {10,80,30,5,4,8,20};
		// 定义一个二位数数组管理容器,以及统计数组
		int[] counts = new int[10];
		int [][] temp = new int [10][arr.length];
		// 获取循环位置
		int max = getMax(arr);
		int len = String.valueOf(max).length();
		for(int i = 0, n = 1; i < len; i++, n*=10){
			// 分配
			// 每个位置上的数据
			for(int j = 0; j < arr.length; j++){
				// 位数的值进行分配
				int ws = arr[j]/n%10;
				temp[ws][counts[ws]++] = arr[j];
			}
			// 收集
			int index = 0;
			// 在统计数组中获取统计数据
			for(int k = 0; k < counts.length; k++){
				if(counts[k]!=0){
					for(int l = 0; l < counts[k]; l++){
						arr[index] = temp[k][l];
						index++;
					}
					// 清空记录
					counts[k] = 0;
				}
			}
		}
		System.out.println(Arrays.toString(arr));  //=> [4, 5, 8, 10, 20, 30, 80]
	}
	public static int getMax(int[] arr){
		int max = arr[0];
		for(int i = 1; i < arr.length-1; i++){
			if(max<arr[i]){
				max = arr[i];
			}
		}
		return max;
	}
}

堆排序

原理

  1. 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序。
  2. 堆分为两种:大顶堆和小顶堆,两者的差别主要在于排序方式。
  3. 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXZLLATN-1653485818491)(imgclip.png "imgclip.png")]

大顶堆的存储结构为:{19,16,15,9,8,1}

小顶堆的存储结构为:{1,8,9,15,16,19}

大顶堆和小顶堆的存储结构未必是有序的,只要父节点大于他的左右孩子节点就是大顶堆了,父节点小于他的孩子左右孩子节点就是小顶堆。可以用以下的公式表示:

大顶堆:arr[i]>=arr[2i+1] && arr[i]>=arr[2i+2]
小顶堆:arr[i]<=arr[2i+1] && arr[i]<=arr[2i+2]

堆排序的基本思想和步骤

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

下面我们举例来说明堆排序的步骤。

给定序列 {15,8,1,19,16,9}

构造好的完全二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bxhmcJ0W-1653485818493)(imgclip_1.png "imgclip_1.png")]

根据大顶堆的原理,我们构造一个大顶堆,此时我们从最后一个非叶子节点开始,如下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DCNIKCf7-1653485818497)(imgclip_2.png "imgclip_2.png")]

大顶堆的存储结构为{19,16,9,8,15,1}

然后我们将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换,步骤如下。

第一步:将堆顶元素19和堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{16,15,9,8,1,19}。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RWs3pW5C-1653485818501)(imgclip_3.png "imgclip_3.png")]

第二步:将堆顶元素16和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{15,8,9,1,16,19}。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6uX2XoL-1653485818503)(imgclip_4.png "imgclip_4.png")]

第三步:将堆顶元素15和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{9,8,1,15,16,19}。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFSkXR3Z-1653485818504)(imgclip_5.png "imgclip_5.png")]

第四步:将堆顶元素9和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{8,1,9,15,16,19}。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8iz5vi5K-1653485818505)(imgclip_6.png "imgclip_6.png")]

第五步,将堆顶元素8和新的无序堆的堆底元素1交换,交换后整个堆为有序,存储结构为:{1,8,9,15,16,19}。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2L9ILEYN-1653485818507)(imgclip_7.png "imgclip_7.png")]

public class Main{
	public static void main(String[] args){
		int[] arr = {9,8,7,6,5,4,3,2,1};
		sort(arr);
		System.out.println(Arrays.toString(arr));
	}
	public static void sort(int[] arr){
		// 构建大顶堆,从第一个非叶子节点从下到上从左到右
		for(int i = arr.length/2-1; i >= 0; i--){
			// 调整结构
			maxHeap(arr, i, arr.length);
		}

		// 反复进行首尾替换
		for(int j = arr.length-1; j > 0; j--){
			swap(arr, 0, j);
			// 交换完成后继续进行顶堆调整
			maxHeap(arr,0,j);
		}
	}
	public static void maxHeap(int[] arr, int i, int j){
		// 保存元素
		int temp = arr[i];
		// 遍历其子元素
		for(int k= i*2+1; k < j; k = k*2+1){
			// 子元素间进行比较
			if(arr[k]<arr[k+1] && k+1<j){
				k++;
			}
			if(temp<arr[k]){
				arr[i] = arr[k];
				i = k;
			}else{
				break;
			}
		}
		// 进行交换
		arr[i] = temp;
	}
	public static void swap(int[] arr, int start, int end){
		int temp = arr[start];
		arr[start] = arr[end];
		arr[end] = temp;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值