首页 > 编程语言 >Java(4)-十大排序算法

Java(4)-十大排序算法

时间:2024-05-02 14:33:06浏览次数:28  
标签:Java nums int 复杂度 元素 算法 数组 排序

更好的总结:RUNOOB.COM 十大经典排序算法

冒泡排序

冒泡排序的基本思想是比较数组中相邻的两个元素,根据比较结果交换它们的位置,让较大的元素排到数组末尾。
遍历过程:

  1. 首轮遍历:从数组的第一个元素开始,比较相邻的两个元素,如果前一个元素大于后一个元素,则交换它们的位置,从而第一遍遍历会让最大的元素移动到数组的末端。
  2. 后续重复:忽略已经排序好的末尾元素,对剩余的元素重复上述操作。
  3. 终止条件:不再有新的元素需要交换时。
public static void sort(int[] nums){  
    int n = nums.length;  
    for(int i = 0;i < n - 1;i++){ // i表示完成排序的元素个数,也就是排到末端的个数  
        for(int j = 0;j < n - 1 - i;j++){   
			if(nums[j] > nums[j+1]){  
                int temp = nums[j];  
                nums[j] = nums[j+1];  
                nums[j+1] = temp;  
            }  
        }  
    }  
}

时间复杂度:

  1. 最好情况:O(n),输入数组已经是排序好的状态,遍历一次,不需要进行任何交换
  2. 最坏情况:O($n^2$),数组初始完全逆序
  3. 平均情况:O($n^2$)
    空间复杂度:
    O(1)

选择排序

思路:从未排序的序列中找到最小的元素,将其放在排序序列的起始位置。
举个例子:一组数字 [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5],选择排序的过程如下:

  • 第一轮选择后,1 是这组数字中的最小值,我们将它放在列表的第一个位置,并将原来的第一个位置的数字(3)放到1原来的位置。现在列表看起来是 [1, 3, 4, 1, 5, 9, 2, 6, 5, 3, 5]。
  • 第二轮选择后,从第二个位置开始的最小值是 1,我们将它放在第二个位置,并将原来第二个位置的数字(3)放到1原来的位置。列表更新为 [1, 1, 4, 3, 5, 9, 2, 6, 5, 3, 5]。
  • 如此继续,每一轮都选出最小的数字放到已排序的序列末尾,直到整个数组排序完毕。
public static void sort(int[] nums) {  
    int n = nums.length;  
    for (int i = 0; i < n - 1; i++) {  
        int minIdx = i; // 最小值索引  
        for (int j = i + 1; j < n; j++) {  
            if (nums[j] < nums[minIdx]) {  
                minIdx = j;  
            }  
        }  
        int temp = nums[i];  
        nums[i] = nums[minIdx];  
        nums[minIdx] = temp;  
    }  
}

时间复杂度:
选择排序无论数组初始状态如何,比较次数都是固定的,复杂度都是O($n^2$)
空间复杂度:
O(1)

插入排序

插入排序工作原理类似于整理扑克牌,想象我们手里拿着一些扑克,需要排序。我们可能会从左往右检查每张牌,每找到一张新牌就将其插入到左侧已经排序好的牌中的对应位置。因此插入排序的核心思想就是:将每一个元素插入到已经排序好的序列中的适当位置。

public static void sort(int[] nums) {  
    int n = nums.length;  
    int pos, cur;  
    for (int i = 1; i < n; i++) { // 默认第一张牌是排序好的,所以从第二张开始遍历  
        pos = i - 1;// 已经排序好的牌中最大的那张  
        cur = nums[i]; // 要插入的新牌  
        while (pos >= 0 && cur < nums[pos]) { //查找cur应该插入的位置,从已排序部分的最后一个元素开始,向前遍历  
            nums[pos + 1] = nums[pos]; //由于cur小于nums[pos],我们需要为cur腾出空间  
            pos--;//继续向前寻找,比较cur和前面的元素  
        }  
        // 循环结束后,pos指向了cur应该插入的位置的前一个位置  
        // 因为循环中我们是比较后将pos减少,所以最终pos停留在正确插入点前的一个位置  
        nums[pos + 1] = cur;  
    }  
}

时间复杂度:

  1. 最好情况:O(1),初始数组就是已经排序好的
  2. 最坏情况:O($n^2$),初始数组完全逆序
  3. 平均情况:O($n^2$)
    空间复杂度:
    O(1)

