常见算法(Java)

排序

冒泡排序

介绍:

冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。

排序原理:

  1. 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。

实现:

public class Bubble {

    public static void main(String[] args) {
        int[] arr = new int[]{43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
        bubbleSort(arr);
        for (int i = 0; i < arr.length; i++) {//遍历输出
            System.out.print(arr[i] + "\t");
        }
    }

    /**
     * 冒泡排序
     * @param array 需要排序的数组
     */
    static void bubbleSort(int[] array) {
        if (array.length <= 1) { 
            return;
        }
        int temp;
        for (int i = array.length - 1; i > 0; i--) {//外循环,元素个数-1 次
            for (int j = 0; j < i; j++) {//内循环,交换相邻的两个元素
                if (array[j] > array[j + 1]) {
                    temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
    }
}

时间复杂度
这个时间复杂度还是很好计算的:外循环和内循环以及判断和交换元素的时间开销;
最优的情况也就是开始就已经排序好序了,那么就可以不用交换元素了,则时间花销为:[ n(n-1) ] / 2;所以最优的情况时间复杂度为:O( n^2 );
最差的情况也就是开始的时候元素是逆序的,那么每一次排序都要交换两个元素,则时间花销为:[ 3n(n-1) ] / 2;(其中比上面最优的情况所花的时间就是在于交换元素的三个步骤);所以最差的情况下时间复杂度为:O( n^2 );
综上所述:
最优的时间复杂度为:O( n^2 ) ;有的说 O(n),下面会分析这种情况;
最差的时间复杂度为:O( n^2 );
平均的时间复杂度为:O( n^2 );

空间复杂度
空间复杂度就是在交换元素时那个临时变量所占的内存空间;
最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n);
平均的空间复杂度为:O(1);

空间复杂度优化
有人会说这个空间复杂度能降到 0,因为空间复杂度主要是看使用的辅助内存,如果没有辅助内存变量,那么可以说空间复杂度为0;所以该算法中空间复杂度一般是看交换元素时所使用的辅助空间;

a = a + b; b = a - b; a = a - b;
a = a * b; b = a / b; a = a / b;
a = a ^ b; b = a ^ b;a = a ^ b;

上面几种方法都可以不使用临时空间来交换两个元素,但是都有些潜在的问题,比如 越界。

快速排序

原理:
快速排序是从冒泡排序中演变而来的算法,但比冒泡排序要高效很多,因为使用了分治的思想。
同冒泡排序一样,快速排序也属于交换排序,通过元素间的比较和位置的交换来达到排序的目的。 不同的是,冒泡排序在每一轮只把一个元素冒泡到散列的一端。而快速排序每一轮挑选一个“基准”元素,并让其他比他大的元素移动到数列一边,比他小的元素移动到数列另一边,从而把数列拆解成两个部分。
img
img
如图所示,在分治法的思想下,原数列在每一轮被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止。
这样一共需要多少轮呢?平均情况下需要 logn 轮,因此快速排序算法的平均时间复杂度是 O(nlogn)

基准元素(pivot)的选取:
img
img
移动元素有两种方式:指针交换法、挖坑法

指针交换法:
假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。然后在这个队列找一个数作为基准,我们选择左边第一个数,即为6,那么需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边。
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从找一个小于6的数,再从找一个大于6的数,然后交换他们。这里可以用两个变量 i 和 j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即=10),指向数字。
这里写图片描述
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j- -),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

能不能在等于6的数停下来呢?不能,造成麻烦

这里写图片描述
这里写图片描述
到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:
这里写图片描述
这里写图片描述
第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下:
这里写图片描述
这里写图片描述
这里写图片描述
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。这样循环下去直至序列不可再分。也就排序完成了。

下图作个总结:
这里写图片描述

代码实现:

package sort;


public class QuickSort {
    
    public static void main(String[] args) {
        int[] arr = {6,1,6,2,6,7,9,6,11,6,4,5,6,10};
        quickSort(arr, 0, arr.length - 1);
        for (int i : arr) {
            System.out.print(i + "\t");
        }
    }

