打怪升级之小白的大数据之旅(七)
Java基础语法之数组的排序算法
目录
上次回顾
上一期我们讲完了Java的数组的第一部分,数组的基本概念,本章开始分享数据的一些基础算法,由浅入深,下一章,会提到几个常见的排序,好了,开始进入正题
数组排序算法
数组的反转
- 将数组内的元素进行倒置
- 反转方式一:借助新数组进行反转
示例代码://反转数组1: public class ReverseArry { public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9}; //(1)先创建一个新数组 int[] newArr = new int[arr.length]; //(2)复制元素 int len = arr.length; for(int i=0; i<newArr.length; i++){ newArr[i] = arr[len -1 - i]; } //(3)舍弃旧的,让arr指向新数组 arr = newArr;//这里把新数组的首地址赋值给了arr //(4)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } }
缺点: 需要重新定义一个数组,浪费额外内存空间,原数组需要垃圾回收
- 反转方式二:首位对应位置交换(数组对称位置的元素交换)
示例代码://反转数组2: public class ReverseArry { public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9}; //(1)计算要交换的次数: 次数 = arr.length/2 //(2)首尾对称位置交换 for(int i=0; i<arr.length/2; i++){//循环的次数就是交换的次数 int temp = arr[i]; arr[i] = arr[arr.length-1-i]; arr[arr.length-1-i] = temp; } //(3)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } }
- 反转方式三:左右对称位置交换,然后再首尾交换
示例代码://反转数组2: public class ReverseArry { public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9}; //左右对称位置交换 for(int left=0,right=arr.length-1; left<right; left++,right--){ //首 与 尾交换 int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; } //(3)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } } }
数组的扩容
- 当原数组长度不够时,就需要进行数组扩容
- 数组扩容只是创建一个新数组,并将原数组元素复制进新数组中
- 原数组被垃圾回收了
- 示例代码:
//数组扩容: public class ExpansionArry { public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9}; //如果要把arr数组扩容,增加1个位置 //(1)先创建一个新数组,它的长度 = 旧数组的长度+1,或者也可以扩大为原来数组长度的1.5倍,2倍等 int[] newArr = new int[arr.length + 1]; //(2)复制元素 //注意:i<arr.length 因位arr比newArr短,避免下标越界 for(int i=0; i<arr.length; i++){ newArr[i] = arr[i]; } //(3)把新元素添加到newArr的最后 newArr[newArr.length-1] = 10; //(4)如果下面继续使用arr,可以让arr指向新数组 arr = newArr; //(4)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } }
注意事项:
(1)至于新数组的长度定义多少合适,看实际情况,如果新增的元素个数确定,那么可以增加指定长度,如果新增元素个数不确定,那么可以扩容为原来的1.5倍、2倍等
(2)数组扩容太多会造成浪费,太少会导致频繁扩容,效率低下
数组元素的插入
- 在原数组的某个位置中[index]插入一个元素
- 示例代码:
数组插入还有两种特殊的情况!//数组插入: public class InsertArry { public static void main(String[] args){ //在索引index位置插入一个整数5 int index=1; int[] arr={1,2,3,4}; //创建新数组扩容 int[] newArr=new int[5]; //复制数组 for (int i = 0; i < arr.length; i++) { newArr[i]=arr[i]; } //向后移动插入位置之后的每个元素 for (int i = newArr.length - 1; i >= 0; i--) { if(i>index){ newArr[i]=newArr[i-1]; } } //插入新元素 newArr[index]=5; arr=newArr; //遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } } }
- 数组插入的特殊情况一:
- 原数组未满(暂时)
- 示例代码:
//数组插入1: public class InsertArry { public static void main(String[] args){ /* * 目前数组的长度是5,而数组的实际元素个数是3, * 如果此时需要在“张三”和“李四”之间插入一个“赵六”, * 即在[index=1]的位置插入“赵六”,需要怎么做呢? * */ String[] arr = new String[5]; arr[0]="张三"; arr[1]="李四"; arr[2]="王五"; //(1)移动2个元素,需要移动的起始元素下标是[1],它需要移动到[2],一共一共2个 System.arraycopy(arr,1,arr,2,2); //(2)插入新元素 arr[1]="赵六"; //(3)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } } }
- 数组插入的特殊情况二:
- 原数组已满(暂时)
- 示例代码:
//数组插入2: public class InsertArry { public static void main(String[] args){ /* * 目前数组的长度是3,而数组的实际元素个数是3, * 如果此时需要在“张三”和“李四”之间插入一个“赵六”, * 即在[index=1]的位置插入“赵六”,需要怎么做呢? * */ String[] arr = new String[3]; arr[0]="张三"; arr[1]="李四"; arr[2]="王五"; //(1)先扩容 String[] newArr = new String[4]; for(int i=0; i<arr.length; i++){ newArr[i] = arr[i]; } arr=newArr; //(2)移动2个元素,需要移动的起始元素下标是[1],它需要移动到[2],一共2个 System.arraycopy(arr,1,arr,2,2); //(3)插入新元素 arr[1]="赵六"; //(4)遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } } }
数组元素的删除
- 根据索引位置删除元素(逻辑删除)
- 示例代码1:
//数组删除2: public class DeleteArry { public static void main(String[] args){ //删除索引1位置的元素 int[] arr={1,2,3,4,5}; //把删除位置之后的每个元素向前移动 for (int i = 0; i < arr.length-1; i++) { if(i>=1){ arr[i]=arr[i+1]; } } //根据实际业务需求,把最后位置的元素修改为某个值 arr[arr.length-1]=0; //遍历显示 for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); } } }
- 示例代码2:
//数组删除2: public class DeleteArry { public static void main(String[] args){ /* * 现在需要删除“李四”,我们又不希望数组中间空着元素,该如何处理呢? * */ String[] arr = new String[3]; arr[0]="张三"; arr[1]="李四"; arr[2]="王五"; //(1)移动元素,需要移动元素的起始下标[2],该元素需要移动到[1],一共需要移动1个元素 System.arraycopy(arr,2,arr,1,1); //(2)因为数组元素整体往左移动,这里本质上是复制,原来最后一个元素需要置空 arr[2]=null; } }
数组的二分查找
- 二分查找,也称之为折半查找,前提必须是有序的数组
- 数组元素必须支持比较大小,并且数组中的元素已经按大小排好序
示例代码:class BinarySearchArray{ public static void main(String[] args){ int[] arr = {2,5,7,8,10,15,18,20,22,25,28};//数组是有序的 int value = 18; int index = -1; int left = 0; int right = arr.length - 1; int mid = (left + right)/2; while(left<=right){ //找到结束 if(value == arr[mid]){ index = mid; break; }//没找到 else if(value > arr[mid]){//往右继续查找 //移动左边界,使得mid往右移动 left = mid + 1; }else if(value < arr[mid]){//往左边继续查找 right = mid - 1; } mid = (left + right)/2; } if(index==-1){ System.out.println(value + "不存在"); }else{ System.out.println(value + "的下标是" + index); } } }
- 待查找数值在数组的右半部分
查找流程: - 待查找数值在数组的左半部分
查找流程:
数组的直接选择排序
- 直接获取最值的下标,再进行排序
示例代码1:// 直接排序 class DirectSearchArray { public static void main(String[] args) { int[] arr = {49, 38, 65, 97, 76, 13, 27, 49}; //比较轮数N-1 for (int j = 0; j < arr.length - 1; j++) { int min = j;//假如当前位置是最小的元素 //每轮比较次数N-j for (int i = j; i < arr.length - 1; i++) { if (arr[min] > arr[i + 1]) { //记录找到的最小元素下标 min = i + 1; } } if (min != j) { int temp = arr[j]; arr[j] = arr[min]; arr[min] = temp; } } //显示结果 for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]); } } }
示例代码2:// 直接排序 class DirectSearchArray { public static void main(String[] args) { int[] arr = {49,38,65,97,76,13,27,49}; for(int i=1; i<arr.length; i++){//外循环的次数 = 轮数 = 数组的长度-1 //(1)找出本轮未排序元素中的最值 /* 未排序元素: 第1轮:i=1,未排序,[0,7],本轮未排序元素第一个元素是[0] 第2轮:i=2,未排序,[1,7],本轮未排序元素第一个元素是[1] ... 第7轮:i=7,未排序,[6,7],本轮未排序元素第一个元素是[6] 每一轮未排序元素的起始下标:0,1,2,3,4,5,6,正好是i-1的 未排序的后面的元素依次: 第1轮:[1,7] j=1,2,3,4,5,6,7 第2轮:[2,4] j=2,3,4,5,6,7 。。。。 第7轮:[7] j=7 j的起点是i,终点都是7 */ int max = arr[i-1]; int index = i-1; for(int j=i; j<arr.length; j++){ if(arr[j] > max){ max = arr[j]; index = j; } } //(2)如果这个最值没有在它应该在的位置,就与这个位置的元素交换 /* 第1轮,最大值应该在[0] 第2轮,最大值应该在[1] .... 第7轮,最大值应该在[6] 正好是i-1的值 */ if(index != i-1){ //交换arr[i-1]与arr[index] int temp = arr[i-1]; arr[i-1] = arr[index]; arr[index] = temp; } } //显示结果 for(int i=0; i<arr.length; i++){ System.out.print(arr[i]); } } }
数组的折半插入排序
- 利用二分法进行插入排序
- 编程思路如下图:
示例代码:import java.util.Arrays; // 折半二分插入排序 class DirectSearchArray { public static void main(String[] args) { /* * 折半插入排序 * 例如:数组{12,2,6,1,5} * 第一次:在[0,1)之间找插入2的位置==>left = [0] ==> {2,12,6,1,5} * 第二次:在[0,2)之间找插入6的位置==>left = [1] ==> {2,6,12,1,5} * 第三次:在[0,3)之间找插入1的位置==>left = [0] ==>{1,2,6,12,5} * 第四次:在[0,4)之间找插入5的位置==>left = [2] ==>{1,2,5,6,12} * */ int[] arr = {12, 2, 6, 1, 5}; for (int i = 1; i < arr.length; i++) { //找到[0,i)之间插入arr[i]的位置 //使用二分查找法 int left = 0; int right = i - 1; while (left <= right) { int mid = (left + right) / 2; if (arr[i] <= arr[mid]) { right = mid - 1; } else { left = mid + 1; } } //在[0,i)插入arr[i] if (left < i) { int temp = arr[i]; System.arraycopy(arr, left, arr, left + 1, i - left); arr[left] = temp; } } for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]+", "); } } }
数组工具类
- Java提供了现成的工具类,方便实现数组的相关操作
- java.util.Arrays数组工具类,提供了很多静态方法来对数组进行操作,而且如下每一个方法都有各种重载形式,以下只列出int[]类型的,其他类型的数组类推:
- static int binarySearch(int[] a, int key) :要求数组有序,在数组中查找key是否存在,如果存在返回第一次找到的下标,不存在返回负数
- static int[] copyOf(int[] original, int newLength) :根据original原数组复制一个长度为newLength的新数组,并返回新数组
- static int[] copyOfRange(int[] original, int from, int to) :复制original原数组的[from,to)构成新数组,并返回新数组
- static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同
- static void fill(int[] a, int val) :用val填充整个a数组
- static void fill(int[] a, int fromIndex, int toIndex, int val):将a数组[fromIndex,toIndex)部分填充为val
- static void sort(int[] a) :将a数组按照从小到大进行排序
- static void sort(int[] a, int fromIndex, int toIndex) :将a数组的[fromIndex, toIndex)部分按照升序排列
- static String toString(int[] a) :把a数组的元素,拼接为一个字符串,形式为:[元素1,元素2,元素3。。。]
- 示例代码:
import java.util.Arrays; import java.util.Random; public class Test{ public static void main(String[] args){ int[] arr = new int[5]; // 打印数组,输出地址值 System.out.println(arr); // [I@2ac1fdc4 // 数组内容转为字符串 System.out.println("arr数组初始状态:"+ Arrays.toString(arr)); Arrays.fill(arr, 3); System.out.println("arr数组现在状态:"+ Arrays.toString(arr)); Random rand = new Random(); for (int i = 0; i < arr.length; i++) { arr[i] = rand.nextInt(100);//赋值为100以内的随机整数 } System.out.println("arr数组现在状态:"+ Arrays.toString(arr)); int[] arr2 = Arrays.copyOf(arr, 10); System.out.println("新数组:" + Arrays.toString(arr2)); System.out.println("两个数组的比较结果:" + Arrays.equals(arr, arr2)); Arrays.sort(arr); System.out.println("arr数组现在状态:"+ Arrays.toString(arr)); }
排序算法知识扩展
排序算法主要是为了加快代码的执行效率,节省我们的时间,这里为了方便小伙伴更加清晰地学习了解算法的原理,推荐一个网址,里面有相关排序算法的详细介绍:
直接输入全拼 http://www.rmboot.com/
注意:
如果地址栏输入下面两个网址进入,会进入别人的博客网站:
1. rmboot.com
2. https://www.rmboot.com/
总结
本章对数组的排序进行了分享,我们常见的冒泡,二分查找,插入,选择,归并,快排等等,这些算法,首先是理解,借助我推荐的那个网址里的动画会更好理解一些,如果有可能,尽量多敲几遍代码,能做到手撸出这些算法,那就很可以了~下一章,二维数组,依旧是重在理解~