希尔排序

最开始接触希尔排序的时候看那些定义有点不明白,找了个例子可以帮助理解。
想象我们要把一大堆乱放的书籍按照一定的顺序排序好。如果我们每次只拿起一本书,找到它应该放的位置,这样效率可能非常低,特别是书非常多的时候。希尔排序就是在这种情况下提出的一个更聪明的办法。
1. 分批处理:首先,我们不再一本一本地整理,而是分批处理。可能先每隔十本书拿起一本,只把这些书放到正确的位置。这样,虽然整体上书还是乱的,但是每十本书中已经有一本是放对了位置。
2. 逐步精细调整:完成第一轮粗略的排序后,再缩小范围,比如这次每隔五本书处理一本。虽然这次需要处理更多的书,但由于第一轮的预处理,很多书已经不需要移动太远。
3. 最后的细节调整:继续这样逐步减少间隔,直到最后每本书都要考虑一次。这时,由于前面的预处理,整理起来会更快更容易。
通过这种分批次的处理,每次虽然我们移动的书不一定都在最终位置,但是大部分书都离自己的位置更近了。这样到了最后,即使是细节上的调整,也因为大部分书已经不错了,所以整体效率提高了很多。
正式地介绍希尔排序:希尔排序是通过将原始列表分割成多个子列表来提高排序的效率。个子列表包含原始列表中间隔特定"增量"的元素。通过逐步缩小增量,直至增量为1,希尔排序使得元素逐渐移向其正确的位置。
排序过程:
1.选择增量序列:最初的增量通常是数组长度的一半,之后每次将增量减半,直到增量为1。
2.分组排序:在每一个增量的步骤中,我们可以用排序算法进行排序,可以是冒泡也可以是插入等。
3. 缩小增量:完成一轮增量值的插入排序后,减少增量值,并重复上述过程。
4.增量为1的最终排序:当增量减少到1时,整个数组就变成了一个子列表。这时进行一次标准的某某排序算法。

public static void sort(int[] nums) {  
    // 分组 + 冒泡  
    int n = nums.length;  
    int gap = n / 2;  
    while (gap > 0) {  
        for (int j = gap; j < n; j++) {  
            int i = j;  
            while (i >= gap && nums[i - gap] > nums[i]) {  
                int temp = nums[i];  
                nums[i] = nums[i - gap];  
                nums[i - gap] = temp;  
                i -= gap;  
            }  
        }  
        gap /= 2;  
    }  
}

时间复杂度:
1.最好情况:O(nlogn)
2.最坏情况:O($n^2$)
3.平均情况:O($n^{3/2}$)
空间复杂度:
O(1)

归并排序

1.5 归并排序 | 菜鸟教程 非常推荐这里的动图,看完就懂了。
归并排序采用分治法的思想将一个数组分成更小的数组,直到每个小数组只有一个元素,然后将这些数组递归合并成较大数组,直到最终得到一个完全排序的数组。
排序步骤:
1. 分割:首先将数组从中间分割成前后两部分,然后递归分割左右两部分,直到每个部分只有一个元素或为空为止,因为一个元素自然是排好序的。
2. 归并:然后将这些分割后的部分两两归并,归并的过程需要将它们排序。比如,我们有两个已排序的子列表,从两个列表的开始(最小端)比较,选择较小的元素放入新的列表中,然后移动该列表的索引,直到所有元素都重新排序在一起。
例子:
假设有数组 [4, 3, 2, 1],归并排序的过程是:
分割:[4, 3, 2, 1] -> [4, 3] 和 [2, 1]
再分割:[4, 3] -> [4] 和 [3];[2, 1] -> [2] 和 [1]
归并:[4] 和 [3] 归并为 [3, 4];[2] 和 [1] 归并为 [1, 2]
再归并:[3, 4] 和 [1, 2] 归并为 [1, 2, 3, 4]