    /**
     * 快速排序 时间复杂度 O(nlogn)
     * @param arr 要排序的数组
     * @param start 要排序的数组的起始索引,包括此元素
     * @param end 要排序的数组的终止索引,包括此元素
     */
    public static void quickSort(int[] arr, int start, int end) {
        if (start >= end) {
            return;
        }
        int i, j, temp, t;
        i = start;
        j = end;
        temp = arr[start];//基准,位置在左边
        //移动指针,进行分区
        while (i < j) {
            //先看右边,依次往左递减。为什么要先看右边呢,因为基准位置在左边,我们要保证两指针相遇的位置上的元素比基准小。
            while (temp <= arr[j] && i < j) j--;
            //再看左边,依次往右递增
            while (temp >= arr[i] && i < j) i++;
            //如果满足条件,互换左右元素
            if (i < j) {
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
        }
        //此时i和j重合,该位置也是基准适当的位置,将这两个位置的元素互换
        arr[start] = arr[i];
        arr[i] = temp;
        //递归分区后的两数组
        quickSort(arr, start, i - 1);
        quickSort(arr, i + 1, end);
    }
}

挖坑法:
img
img
img
代码实现:

/**
 * 快排 挖坑法
 * @param arr 要排序的数组
 * @param start 要排序的数组的起始索引,包括此元素
 * @param end 要排序的数组的终止索引,包括此元素
 */
public static void quickSort2(int[] arr, int start, int end) {
    if (start >= end) return;//终止递归条件
    int left = start;//左指针
    int right = end;//右指针
    int pivot = arr[start];//基准
    int index = left;//坑的位置,初始为左指针的位置
    while (left < right) {//开始这轮指针移动
        while (arr[right] >= pivot && left < right) right--;//移动右指针,当遇到比基准小的数停下来
        arr[index] = arr[right];//将数填入坑中
        index = right;//更新坑的位置,为右指针的位置
        while (arr[left] <= pivot && left < right) left++;//移动左指针,当遇到比基准大的数停下来
        arr[index] = arr[left];//将数填入坑中
        index = left;//更新坑的位置,为左指针的位置
    }
    arr[index] = pivot;//这一轮完毕,将基准填入坑中,此时坑的位置即为基准的适当位置
    quickSort2(arr, start, index - 1);//递归左边分区的序列
    quickSort2(arr, index + 1, end);//递归右边分区的序列
}

查找

二分法查找

前提:所要查找的数组必须有序
原理:从数组中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组已经为空,则表示找不到指定的元素。二分法查找每次是使查找范围缩小一半,其时间复杂度是O(logn)

代码实现:

public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = new int[]{-98,-34,2,34,54,66,79,105,210,333};
        System.out.println(binarySearch(arr, 100, 0, arr.length - 1));
    }

    /**
     * 二分法查找,时间复杂度 O(logn),返回目标元素索引,找不到则返回-1
     * @param arr 目标数组
     * @param dest 目标元素
     * @param start 起始搜索索引 包含此元素
     * @param end 终止索引 包含此元素
     * @return
     */
    public static int binarySearch(int[] arr, int dest, int start, int end) {
        int middle;
        while (start <= end) {//开始二分
            middle = (end + start) / 2;//中间点
            if (dest == arr[middle]) {//如果该中间点为目标元素,则返回
                return middle;
            } else if (dest > arr[middle]) start = middle + 1;//如果目标元素比中间点大,则继续找右边
            else end = middle - 1;//反之则继续找左边
        }
        return -1;//找不到返回-1
    }
}
``
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
常见的排序算法有许多种,以下是一些常见的排序算法Java实现示例: 1. 冒泡排序(Bubble Sort): ```java public void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // 交换arr[j]和arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } ``` 2. 插入排序(Insertion Sort): ```java public void insertionSort(int[] arr) { int n = arr.length; for (int i = 1; i < n; ++i) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j = j - 1; } arr[j + 1] = key; } } ``` 3. 选择排序(Selection Sort): ```java public void selectionSort(int[] arr) { int n = arr.length; for (int i = 0; i < n-1; i++) { int minIndex = i; for (int j = i+1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } // 交换arr[i]和arr[minIndex] int temp = arr[minIndex]; arr[minIndex] = arr[i]; arr[i] = temp; } } ``` 4. 快速排序(Quick Sort): ```java public void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } private int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; // 交换arr[i]和arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 交换arr[i+1]和arr[high] int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; } ``` 这些只是一些常见的排序算法的示例,还有其他排序算法,如归并排序、堆排序等。你可以根据需要选择适合的排序算法进行实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值