从零开始学Java-算法排序

98 阅读11分钟

目录

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 递归算法
  • 快速排序

冒泡排序

  • 核心:
    • 冒泡排序:相邻的数据两两比较,大的放在右边,小的放在左边
    • 第一轮循环结束后,最大的数据已经确定了,在数组最右边
    • 第二轮循环只要在剩余的元素找最大值,以此类推

实现步骤

假设,我们现在有一个待排序的数组,其数组元素值依次为[5,8,6,3,9,,2,1,7]。如果我们采用冒泡排序算法,按从小到大的规则对其排序,其详细过程会如下所示:

(1). 将5和8进行比较,因为满足左小右大的规则,不需要交换,保持元素位次不变;

(2). 将8和6进行比较,因不满足左小右大的规则,则需要交换。将8和6位置互换,互换位置后,元素6在下标1这个位置上,元素8在下标2这个位置上;

(3). 接着将8和3进行比较,不满足左小右大规则,需要交换。将8和3位置互换,互换位置后,元素3在下标2的位置上,元素8在下标3的位置上;

(4). 继续将8和9进行比较,满足左小右大规则,不需要交换,保持元素位次不变;

(5). 将9和2进行比较,不满足左小右大的规则,需要交换。将9和2位置互换,互换位置后,元素2在下标4的位置上,元素9在下标5的位置上;

(6). 将9和1进行比较,不满足左小右大的规则,需要交换。将9和1位置互换,互换位置后,元素1在下标5的位置上,元素9在下标6的位置上。

(7). 继续将9和7进行比较,不满足左小右大的规则,需要交换。互换位置后,元素7在下标6的位置上,元素9在下标7的位置上。

(8).后面的以此类推就可以了。

下面我们来实操一下吧:

代码演示

将下面数组进行冒泡排序:int[] arr = {5,8,6,3,9,2,1,7};

// 1.定义数组
int[] arr = {5,8,6,3,9,2,1,7};
// 2.利用冒泡排序将数组中的数据进行排序
// 外循环:表示我要执行多少次,如果有n个数据,那么执行n-1次
for (int i = 0; i < arr.length - 1; i++) {
    // 内循环:每一轮中我如何比较数据并找到当前的最大值
    // -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;
        }
    }
}
// 3.遍历数组
for (int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + " ");
}

image.png

是不是就按照从小到大进行排序了呀!

选择排序

  • 核心:
    • 选择排序:从0索引开始,拿着每一个索引的元素跟后面的元素依次比较,小的放在前面,大的放在后面,以此类推。
    • 第一轮循环结束后,最小的数据已经确定了
    • 第二轮循环从1索引开始以此类推

实现步骤

如我们现在有一个待排序的数组[5,8,6,3,9,2,1,7],若采用选择排序算法进行排序,其实现步骤如下:

(1). 初始化待排序数组[5,8,6,3,9,2,1,7];

(2). 从待排序数组中,选出最小值1,和第一个元素5进行交换,即将最小的元素放在下标0的位置上;

(3). 在剩下的无序区间的元素中,选择最小的元素2,并将最小的元素2与无序区间的第一个元素8进行交换。交换后,有序区间的元素变为2个,分别是1和2,剩余的为无序区间。

(4). 依次类推,将所有的元素通过不断选择的方式,按有序的方式放到有序区间,最终把整个数组全部排好顺序。

下面我们来实操一下吧:

代码演示

将下面数组进行选择排序:int[] arr = {5,8,6,3,9,2,1,7};