public static void sort(int[] nums, int start, int end) {  
    if (start < end) {  
        int mid = (start + end) / 2;  
        // 分别排序  
        sort(nums, start, mid);  
        sort(nums, mid + 1, end);  
        // 合并  
        merge(nums, start, end);  
    }  
}  
public static void merge(int[] nums, int left, int right) {  
    // 合并的是两个有序数组  
    int[] temp = new int[nums.length];  
    int mid = left + (right - left) / 2;  
    int p1 = left; // 左边的数组  
    int p2 = mid + 1; // 右边的数组  
    int k = left;  
    while (p1 <= mid && p2 <= right) {//合并相同长度的部分  
        if (nums[p1] <= nums[p2]) {  
            temp[k++] = nums[p1++];  
        } else {  
            temp[k++] = nums[p2++];  
        }  
    }  
    while (p1 <= mid) {//合并左边数组可能多的情况  
        temp[k++] = nums[p1++];  
    }  
    while (p2 <= right) {//合并右边数组可能多的情况  
        temp[k++] = nums[p2++];  
    }  
    for (int i = left; i <= right; i++) {  
        nums[i] = temp[i];  
    }  
}

时间复杂度:
最好最坏情况,时间复杂度都是O(n logn)
空间复杂度:
O(n)

快速排序

快速排序是分治策略,基本步骤:
1.选择基准:从数组中选择一个元素作为基准值
2.分区操作:重排数组,使得所有小于基准值的元素都移到基准左边,大于基准值的元素移到右边
3.递归排序:递归地将小于基准值的部分和大于基准值的部分分别进行快排

public static void sort(int[] nums, int start, int end) {  
    if (start >= end) return;  
    int left = start;  
    int right = end;  
    int temp = nums[left];  
    while (left < right) {  
        while (left < right && temp <= nums[right]) {  
            right--;  
        }  
        nums[left] = nums[right];  
        while (left < right && temp >= nums[left]) {  
            left++;  
        }  
        nums[right] = nums[left];  
    }  
    nums[left] = temp;  
    sort(nums, start, left - 1);  
    sort(nums, left + 1, end);  
}

时间复杂度:
1.最好情况:O(n logn),每次都可以将数组精确分成两个大小几乎相等的子数组
2.最坏情况:O($n^2$),每次选项的基准值都是最大或者最小的值
3.平均情况:O(n logn)
空间复杂度:
1.最坏情况:O(n),每次分区只选到基准值是最大或者最小值的情况,导致每次递归只能减少一个元素,分成的两部分中一部分包含n-1个元素,另一部分只有0个元素
2.平均和最好情况:O(logn),每次分区都将数组分成大致相等的两部分

堆排序

堆本质上是一个完全二叉树,分成最大堆和最小堆:最大堆中,每个节点的值都大于等于其子节点的值;最小堆中,每个节点的值都小于等于其子节点的值。堆排序就是利用了这种数据结构进行排序的算法。
排序步骤:
1.建立堆:
- 将无序的数据数组构造成一个最大堆或最小堆。一般来说,如果想要排序结果是升序的,就建立最大堆;如果想要的是降序的,就建立最小堆。
- 这个过程是从最后一个非叶子节点开始,向前逐个进行下沉调整(让所有子节点都小于或等于父节点)。
2.调整堆
- 将堆顶元素(最大或最小值)与堆的最后一个元素交换,然后移除最后一个元素(这个元素已经是当前最大或最小值了)。
- 对剩下的元素重新进行下沉调整,确保顶部是最大或最小值。
- 重复此过程,直到所有元素都被移除堆,即完成排序。

public static void sort(int[] list) {  
    /**  
     * 构造堆  
     * 从第一个非叶子节点,也就是倒数第二行最后一个开始调整  
     * 左右孩子节点中较大的交换到父节点中  
     * i是从下往上  
     */  
    for (int i = list.length / 2 - 1; i >= 0; i--) {  
        headAdjust(list, list.length, i);  
    }  
    /**  
     * 排序堆  
     * 将最大节点list[0]放在堆尾list[i]  
     * 然后从根节点重新调整  
     * 每次把最后一个排好位置的最大值忽略掉  
     */  
    for (int i = list.length - 1; i >= 1; i--) {  
        int temp = list[0];  
        list[0] = list[i];  
        list[i] = temp;  
        headAdjust(list, i, 0);  
    }  
}  
  
/**  
 * 调整堆  
 *  
 * @param list:整个二叉树  
 * @param len:list的长度  
 * @param i:三个中的根节点  
 */  
