排序

排序

​ 排序是一个很重有的功能,平时也会经常用到排序。这篇文章会介绍三种常用的排序算法。还有其他的排序方法,比如堆排序,归并排序等等,这些排序会在后面的内容讲述。

​ 以下排序的对象是一个数组 a,有 n 个元素,需要从小到大进行排序。

选择排序

​ 选择排序就是 n 次选择, 每次从剩下的元素中选择一个最小的出来。

​ 即第 i 次选择就是从 i ~ n 这些位置中的元素里面找到一个最小的元素,然后和 i 这个位置进行交换。做完 n 次选择之后,数组就会变得有序。

​ 第1次选择找到第1小的元素放在了0这个位置

​ 第2次选择找到第2小的元素放在了1这个位置

​ 第i次选择找到第i小的元素放在了 i-1这个位置

​ 一共需要 n 次选择, 每次选择需要从剩下的元素中遍历,所以总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

public void selectSort(int[] a){
    int n = a.length;
    for (int i = 0; i < n; ++i){
        int mn = a[i], pos = i;  // 初始化最小的元素为 mn, 位置为 i。
        for (int j = i; j < n; ++j){  // 在剩下的元素中找一个最小的出来。
            if (a[j] < mn){    // 如果当前的元素比之前记录的小,那么就要更新。
                mn = a[j];    // 更新当前值为最小值。
                pos = j;       // 更新当前的位置。
            }
        }
        int tmp;  // 交换两个位置的元素。
        tmp = a[i]; a[i] = a[pos]; a[pos] = tmp;
    }
}

冒泡排序

冒泡排序是 n 次循环,每次循环找到一个最大的元素,然后放到数组的后面。

​ 第 1 次找到第1大的元素放在n-1的位置,

​ 第2次找到第2大的元素,放到n-2的位置,

​ 第i次找到第i大的元素放到n-i的位置。

​ 具体的找法就和选择排序不一样了。

​ 从前往后遍历,每次比较相邻的元素,如果大的元素在前,就交换两个元素的位置,否则不做修改,遍历结束,大的元素就跑到数组后面了。

一共是n次循环, 每次循环也要遍历整个数组,所以总的事件复杂度为 O ( n 2 ) O(n^2) O(n2)

public void bubbleSort(int[] a){
    int n = a.length,tmp;
    for (int i = 0; i < n; ++i){  // n 次循环
        for (int j = 1; j < n-i; ++j){  // 每次循环,遍历一下数组。
            if (a[j-1] > a[j]){  // 比较相邻的元素。 如果前面的元素大于后面的元素,那么就交换。
                tmp = a[j-1];
                a[j-1] = a[j];
                a[j] = tmp;
            }
        }
    }
}

第 2 重 for 循环 j 的结束位置 < n - i, 是因为 n-i ~ n-1 这些位置已经有序了,没有必要再遍历了。

快速排序

​ 快速排序应该是我们最常用的算法了,因为它的时间复杂度十分优秀,是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) ,要很多接口可以调用快速排序,但是我们还是要知道快速排序的原理,并且可以手写快排,在面试的过程中, 经常会有面试官让我们实现一个快速排序算法。

快速排序的大概原理:

​ 1、对区间 [l, r] 进行排序, 先找一个基准数 pivot = a[l],定义两个指针 i = lj = r

​ 2、先右指针j不断向左移动,找到第一个小于 pivot 的位置,然后左指针i不断向右移动,找到第一个大于 pivot 的位置。

​ 3、如果 i < j 则交换 i j 位置的元素,也就是把大的元素放在右边,小的元素放在左边。然后继续 步骤2

​ 4、如果 i==j 代表指针相遇了。由于先移动右指针,所以j 这个位置必然小于等于 pivot ,所以要把 j 这个位置的元素和 l 位置的元素交换一下,也就是把 pivot换到 i 这个位置上,此时 pivot 已经在数组中找到了自己的位置,以后也不会再变了。

​ 5、当前区间 [l,r] 划分成了两个小区间 [l, i-1], [j+1,r] ,左区间的所有值都是小于等于 pivot, 右区间的值都是大于等于 pivot

​ 6、对两个子区间分别进行步骤一

下图是快速排序的一个图解:

image-20201211101151963

public void quickSort(int l, int r, int[] a){ // 对区间 [l,r]进行排序
    if (l >= r) return;  // 区间只有一个数,直接退出。
    int i = l, j = r,pivot = a[l],tmp;  // 初始化
    while(i < j){ // 两个指针 i j 如果相遇就退出,否则继续执行。
        while((i < j) && (a[j] >= pivot)) j--; // 右指针找到第一个小于 pivot 的位置
        while((i < j) && (a[i] <= pivot)) i++; // 左指针找到第一个大于 pivot 的位置
        if (i < j){   // 交换左右指针位置元素的值
            tmp = a[i]; a[i] = a[j]; a[j] = tmp;
        }
    }
    a[l] = a[i];  //把 i 位置的值换到 l 位置
    a[i] = pivot; // pivot 的值换到 i 这个位置。
    quickSort(l,i-1,a);  // 处理左区间
    quickSort(i+1,r,a);  // 处理右区间
}

练习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值