文章目录
五、排序
3.1 冒泡排序
@Test
public void test1Bubble() {
/**
* 方式一:
* 两个循环的初始值都是0 循环次数都是数组长度
*/
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if(arr[j] > arr[i]){
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
System.out.println("快速排序:" + Arrays.toString(arr));
/**
* 方式二:
* 外层遍历最后一个元素不需要在遍历一次
* 内层遍历找每次从第i个未移动的进行比较
*
*/
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("冒泡排序:" + Arrays.toString(arr));
}
3.2 选择排序
3.2.1 思想
-
第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换
-
,…,
-
第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
@Test
public void test2Selection() {
/**
* 先从第i个找,并认定其最小,然后从后面比较有比他小的没,有就装min中
* 每次结束一层遍历,就和第i个元素交换
*/
for (int i = 0; i < arr.length - 1; i++) {
int min = arr[i];
int index = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < min) {
index = j;
min = arr[index];
}
}
int temp = arr[i];
arr[i] = min;
arr[index] = temp;
}
System.out.println("选择排序:" + Arrays.toString(arr));
}
3.3 插入排序
3.3.1 思想
插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
@Test
public void test3Insertion() {
//从第i个元素开始向前找,比前面小的就换到前面。
//判断交换即可
for (int i = 1; i < arr.length - 1; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
System.out.println("插入排序:" + Arrays.toString(arr));
}
3.4 希尔排序
3.4.1 思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
@Test
public void test4Shell() {
//
//规定一个跳跃间隔(i),并每次都/2,满足其大于0
for (int i = arr.length / 2; i > 0; i /= 2) {
//通过得知遍历的间隔,我们可以确定可以这样遍历的次数j
for (int j = i; j < arr.length; j++) {
//得知指向数值的一个指针,通过间隔就可以进行两个数的比较 k=j-i,是获得的最较低位的地方,需要加上一个间隔,就可以进行比较了
for (int k = j - i; k >= 0; k -= i) {
//然后比较两个值 满足条件进行互换
if (arr[k] > arr[k + i]) {
int temp = arr[k];
arr[k] = arr[k + i];
arr[k + i] = temp;
}
}
}
}
System.out.println("希尔排序:" + Arrays.toString(arr));
}
3.5 快速排序
3.5.1 思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
public class Quick {
public static void main(String[] args) {
int arr[] = {3, 9, -1, 10, 20};
quick(arr,0,arr.length -1);
System.out.println(Arrays.toString(arr));
}
/**
*
* @param arr 传入的数组
* @param left 从0开始
* @param right 到数组最后一位结束
*/
private static void quick(int[] arr,int left,int right){
int l = left;//记录左指针
int r = right;//记录右指针
int midValue = arr[(left + right)/2];//找到中间的索引的值
while (l < r){//满足左指针小于右指针循环
while (arr[l] < midValue){//当左边的小于中间的就让左指针++
l++;
}
while (arr[r] > midValue){//当右边的大于中间的就让右指针--
r--;
}
if(l>=r){//若本次左指针大于等于右指针了跳出循环
break;
}
//*****交换操作******
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//*****交换操作******
if(arr[l] == midValue){//当前值指向中间值左指针继续++ 以便于完成递归
l++;
}
if(arr[r] == midValue){//当前值指向中间值右指针继续-- 以便于完成递归
r--;
}
}
if(l==r){//如果两个指针同时指向同一个位置左+右-
l++;
r--;
}
//这时候相当于第二遍进行递归快速排序
//右指针满足大于左指针就递归
if(r > left){
quick(arr,left,r);
}
if(l < right){
quick(arr,l,right);
}
}
}
3.6 归并排序
4.6.1 思路
- 分治思想
- 具体过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kvhwaptw-1620135333851)(https://gitee.com/jerrygrj/img/raw/master/img/%E8%B7%AA(1)].png)
- 编写合的方法(int[] arr,int left,int mid,int right,int[] temp)
- 记录left right mid temp的指针
- 左边的数据与右边的数据比较填入temp
- 检查有没有剩下的元素,进行填充
- 然后将temp数组的内容填入到arr
- 编写分的方法(int[] arr,int left,int right,int[] temp)
- 声明mid值(left+right)/2
- 然后递归本方法
- 左半部分(arr,left,mid,temp)
- 右半部分(arr,mid+1,right,temp)
- 调用合的方法并传入参数
public 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 做中转的数组
*/
public 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数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到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
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
3.7 基数排序
3.7.1基数排序基本思想
-
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
-
这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤
3.7.2 步骤
- 首先找到arr中的最大数的位数,作为外层循环
int maxLength = (max+"").length;
- 创建一个二维数组(表示10个桶,和每个桶中的数字),再创建一个一维数组,表示每个桶中数字的个数。按个位、十位…的数字添加到对应的桶中
- 将每个桶中的数组再拿出来按顺序放到arr中
3.7.4 代码
/**
* 基数排序
*
* @param arr 传入要排序的数组
*/
public static void radixSort(int[] arr) {
//找数组中的最大值,并获取最大数的长度,用于外层循环
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//获得最大数的长度
int maxLength = (max + "").length();
//创建二维数组 [数字] [内容] ---数字对应的数据
int[][] bucket = new int[10][arr.length];
//用于基数每个数(桶)的个数
int[] countBucket = new int[10];
//最大位数作为循环条件抽取每一个放入对应的数(桶)中
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {//定义一个除数,用于每次得出个位、十位、百位...
for (int j = 0; j < arr.length; j++) {
int digitEnd = arr[j] / n % 10;//将拿到的这个数字存放起来
bucket[digitEnd][countBucket[digitEnd]] = arr[j];//把对应位的原数据放入bucket中
countBucket[digitEnd]++;//计数桶自增
}
int index = 0;//定义放回到原数组的指针
for (int j = 0; j < countBucket.length; j++) {//扫描计数桶
if (countBucket[j] != 0) {//桶里不为空就装入原数组
for (int k = 0; k < countBucket[j]; k++) {
arr[index++] = bucket[j][k];
}
}
//记得将桶清空,一遍下次装入
countBucket[j] = 0;
}
}
}
3.8 堆排序
3.8.1 堆排序基本介绍
-
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
-
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
-
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
-
大顶堆举例说明
- 特点:arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2] // i 对应第几个节点,i从0开始编号
- 小顶堆举例说明
- 特点:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2] // i 对应第几个节点,i从0开始编号
3.8.2 排序思想
-
将待排序序列构造成一个大顶堆
-
此时,整个序列的最大值就是堆顶的根节点。
-
将其与末尾元素进行交换,此时末尾就为最大值。
-
然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
3.8.3 代码
/**
* 传入待排序的数组
* @param arr
*/
private static void heapSort(int[] arr) {
int temp = 0;
//通过中间访问
将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for (int i = arr.length / 2-1; i >= 0; i--) {
sorth(arr,i,arr.length);
}
for (int i = arr.length - 1; i > 0 ; i--) {
temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
sorth(arr,0,i);
}
}
/**
* i:父节点索引 2*i+1就是左子节点
* @param arr
* @param i
* @param length
*/
private static void sorth(int[] arr, int i, int length) {
int temp = arr[i];//先取出当前元素的值,保存在临时变量
for (int j = 2*i+1; j <length; j= j*2+1) {
if(j+1<length&&arr[j]<arr[j+1]){//说明左子结点的值小于右子结点的值
j++;// j 指向右子结点
}
if(temp < arr[j]){//子节点大于父节点 ,把大的给了父节点。把子节点指针赋给父节点,循环比较
arr[i] = arr[j];
i = j;
}else {
break;
}
}
//将原来的父节点的值给了子节点
arr[i] = temp;
}
3.9 计数排序
3.9.1 算法步骤
- 找出最值
- 开辟存储空间,存储次数
- 累加次数,根据前一个
- 从后往前遍历,找到合适的位置
- 讲有序的数组存入新的数组中
3.9.2 代码
public class CountSort {
public static void main(String[] args) {
Integer[] arr= Integers.random(10,1,100);
System.out.println(Arrays.toString(arr));
int max = arr[0];
int min = arr[0];
for (int i = 0; i < arr.length; i++) {
if(max < arr[i]){
max = arr[i];
}
if(min > arr[i]){
min = arr[i];
}
}
int[] countInt = new int[max-min+1];
for (int i = 0; i < arr.length; i++) {
countInt[arr[i] - min]++;
}
for (int i = 1; i < countInt.length; i++) {
countInt[i] += countInt[i-1];
}
int[] newInt = new int[arr.length];
for (int i = arr.length-1; i >= 0 ; i--) {
newInt[--countInt[arr[i] - min]] = arr[i];
}
System.out.println(Arrays.toString(newInt));
}
}