public static void headAdjust(int[] list, int len, int i) {  
    int index = 2 * i + 1; // 左孩子  
    while (index < len) {  
        if (index + 1 < len) { // 说明还有右孩子  
            if (list[index] < list[index + 1]) {  
                index = index + 1;  
            }  
        }  
        // 检查是否有右孩子,且右孩子比左孩子大,如果是,更新index为右孩子索引  
        if (list[index] > list[i]) {  
            int temp = list[i];  
            list[i] = list[index];  
            list[index] = temp;  
            i = index;  
            index = 2 * i + 1;  
        } else {  
            break;  
        }  
    }  
  
}

时间复杂度:
最好最坏和平均情况下都是O(n logn)
空间复杂度:
O(1)

计数排序

计数排序,计数计的是数组中每个数字出现的次数。
排序步骤:
1. 初始化:首先,创建一个足够大的数组,大小为待排序数组中的最大值加1(比如1到100的范围,就创建101个格子的数组)。
2. 计数:遍历待排序的数组,每读到一个数字,就在相应的数组索引位置增加1。这样,每个位置的数字就代表了该索引值在原数组中出现的次数。
3. 输出排序结果:最后,遍历这个计数数组,根据每个位置上的数值,重复输出其索引,从而得到一个有序数组。
计数排序适用于数据范围较小并且分布比较均匀的情况,比如成绩排序、调查问卷数据排序等;因此对于数据范围很大或者数据分布很不均匀的情况,就会导致占用过多的内存。

public static void sort(int[] nums, int min, int max) {  
    int n = nums.length;  
    int[] temp = new int[max - min + 1];  
    // 计数  
    for (int i = 0; i < n; i++) {  
        temp[nums[i] - min]++;  
    }  
    int idx = 0;  
    for (int i = 0; i < temp.length; i++) {  
        int cnt = temp[i];  
        while (cnt != 0) {  
            nums[idx] = i + min;  
            idx++;  
            cnt--;  
        }  
    }  
}

时间复杂度:
O(n+k) :n是原数组的长度,k是数据范围的长度
空间复杂度:
O(k)

桶排序

桶排序是计数排序的升级版。桶排序解决了计数排序在处理大范围数据时效率低和空间浪费的问题,并且计数排序仅适用于非负整数,桶排序可以用于整数、浮点数甚至其他数据类型。
想象一下你有一堆乒乓球,每个球上面标有不同的数字,你需要按照数字的大小排序。桶排序的过程就像是你用多个桶来分类这些球:将数字范围划分为几个区间,每个区间对应一个桶,然后根据乒乓球上的数字将它们放入相应的桶中。放好之后,再在每个桶内进行排序,最后依次将每个桶里的球取出,这样就完成了整体的排序。
排序步骤:
1.确定桶的数量和范围:基于待排序数据的范围和特点,选择合适的桶的数量和每个桶的值域范围。
2. 分配元素到桶中:遍历原始数据,根据元素值将每个元素分配到对应的桶中。
3. 对每个桶内部进行排序:可以使用其他排序方法,如快速排序或插入排序。
4. 收集桶中的元素:按顺序从每个桶中取出元素,合并成一个有序的数组。

/**  
 * 以浮点数排序为例  
 */  
public static void sort(float[] nums) {  
    // 1.初始化桶,桶内会频繁插入数据,选用LinkedList  
    ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();  
    for (int i = 0; i < 10; i++) {  
        buckets.add(new LinkedList<>());  
    }  
    // 2.数据放入桶中并完成排序  
    for (float num : nums) {  
        int idx = getBucketIdx(num); // 桶序号  
        insertSort(buckets.get(idx), num);  
    }  
    // 3.从桶中取出数据  
    int idx = 0;  
    for (LinkedList<Float> bucket : buckets) {  
        for (Float num : bucket) {  
            nums[idx++] = num;  
        }  
    }  
  
  
}  
  
// 计算元素放入哪个桶中  
public static int getBucketIdx(float num) {  
    // 取浮点数的整数部分作为桶的索引值,实际具体怎么分桶可以变  
    return (int) num;  
}  
  
// 桶内元素进排序,具体怎么排序自己定  
public static void insertSort(List<Float> bucket, float num) {  
    ListIterator<Float> iterator = bucket.listIterator();  
    boolean insertFlag = true;  
    while (iterator.hasNext()) {  
        if (num <= iterator.next()) {  
            iterator.previous();  
            iterator.add(num);  
            insertFlag = false;  
            break;        }  
    }  
    if (insertFlag) {  
        bucket.add(num);  
    }  
}

