排序

 冒泡排序

最简单的一种排序算法,效率也是最低下的排序算法。
多个数字通过冒泡排序时,会依次将数组中最大的那个数移动到数组的末尾。

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集合工具类

## 自然排序

## 定制排序

 

快速排序

原理:    快速排序,说白了就是给基准数据找其正确索引位置的过程.    如下图所示,假设最开始的基准数据为数组第一个元素23,则首先用一个临时变量去存储基准数据,即tmp=23;然后分别从数组的两端扫描数组,设两个指示标志:low指向起始位置,high指向末尾.

   

 

   首先从后半部分开始,如果扫描到的值大于基准数据就让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的正确位置
    }
}

 

 
posted @ 2022-09-07 13:58  岁月记忆  阅读(103)  评论(0)    收藏  举报