// 1.定义数组
int[] arr = {5,8,6,3,9,2,1,7};
// 2.利用选择排序将数组中的数据进行排序
// 外循环:表示我要执行多少次,如果有n个数据,那么执行n-1次
// i 表示这一轮中我拿着哪个索引上的数据跟后面的数据进行比较并交换
for (int i = 0; i < arr.length - 1; i++) {
    // 内循环:每一轮我要干什么
    // 拿着i跟i后面的数据进行比较交换
    for (int j = i + 1; j < arr.length; j++) {
        if (arr[i] > arr[j]) {
            //临时变量 用于交换
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
}
// 3.遍历数组
for (int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + " ");
}

image.png

怎么样,是不是和冒泡排序是类似的呀!

插入排序

  • 核心:
    • 插入排序:将0索引的元素到N索引的元素看作是有序的,把N+1索引的元素到最后一个当作无序的,遍历无序数据,将遍历到的元素插入到有序的适当位置,如果遇到同样的就插入到后面。

实现步骤

(1). 第1步:从数列的第2个元素开始抽取元素;

(2). 第2步:把它与左边的第一个元素进行比较,如果左边的第一个元素比它大,则继续与左边第二个元素比较下去,直到遇到小于等于它的元素,然后插到这个元素的右边。

(3). 第3步:继续选取第3、4....n个元素,重复步骤2,并选择适当的位置插入。

PixPin_2024-12-25_20-46-09.gif

下面我们来实操一下吧:

代码演示

将下面数组进行插入排序:int[] arr = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};

// 1.定义数组
int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
// 2.找到无序的数据是从哪个索引开始的。
int startIndex = -1;
for (int i = 0; i < arr.length; i++) {
    if (arr[i] > arr[i + 1]) {
        startIndex = i + 1;
        break;
    }
}
// 3.遍历数组交换
for (int i = startIndex; i < arr.length; i++) {
    // 记录当前要插入数据的索引
    int j = i;
    while (j > 0 && arr[j] < arr[j - 1]) {
        // 交换位置
        int temp = arr[j];
        arr[j] = arr[j - 1];
        arr[j - 1] = temp;
        j--;
    }
}
// 4.遍历数组
for (int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + " ");
}

image.png

是不是成功按小到大进行排序了呀,这就是插入排序,这部分也需要多加练习理解一下while里面的循环是什么意思!

我们接着来学习快速排序吧,在学习快速排序前我们先来学习一下递归算法,方便理解:

递归算法

  • 递归算法:递归指的是方法中调用方法本身的现象

  • 注意点:递归一定要有出口,否则就会出现内存溢出

  • 作用:把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

  • 书写递归的两个核心:

    • 找出口,什么时候不在调用方法
    • 找规则:如何把大问题变成规模较小的问题

我们下面来看一个案例吧:

  • 需求:利用递归求1-100之间的和
public static void main(String[] args) {
    // 需求:利用递归求1-100之间的和
    System.out.println(getSum(100));    // 5050
}

public static int getSum(int number){
    if(number == 1){
        return 1;
    }
    // 如果number不是1
    return number + getSum(number -1);
}

我们来运行看一下

image.png

怎么样,是不是很简单呢?我们再来看一个:

  • 需求:利用递归5的阶乘
   public static void main(String[] args) {
       // 需求:用递归求5的阶乘,并把结果在控制台输出
       System.out.println(getJC(5));
   }
   
   // 递归求5的阶乘
   public static int getJC(int number) {
       if (number == 1) {
           return 1;
       }
       // 如果number不是1
       return number * getJC(number - 1);
   }

image.png

怎么样,是不是都是一样的呀!

关于递归需要理解的是:

  1. 找出口,什么时候不在调用方法
  2. 找规则:如何把大问题变成规模较小的问题

好啦,到这里递归算法就学习完毕啦,我们继续来学习快速排序吧:

快速排序

  • 核心:
    • 快速排序:第一轮:把0索引的数字作为基准数,确定基准数在数组中正确的位置。比基准数小的全部在左边,比基准数大的全部在右边

实现步骤

如我们现在有一个待排序的数组[5,8,6,3,9,2,1,7],若采用选择排序算法进行排序,其实现步骤如下:

(1). 基准元素5的前半部分[3, 1, 2],以3为基准元素,经过排序,结果为[2, 1, 3]。本轮下来,本轮的基准元素3的位置就是其最终位置。

(2). 上轮基准元素3左侧的队列[2, 1],以2为基准元素排序,排序结果为[1, 2]。本轮下来,本轮的基准元素2的位置就是其最终位置。