时间复杂度:
桶排序的时间复杂度取决于数据的分布、桶的数量以及桶内排序算法的效率。理想情况下,如果数据均匀分布,桶的数量合适,并且每个桶内使用线性时间的排序算法(如计数排序),那么桶排序的平均时间复杂度可以达到

标签:Java,nums,int,复杂度,元素,算法,数组,排序
From: https://www.cnblogs.com/marigo/p/18170175

相关文章

  • Java线程池核心线程用尽后为何优先排队而不是继续创建线程直至最大线程数?
    前阵子在v2ex上看到这篇帖子讨论这个问题,有意思的是这个如此基础的问题在Javaer的世界里并没有广泛的共识,下面的回答也是七嘴八舌的,刚好在《JavaPerformace》上看到对这个问题的解释,尝试总结一下。原因书中对线程池的解释基于以下几点前提:如果CPU已经跑满,增加线程并不能提高......
  • 一份透心凉的北森java冷面(面经)
    1.对于分布式的理解2.几台机器合作怎么保证高可用3.es打了几个节点4.为什么es快5.es的build和body的区别6.es想进行时间范围搜索,用到什么命令和接口7.es的索引有哪些8.redis为什么搜索快9.在什么地方使用了redis10.将数据直接放到本地内存里更快,为什么用redis11.分布式......
  • java命名规范
    1、java文件名规范a、一个文件中最多只能有一个public类且文件名必须和public类名一致b、文件中可以有多个类,若无public修饰的类,此文件名可以是任意名2、java类与成员命名规范a、类名规范-类名首字母必须大写,使用驼峰命名法b、类修饰符(写在类前边,只有两种)-public-defau......
  • Akima算法
        测量数据的内插已有各种方法,如线性内插、多项式内插、样条函数插值等,但这里的Akima插值法具有独特的优点。  线性内插只顾及其附近两点的影响。  多项式内插时,低阶多项式由于参数较少,内插精度很低,而使用高阶多项式又会使解不稳定,出现“龙格”现象,即......
  • HPA* (Near Optimal hierarchical Path-finding)算法的效果图
    本文中的图全部来自:https://mohitsharma0690.blogspot.com/2016/01/hierarchical-pathfinding.html图的说明:Hereisanexampleofhowclustersarecreatedinanopenspaceenvironment.Thewhitesquaresrepresentwalkablegrids.Non-walkablegridspacesaremark......
  • 读天才与算法:人脑与AI的数学思维笔记15_声响的数学之旅
    1. 音乐1.1. 巴赫的作品以严格的对位著称,他十分中意对称的结构1.2. 巴托克的作品很多都以黄金比例为结构基础,他非常喜欢并善于使用斐波纳契数列1.3. 有时,作曲家是本能地或者不自知地被数学的模式和结构所吸引,而他们并没有意识到这些数学模式的意义1.4. 有时,他们主动去寻......
  • 05.Java 方法详解
    1.方法的定义及调用设计方法的原则:一个方法只完成一个功能,有利于后期的扩展方法的定义:修饰符(可选)返回值类型方法名(参数类型参数名(可选)){方法体return返回值;}2.方法重载重载:就是在一个类中,有相同的函数名称,但形参不通的函数方法的重载规则:方法名称必......
  • 冒泡排序
    冒泡排序:也就是用第一个元素和第二个元素进行比较,如果第一个元素的值大于第二个元素则两者位置互换,否则不交换。然后第二个元素和第三个元素比较.......最后序列中最大的元素被交换到了序列的尾部,这样就完成了一轮交换,经过n轮交换之后,就可以得到一个有序序列。#include<stdio.h>......
  • 元素排序去重
    排序去重先是使用了C++中algorithm中设计好的sort算法,进行一个从小到大的排序,然后使用unique函数对这个数组进行一个整理。unique函数做的事情就是把重复的元素放到后面去,实际上并没有把这些元素删除。https://www.acwing.com/problem/content/description/5068/#include......
  • 冒泡排序
    /*******************************************************************************************************@filename: :BubbleSort*@brief :冒泡排序*@author :[email protected]*@date :2024/04/30*@version1.0 :V1.0*@prop......