因为还在学习的阶段,其实我对于八个排序算法中的堆排序和归并排序还是挺懵的.....
1、冒泡排序
冒泡排序是一个简单的排序算法,核心思想是两两比较,每次循环将最大(最小)的数移动到数组末端,然后进行第二次循环....直到数组有序。算法的时间复杂度为〇(n²),效率较低。 代码:
public static void bubbleSort(int[] arr) {
int n;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[ j ] > arr[ j + 1 ]) {
n = arr[ j ];
arr[ j ] = arr[ j + 1 ];
arr[ j + 1 ] = n;
}
}
}
}
冒泡排序的效率很低,所以我们对他进行改进,试想一下,如果一个数组是这样的 [1,0,2,3,4,5],(这个数组确实太极端了,但是我们只是想有一个可以对算法改进的例子而已) 对它进行排序,其实在第一次交换后就已经有序了,但还是需要循环n²次,所以我们对它改进:
public static void bubbleSort(int[] arr) {
int n;
boolean flag;
for (int i = 0; i < arr.length - 1; i++) {
flag = true;
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[ j ] > arr[ j + 1 ]) {
n = arr[ j ];
arr[ j ] = arr[ j + 1 ];
arr[ j + 1 ] = n;
flag = false;
}
}
if (flag) {
break;
}
}
}
定义一个标志,如果需要交换就将标志赋值为false,每次外层循环开始时把标志置为true,内层循环遍历完数组后判断是否发生交换,若没有,falg的值应为true,说明数组已经有序,退出循环。
2、选择排序
选择排序的宗旨是每次循环选择最大或最小的数,放在数组的第一位,然后进入下一次循环...直至数组有序,选择排序的每次排序仅仅只交换需要交换的数,而不是两两交换,时间复杂度为〇(n²)。 代码:
public static void selectSort(int[] arr) {
int min;
int index;
for (int i = 0; i < arr.length - 1; i++) {
index = i;
min = arr[ i ];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[ j ]) {
min = arr[ j ];
index = j;
}
}
arr[ index ] = arr[ i ];
arr[ i ] = min;
}
}
还有个聊胜于无的优化,当需要交换的索引为自身的时候,不需要交换:
public static void selectSort(int[] arr) {
int min;
int index;
for (int i = 0; i < arr.length - 1; i++) {
index = i;
min = arr[ i ];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[ j ]) {
min = arr[ j ];
index = j;
}
}
if (index != i) {
arr[ index ] = arr[ i ];
arr[ i ] = min;
}
}
}
3、插入排序
插入排序的主要思想是把一个数组分为两个部分,前一个部分是有序的数组,后一个部分是无序的数组,每次把无序数组的第一个数插入到有序数组中,第一次数组第一个元素为一个有序的序列,时间复杂度为〇(n²)。 代码:
public static void insertionSort(int[] arr) {
int indexVal;
int index;
for (int i = 1; i < arr.length; i++) {
indexVal = arr[ i ];
index = i;
//在索引还能往前走的情况下,比较要插入有序序列的值 应该在哪个位置,
// 每次循环前一个元素把当前元素覆盖,相当于把比目标元素小的元素后移,直到找到可以插入的位置 退出
while (index - 1 >= 0 && indexVal < arr[ index - 1 ]) {
arr[ index ] = arr[ index - 1 ];
index--;
}
arr[ index ] = indexVal;
}
}
4、希尔排序
希尔排序是选择排序的一个进阶版本,对选择排序进行了改进,使得效率更高,平均时间复杂度为〇(n ㏒n),最坏时间复杂度接近于〇(n²)。
希尔排序又称为缩小增量排序,增量可以理解为把一个数组分为若干个小组,一般初始增量为数组长度的一半,然后每个小组进行选择排序,每次循环将增量折半,直到增量缩减为1,完成排序。其实比起增量我更喜欢叫步长,因为同一组元素的距离就是步长。
public static void shellSort(int[] arr) {
int len = arr.length;
int step = len;
int index;
int indexVal;
while (step != 0) {
step = step >> 1;
//step指的是步长,即隔step个数为一组,step也指的是每次第一组的第二个数(因为第一个数默认有序)
//例如:10/2=5,第一组第一个数是arr[0],第二个是arr[5]。。。5/2=2,第一组第一个数是arr[0],第二个数是arr[2]
for (int i = step; i < len; i++) {
index = i;
indexVal = arr[ i ];
if (arr[ i ] < arr[ i - step ]) {
while (index - step >= 0 && indexVal < arr[ index - step ]) {
arr[ index ] = arr[ index - step ];
index -= step;
}
arr[ index ] = indexVal;
}
}
}
}
5、快速排序
快排利用递归,每次递归将数组(不是完整的数组,因为方法需要两个参数,一个左边界,一个右边界)看做两半,取中心轴,然后把数组中比中轴小的数放在数组的左边,大的放在数组的右边,最后把中轴插在中心,再次对中轴的左子数组和右子数组进行相同的操作,最后数组有序。
jdk源码中Arrays工具类的sort方法使用的是双轴快速排序,平均时间复杂度为〇(n ㏒n),最坏时间复杂度为〇(n²)。
public static void quickSort(int[] arr, int left, int right) {
if (left > right) {
return;
}
int l = left;
int r = right;
//这个中轴取数组中的哪个数都是一样的,只是逻辑要修改一点
int pivot = arr[ l ];
while (l < r) {
while (l < r && arr[ r ] >= pivot) {
r--;
}
if (l < r) {
arr[ l ] = arr[ r ];
}
while (l < r && arr[ l ] <= pivot) {
l++;
}
if (l < r) {
arr[ r ] = arr[ l ];
}
}
arr[ l ] = pivot;
quickSort(arr, left, l - 1);
quickSort(arr, l + 1, right);
}
/**
* 重载方法
* @param arr 要排序的数组
*/
public static void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
5.1、快排应用
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例:
输入:arr = [3,2,1], k = 2 输出:[1,2] 或者 [2,1]
这个题目的解法有很多中,最容易想到的就是排序,然后输出数组的前k个即可,重要的是选择哪种排序算法最好。
看到例题上的输出:只需要输出前k个较小数即可,可以无序。想到快排,快排就是选出轴,将比轴小的数放在轴的左边,比轴大的数放在轴的右边,也就是说:当轴的下标等于k的时候就完成了算法。
递归停止条件是左边界大于等于右边界,以及轴的下标等于k。
最重要的是递归参数。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
quickSort(arr,0,arr.length-1,k);
int[] res=new int[k];
System.arraycopy(arr,0,res,0,k);
return res;
}
public void quickSort(int[] arr,int l,int r,int k){
if(l>=r) return;
int i=l,j=r,p=arr[i];
while(i<j){
while(i<j && arr[j]>=p){
j--;
}
if(i<j){
arr[i]=arr[j];
}
while(i<j && arr[i]<=p){
i++;
}
if(i<j){
arr[j]=arr[i];
}
}
arr[i]=p;
// 当i==k 时, 即 有i (0~i-1) 个数比 arr[i]小 ,不需要排序直接退出即可。
if(i==k){
return ;
}
// 这里递归参数是:当i>k , 即 k 在中轴左边,那么只需要再次对当前中轴左边的序列进行排序,可以抛弃右边的序列,反之一样。
if(i>k){
quickSort(arr,l,i-1,k);
}else{
quickSort(arr,i+1,r,k);
}
}
}
6、基数排序
思想:将所有待比较数值统一为同样的数位长度,数位较短的数在前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就成为了一个有序的序列。
public static void radixSort(int[] nums) {
int max = nums[ 0 ];
// 求出数组中最大的数 , 用来控制最外层循环次数
for (int num : nums) {
max = Math.max(max, num);
}
int maxLen = String.valueOf(max).length();
int[][] bucket = new int[ 10 ][ nums.length ];
// 位于 bucket[i] 中的桶有 bucketElement[i] 个元素
// 即 当前位(个位 | 十位 。。)是 i 的 数有 bucketElement[i] 个
int[] bucketElement = new int[ 10 ];
for (int m = 0; m < maxLen; m++) {
// 将 元素 放入桶中
for (int num : nums) {
// 第一次循环求出个位,第二次求出十位 .....
int n = num / (int) Math.pow(10, m) % 10;
bucket[ n ][ bucketElement[ n ] ] = num;
bucketElement[ n ]++;
}
int index = 0;
// 从桶中取出元素 ,复制到原数组中
for (int i = 0; i < bucket.length; i++) {
if (bucketElement[ i ] != 0) {
for (int j = 0; j < bucketElement[ i ]; j++) {
nums[ index++ ] = bucket[ i ][ j ];
}
}
// 将 这个桶中元素个数置为0
bucketElement[ i ] = 0;
}
}
}