排序
冒泡排序
最简单的一种排序算法,效率也是最低下的排序算法。
多个数字通过冒泡排序时,会依次将数组中最大的那个数移动到数组的末尾。
int[] arr = {2,3,1,5,4}; //第一次:2,3,1,4,5 最大数为5,下一轮在2,3,1,4中找出最大数 //第二次:2,3,1,4,5 最大数为4,下一轮在2,3,1中找最大数 //第三次:2,1,3,4,5 最大数为3,下一轮在2,1中找最大数 //第四次:1,2,3,4,5
N个数字来排队,两两相比小靠前,外层循环N-1,内层循环N-1-i
int[] arr = {2,3,1,5,4}; //外层循环每循环每一次,就能够从剩下的序列中找出最大的那一个数,并移动到数组的末尾 for(int i = 0;i < arr.length - 1;i++){ //内层循环不断在剩下的无序列中比较,并找出最大的那个数 for(int j = 0;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; } } }
在多个数列中通过冒泡排序,最坏的情况下相邻的两个数都需要进行比较然后交换位置。这导致了冒泡排序的效率非常低下。
冒泡排序是一种稳定的排序算法。
选择排序
冒泡排序效率低下的原因在于频繁的发生相邻元素的位置交换。
选择排序每一轮在比较时,在无序的序列中找出最小的那个数的下标,这一轮比较完成后,只发生一次位置的交换。
选择排序元素交换的次数会明显的低于冒泡排序,因此效率比冒泡排序略高。
选择排序每次在无序的序列中找出最小值。
int[] arr = {25,3,18,5,14}; //first: {3,25,18,5,14} 3是有序的 //second: {3,5,25,18,14} 3,5有序的 //third: {3,5,14,18,25} 3,5,14有序 //fourth: {3,5,14,18,25} 排序完成 for(int i = 0;i < arr.length - 1;i++){ //假设当前i下标的元素是这个序列中最小的值 int min = i; //利用内层循环验证i是否是最小的,一旦发现有比i下标的值还要小的,则替换min的值 for(int j = i + 1;j < arr.length;j++){ //比较当前j下标的值是否比下标min的值还要小 if(arr[j] < arr[min]) min = j; } //判断最小值的下标是否发生变化 if(min != i){ //将min的值和i的值进行位置交换 int temp = arr[min]; arr[min] = arr[i]; arr[i] = temp; } }
插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 [1] 。插入排序是一种最简单的[排序](https://baike.baidu.com/item/排序/1066239)方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动
int[] arr = {25,3,18,5,14}; for(int i = 1;i < arr.length;i++){ int j = i; //控制从右向左比较的下标 int temp = arr[j]; //摸到的牌 //从右向左比较 for(;j > 0 && arr[j - 1] > temp;j--){ //一旦左侧下标为j的数比摸到的牌temp大,则下标为j的数 //向右移动一位,目的是给temp滕位置 arr[j] = arr[j-1]; } arr[j] = temp; }
希尔排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
希尔排序目的为了加快速度改进了插入排序,交换不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
在此我们选择增量 gap=length/2,缩小增量以 gap = gap/2 的方式,用序列 {n/2,(n/2)/2...1} 来表示。
如图示例:
(1)初始增量第一趟 gap = length/2 = 4
(2)第二趟,增量缩小为 2
(3)第三趟,增量缩小为 1,得到最终排序结果
//增量gap,并逐步缩小增量 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // gap /=2 缩小增量 //从第gap个元素,逐个对其所在组进行直接插入排序操作 for (int i = gap; i < arr.length; i++) { //记录下当前增量下标 int j = i; //获取增量下对应的牌 int pork = arr[j]; //假设: gap和0下标比较,如果0下标的值比增量gap下标的值小,则交换 if (arr[j] < arr[j - gap]) { while (j - gap >= 0 && pork < arr[j - gap]) { //移动法,让下标为0和下标为gap处的数交换位置 arr[j] = arr[j - gap]; j -= gap; } arr[j] = pork; } } }
Arrays数组工具类
Arrays类提供了很多个静态方法(类方法,直接通过类名点出来的方法)。利用IDEA查看Arrays提供的方法以及源码。
java中,数组工具类`java.util.Arrays`提供了默认的排序算法。
在代码编写窗口,通过组合按键`ctrl + N`打开对话框
常用方法:
| 方法名 | 描述 |
| -------------- | ------------------------------------------------------------ |
| sort() | 对给点参数的数组进行排序 |
| parallelSort() | 并行排序 |
| binarySearch() | 通过二分法在一个有序的序列中查找指定的值 |
| equals() | 比较两个数组以及当中的值是否相同 |
| fill() | 将数中的每一个元素用给定的值填充 |
| copyOf() | 将原始数组中的值复制到一个给定长度的新的数组中,返回新数组 |
| toString() | 将数组转换为字符串,方便调试 |
| stream() | 将数组转换为Stream,JDK8中的新技术,利用StreamAPI可以提高代码的编写效率以及使用lambda表达式 |
int[] arr = {...}; Arrays.sort(arr); //利用系统的排序算法对arr数组中的元素排序
## Collections集合工具类
## 自然排序
## 定制排序
原理: 快速排序,说白了就是给基准数据找其正确索引位置的过程.
首先从后半部分开始,如果扫描到的值大于基准数据就让high减1,如果发现有元素比该基准数据的值小(如上图中18<=tmp),就将high位置的值赋值给low位置 ,结果如下:
然后开始从前往后扫描,如果扫描到的值小于基准数据就让low加1,如果发现有元素大于基准数据的值(如上图46=>tmp),就再将low位置的值赋值给high位置的值,指针移动并且数据交换后的结果如下:
然后再开始从后向前扫描,原理同上,发现上图11<=tmp,则将high位置的值赋值给low位置的值,结果如下:
然后再开始从前往后遍历,直到low=high结束循环,此时low或high的下标就是基准数据23在该数组中的正确索引位置.如下图所示.
这样一遍走下来,可以很清楚的知道,其实快速排序的本质就是把基准数大的都放在基准数的右边,把比基准数小的放在基准数的左边,这样就找到了该数据在数组中的正确位置. 以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。
一些小结论 从上面的过程中可以看到:
①先从队尾开始向前扫描且当low < high时,如果a[high] > tmp,则high–1,但如果a[high] < tmp,则将high的值赋值给low,即arr[low] = a[high],同时要转换数组扫描的方式,即需要从队首开始向队尾进行扫描了 ②同理,当从队首开始向队尾进行扫描时,如果a[low] < tmp,则low++,但如果a[low] > tmp了,则就需要将low位置的值赋值给high位置,即arr[high] = arr[low],同时将数组扫描方式换为由队尾向队首进行扫描. ③不断重复①和②,知道low>=high时(其实是low=high),low或high的位置就是该基准数据在数组中的正确索引位置.
public class QuickSort { public static void main(String[] args) { int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 }; quickSort(arr, 0, arr.length - 1); System.out.println("排序后:"); for (int i : arr) { System.out.println(i); } } private static void quickSort(int[] arr, int low, int high) { if (low < high) { // 找寻基准数据的正确索引 int index = getIndex(arr, low, high); // 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序 //quickSort(arr, 0, index - 1); 之前的版本,这种姿势有很大的性能问题,谢谢大家的建议 quickSort(arr, low, index - 1); quickSort(arr, index + 1, high); } } private static int getIndex(int[] arr, int low, int high) { // 基准数据 int tmp = arr[low]; while (low < high) { // 当队尾的元素大于等于基准数据时,向前挪动high指针 while (low < high && arr[high] >= tmp) { high--; } // 如果队尾元素小于tmp了,需要将其赋值给low arr[low] = arr[high]; // 当队首元素小于等于tmp时,向前挪动low指针 while (low < high && arr[low] <= tmp) { low++; } // 当队首元素大于tmp时,需要将其赋值给high arr[high] = arr[low]; } // 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置 // 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low] arr[low] = tmp; return low; // 返回tmp的正确位置 } }