一、数组排序
1、冒泡排序
对数组进行排序是程序中非常基本的需求。常用的排序算法有冒泡排序、插入排序和快速排序。二冒泡排序是对于初学者相对好理解以及重要的排序算法。
实现步骤(演示升序排序)
- 比较相邻的元素,如果第一个比第二个大则交换位置。
- 对每一对相邻元素作同样的工作,从一开始第一对到最后一对。这个步骤完成后,末尾的元素会是最大的数。
- 针对所有的元素重复以上步骤,除最后一个元素。
- 持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
冒泡排序的特点是,每一轮循环后,最大的一个数就被交换到末尾,因此,下一轮循环就可以”排除“最后的数,每一轮循环都比上一轮循环的结束位置靠前一位。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] numbers = { 3, 6, 2, 4, 8, 12, 7, 9 };
System.out.println(Arrays.toString(numbers));
int count=0;
//比较N-1轮
for (int i = 0, n = numbers.length; i < n - 1; i++) {
count++;//计数器
//是否已经处于"有序"状态
//true表示有序
//false表示无序
//每次一轮后第二轮开始将isSorted归为true
//若为进入if则表示已经有序,退出循环
boolean isSorted=true;
for (int j = 0; j < n - 1 - i; j++) {
if (numbers[j] > numbers[j + 1]) {
numbers[j] = numbers[j] ^ numbers[j + 1];
numbers[j + 1] = numbers[j] ^ numbers[j + 1];
numbers[j] = numbers[j] ^ numbers[j + 1];
isSorted=false;
}
}
if(isSorted) {
break;
}
}
System.out.println("总共比较了"+count+"轮");//总共比较了3轮
System.out.println(Arrays.toString(numbers));//[2, 3, 4, 6, 7, 8, 9, 12]
}
}
2、使用Arrays工具类排序
实际上,Java的标准库已经内置了排序功能,我们只需要调用Arrays.sort()就可以排序:
public class Main{
public static void main(String[] args){
int[] ns ={28,12,89,73,65,18,96,50,8,36};
//排序前
System.out.println("排序前:"+Arrays.toString(ns));
//排序
Arrays.sort(ns);
//排序后
System.out.println("排序后:"+Arrays.toString(ns));
}
}
小结
- 常用的排序算法有冒泡排序、插入排序和快速排序等
- 冒泡排序使用两次for循环实现排序
- 交换两个变量的值需要借助一个临时变量
- 可以直接使用Java标准库提供的Arrays.sort()进行排序
- 对数组排序会直接修改数组本身
二、无序数组查找
1、无序数组查找元素
在一个无序数组中,如果进行指定元素的查找,可以通过循环遍历或Arrays工具类两种方法进行查找。
遍历查找
遍历的方式进行查找:可以通过对该无序数组进行遍历,将数组的每个元素与指定元素进行比较,从而确定该数组中是否存在指定元素。
整型数组
int[] array={28,12,89,73,65,18,96,50,8,36};
int target=36;//目标元素
int index=-1;//目标元素下标,默认为-1,代表不存在
//遍历查找
for(int i=0;i<array.length;i++){
if(array[i]==target){
index=i;
break;
}
}
System.out.printf("目标值%d在数组中的下标为:%d",target,index);
字符串数组
String[] singerArray = {"李荣浩","盘尼西林","王菲","王贰浪","鹿先森乐队","孙燕姿","G.E.M.邓紫棋","方大同","品冠儿子"};
Scanner input = new Scanner(System.in);
String target = "方大同";
int index = -1;
//遍历查找
for(int i = 0; i < singerArray.length; i++){
//字符串等值判断使用equals()方法
if(target.equals(singerArray[i])){
index = i;
break;
}
}
双指针遍历查找
双指针遍历的方式进行查找:通过两个下标,分别从数组头部和尾部,同时对该无序数组进行遍历,将数组中的每个元素与指定元素进行比较,从而确定该数组中是否存在指定元素。
整型数组
int[] array={28,12,89,73,65,18,96,50,8,36};
int target=36;//目标元素
int index=-1;//目标元素下标,默认为-1,代表不存在
//双指针查找
for(int i = 0, k = array.length - 1; i <= k; i++, k--){
if(array[i] == target){
index = i;
break;
}
if(array[k] == target){
index = k;
break;
}
}
System.out.printf("目标值%d在数组中的下标为:%d",target,index);
字符串数组
String[] singerArray = {"李荣浩","盘尼西林","王菲","王贰浪","鹿先森乐队","孙燕姿","G.E.M.邓紫棋","方大同","品冠儿子"};
Scanner input = new Scanner(System.in);
String target = "方大同";
int index = -1;
//双指针查找
for(int i = 0, k = singerArray.length - 1; i <= k; i++, k--){
if(singerArray[i] == target){
index = i;
break;
}
if(singerArray[k] == target){
index = k;
break;
}
}
2、有序数组查找元素
Arrays工具类binarySearch()方法
除了遍历查找,还可以通过调用Arrays.binarySearch()方法,进行查找:由于该方法是基于二分查找实现,数组必须处于有序状态。所以,需要先对数组进行排序,然后再通过Arrays.binarySearch()进行查找。
public class Main{
public static void main(String[] args){
int[] array = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
int target = 36;
//先排序
Arrays.sort(array);
//再查找
int index = Arrays.binarySearch(array,target);
System.out.printf("目标值%d在数组中的下标为:%d",target,index);
}
}
该方发只能对有序数组进行查找,所以必须先排序,再查找指定元素。
二分查找元素
二分查找的作用
在一个有序数组中,如果需要指定元素进行查找,可以采用二分查找算法,提高查找元素的效率。
二分查找的过程
- 判断搜索数组的“中位元素”与要查找的“目标元素”是否相等。
- 如果相等,代表查找成功,退出算法;
- 如果不相等,继续比较”中位元素“与要查找的”目标元素“的大小关系;
- 如果“中位元素”大于“目标元素”,当前数组的前半部分作为新的搜素数组,因为后半部分的所有元素都大于“目标元素”,他们全都被排除了。
- 如果“中位元素”小于“目标元素”,当前数组的后半部分作为新的搜素数组,因为前半部分的所有元素都小于“目标元素”,他们都被排除了
- 在新的搜素数组上,重新开始第一步的工作
二分查找的效率高效,是因为它在匹配不成功的时候,每次都能排除剩余元素中的一般的元素。因此可能包含目标元素的有效范围就收缩的很快,而不像遍历查找那样每次仅能排除一个元素。
public class Main{
public static void main(String[] args){
int[] array = { 1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59};
int target = 37;//目标元素
int index = -1;//目标元素的下标,默认为-1,表示不存在
//low和high分别表示搜索数组的开始下标与结束下标
//默认搜索数组是整个数组
int low = 0, high = array.length - 1;
while(low <= high){
//计算“中位元素”的下标
int mid = (high + low) / 2;
if(array[mid] < target){
//如果“中位元素”等于“目标元素”,查找成功,退出
index = mid;
break;
}else if(array[mid] < target){
//如果“中位元素”小于“目标元素”,当前数组的后半部分作为新的搜索数组
low = mid + 1;
}else if(array[mid] > target){
//如果“中位元素”大于“目标元素”,当前数组的前半部分作为新的搜索数组
high = mid - 1;
}
}
System.out.printf("目标值%d在数组中的下标为:%d",target,index);
}
}
二分查找,除了在有序数组中使用之外,也应用于BST二叉搜索树中进行指定节点值的查找。
小结
- 查找数组中的指定元素分为两种情况:无序数组查找和有序数组查找
- 无序数组可以通过遍历数组或Arrays工具类两种方式查找指定元素
- 有序数组可以通过二分查找算法查找指定元素
三、数组乱序
算法简述
假设题目:有一个大小为100的数组,里面的元素是从1到100,随机从数组中选择50个不重复数(也可以理解为产生50个不重复的1-100的数字)。
实现过程中,不仅仅是用Math.random()*100重复50次,就可以拿到50个1到100的随机数,因为在这个过程中,产生重复数字的概率是非常大的。例如,第一次遇到5,第二次如果再一次遇到5,不符合“不重复”的要求,所以需要重新生成随机数。所以,要选出50个不重复数的话,随机次数远远大于50,因为越到后面随机带的数与前面选出的数重复的概率越大。这个时候,就需要用到数组乱序算法。
数组乱序,也被称为数组洗牌,实际算法中有一个非常著名的洗牌算法Fisher-Yates算法,是由Ronald A.Fisher和Frank Yates于1938年发明的,后来被Kunth在自己的著作《The Art of Computer Programming》中介绍,很多人直接成Knuth洗牌算法。
实现步骤
- 假设有一组等待乱序的数组P
- 从P中随机选取一个未乱序的元素
- 将该元素与数组P中最后一个未乱序的元素交换
- 重复 2 - 3 的步骤,直到数组P中元素全部完成乱序
int[] p = { 1, 2, 3, 4, 5, 6, 7};
for(int i = p.length - 1; i >0; i--){
int index = (int) (Math.random() * i);
int temp = p[index];
p[index] = p[i];
p[i] = temp;
}
System.out.println(Arrays.toString(p));
四、数组旋转
数组旋转可以实现类似于音乐播放器的播放列表的功能,例如:将数组(音乐列表)的第一位通过旋转放置在最后一位,第二位放置在第一位以此类推。
向左旋转
例:向左旋转两位
[ 1, 2, 3, 4, 5, 6, 7, 8]
↓
[ 3, 4, 5, 6, 7,8,1, 2]
原始:[ 1, 2, 3, 4, 5, 6, 7, 8]
第一次:[ 2, 3, 4, 5, 6, 7, 8, 1]
第二次:[ 3, 4, 5, 6, 7, 8, 1, 2]
public void left(int w, int[] array){
for(int i = 1; i <= w; i++){
for(int k = 0; k < array.length - 1; k++){
array[k] = array[k] ^ array[k+1];
array[k+1] = array[k] ^ array[k+1];
array[k] = array[k] ^ array[k+1];
}
}
}
向右旋转
例:向右旋转两位
[ 1, 2, 3, 4, 5, 6, 7, 8]
↓
[ 7, 8, 1, 2, 3, 4, 5, 6]
原始:[ 1, 2, 3, 4, 5, 6, 7, 8]
第一次:[ 8, 1, 2, 3, 4, 5, 6, 7]
第二次:[ 7, 8, 1, 2, 3, 4, 5, 6]
public void rigth(int w, int[] array){
for(int i = 1; i <= w; i++){
for(int k = array.length - 1; k > 0; k--){
array[k] = array[k] ^ array[k-1];
array[k-1] = array[k] ^ array[k-1];
array[k] = array[k] ^ array[k-1];
}
}
}