(3). 上轮基准元素2左侧只剩下元素1,1就是自己的基准元素。这样元素1的最终位置就确定了。

(4). 基准元素5的后半部分[9, 6, 8, 7],以9为基准元素进行排序,结果为:[7, 6, 8, 9],本轮下来,本轮的基准元素9的位置就是其最终位置。

(5). 上轮基准元素9左侧的队列[7, 6, 8],以7为基准元素进行排序,结果为[6, 7, 8]。本轮下来,本轮的基准元素7的位置就是其最终位置。

(6). 上轮基准元素7左侧只剩下6,6就是自己的基准元素。这样元素6的最终位置就确定了。

(7). 基准元素7右侧只剩下8,8就是自己的基准元素。这样元素8的最终位置就确定了。

(8). 此时基准元素5、3、2、1、9、7、6、8都找到其正确的位置,则排序结束。

下面我们来实操一下吧:

代码演示

将下面数组进行快速排序:int[] arr = {5,8,6,3,9,2,1,7};

public static void main(String[] args) {
    // 1.定义数组
    int[] arr = {5, 8, 6, 3, 9, 2, 1, 7};
    quickSort(arr,0,arr.length-1);
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
}
// 2.定义一个方法
/*
 * 参数一:要排序的数组
 * 参数二:要排序数组的起始索引
 * 参数三:要排序数组的结束索引
 * */
public static void quickSort(int[] arr, int i, int j) {
    // 定义两个变量记录要查找的范围
    int start = i;
    int end = j;
    // 递归的结束出口
    if(start > end){
        return;
    }
    // 定义一个变量记录基准数
    int baseNumber = arr[i];
    // 利用循环找到要交换的数字
    while (start != end) {
        // 利用end从后往前开始找,找比基准数小的数字
        while (true) {
            if (end <= start || arr[end] < baseNumber) {
                break;
            }
            end--;
        }
        // 利用start从前往后找,找比基准数大的数字
        while (true) {
            if (end <= start || arr[start] > baseNumber) {
                break;
            }
            start++;
        }
        // 把end和start指向的元素进行交换
        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
    }
    // 当start和end指向同一个元素的时候,那么上面的循环就结束,表示已经找到了基准数在数组中应存入的位置
    // 基准数归位
    int temp = arr[i];
    arr[i] = arr[start];
    arr[start] = temp;

    // 确定6左边的范围,重复刚刚所作的事情
    quickSort(arr,i,start-1);
    // 确定6右边的范围,重复刚刚所作的事情
    quickSort(arr,start+1,j);
}

image.png

是不是也按照小到大进行排序了呀,其实快速排序的效率是非常非常快的,我们可以做个测试:

定义一个新的数组存放100000个随机数,看看排序完用的时间是多少:

int[] arr = new int[100000];
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
    arr[i] = r.nextInt();
}
long start = System.currentTimeMillis();
quickSort(arr,0,arr.length-1);
long end = System.currentTimeMillis();
System.out.println(end-start);

我们来运行看一下:

image.png

100000个数字进行排序,只花了27毫秒,你说快不快!

下面我们来做个总结吧:

  • 冒泡排序:
    • 相邻的元素两两比较,小的放在左边,大的放在右边。
  • 选择排序:
    • 从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较,小的放左边,大的放右边,以此类推。
  • 插入排序:
    • 将数组分为有序和无序两组,遍历无序数据,将元素插入有序序列中即可。
  • 快速排序:
    • 将排序范围中的第一个数字作为基准数,再定义两个变量start,end。
    • start从前往后找比基准数大的,end从后往前找比基准数小的
    • 找到之后交换start和end指向的元素,并循环这一过程,直到start和end处于同一个位置,该位置是基准数在数组中应存入的位置,再让基准数归位。
    • 归位后的效果,基准数左边的要比基准数小,基准数右边的要比基准数大

好啦,到这里基本的四种排序我们就学习完毕啦,这四种排序都需要重点掌握的,所以需要多加练习哟,有什么不懂的可以在评论区互相探讨,我们下期不见不散!!!

==最后非常感谢您的阅读,也希望能得到您的反馈  ==