【数据结构】排序算法大全

简介: 【数据结构】排序算法大全

一、排序算法概述

1、排序的介绍

排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程

2、排序的分类

1)存储介质

内部排序:数据量不大,数据在内存

外部排序:数据量大,数据在外存,分批调入内存

2)比较器个数

串行排序:单处理机

并行排序:多处理机

3)主要操作

比较排序:用比较的方法

基数排序:根据取值确定有序位置

4)辅助空间

原地排序:空间O(1),无辅助空间

非原地排序:空间不是O(1)

5)稳定性

稳定性只对结构类型数据有意义

稳定排序:相等元素排序后次序不变

非稳定排序:相等元素排序后次序发生了变化

6、自然性

自然排序:原来的数据有序,排序速度很快

非自然排序:原来的数据有序,但排序速度却变慢了

二、算法的复杂度

1、时间度量方法

  1. 事后统计法
  2. 事前估计法

2、计算时间复杂度

  1. 用常数1代替运行时间中的所有加法常数
  2. 修改后的运行次数两数中,只保留最高阶项
  3. 去除最高阶项的系数

3、常见的时间复杂度

  1. 常数阶 O(1)
  2. 对数阶 O(log2n)
  3. 线性阶 O(n)
  4. 线性对数阶 O(nlog2n)
  5. 平方阶 O(n^2)
  6. 立方阶 O(n^3)
  7. k次方阶 O(n^k)
  8. 指数阶 O(2^n)

4、平均时间复杂度和最坏时间复杂度

平均时问复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。

最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。

平均时间复杂度和最坏时间复杂度是否一致,和算法有关

5、算法的空间复杂度

类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模口有关,它随着口的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法,基数排序就属于这种情况

在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品(redis、memcache)和算法(基数排序)本质就是用空间换时间

三、排序算法

1、冒泡排序

1)介绍

冒泡排序(Bubble Sorting):通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。

2)优化

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 冒泡排序
 * @author  王铭颢
 * @date  2022/7/2 10:25
 */
public class BubbleSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
        System.out.println(Arrays.toString(num));
        for(int i = 0; i < num.length; i++){
   
   
            boolean flag = true;
            for (int j = 0; j < num.length - i - 1; j++) {
   
   
                if(num[j] > num[j+1]){
   
   
                    // 交换
                    int temp = num[j];
                    num[j] = num[j+1];
                    num[j+1] = temp;
                    // 标记
                    flag = false;
                }
            }
            if(flag){
   
   
                break;
            }
        }
        System.out.println(Arrays.toString(num));
    }
}

2、选择排序

1)介绍

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。

2)思想

后面序列中找到最值与前面元素互换

  1. 选择排序一共有“数组大小 - 1”轮排序
  2. 每轮排序,又是一个循环,循环的规则
    1. 先假定当前这个数是最小数
    2. 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
    3. 当遍历到数组的最后时,就得到本轮最小数和下标
    4. 交换

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 选择排序
 * @author  王铭颢
 * @date  2022/7/2 10:24
 */
public class SelectSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
        System.out.println(Arrays.toString(num));
        for (int i = 0; i < num.length; i++) {
   
   
            // 找最小值
            int min = i;
            for (int j = i + 1; j < num.length; j++) {
   
   
                min = num[j] > num[min] ? min :j;
            }
            // 交换
            int temp = num[i];
            num[i] = num[min];
            num[min] = temp;
        }
        System.out.println(Arrays.toString(num));
    }
}

3、插入排序

1)介绍

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的

2)思想

把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 插入排序
 * @author  王铭颢
 * @Date  2022/7/2 10:35
 */
public class InsertSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   10,9,8,7,6,5,4,3,2,1};
        System.out.println(Arrays.toString(num));
        for (int i = 1; i < num.length; i++) {
   
   
            // 取出无需表首元素
            int temp = num[i];
            // 把temp找位置插入
            for (int j = i ; j >= 0; j--) {
   
   
                // 找到大于前一个元素的位置插入
                // 如果是最小值,j == 0,插入最前面
                if(temp > num[j] || j == 0){
   
   
                    num[j] = temp;
                    break;
                }
                // 元素后移(对于插入元素已经存在temp中了)
                num[j] = num[j - 1];
            }
        }
        System.out.println(Arrays.toString(num));

    }
}

4、希尔排序

1)介绍

希尔排序是希尔 (Donald Shell)于 1959 年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。

2)基本思想

插入排序的问题:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.

把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 希尔排序
 *
 * @author 王铭颢
 * @Date 2022/7/3 08:51
 */
public class ShellSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
        System.out.println(Arrays.toString(num));
        shell_2(num);
        System.out.println(Arrays.toString(num));
    }

    /**
     * 希尔排序_交换法
     *
     * @param num 待排序数组
     */
    public static void shell_1(int[] num) {
   
   
        // 分组
        // gap:每组的元素个数
        for (int gap = num.length / 2; gap > 0; gap /= 2) {
   
   
            // 从最后一组的第一个元素开始,依次与上一组的对应元素比较并交换
            for (int i = num.length - gap - 1; i < num.length; i++) {
   
   
                for (int j = i - gap; j >= 0; j -= gap) {
   
   
                    if (num[j] > num[j + gap]) {
   
   
                        int temp = num[j];
                        num[j] = num[j + gap];
                        num[j + gap] = temp;
                    }
                }
            }
        }
    }

    /**
     * 希尔排序_移位法
     *
     * @param num 待排序数组
     */
    public static void shell_2(int[] num) {
   
   
        int count = 0;
        // 分组
        // gap:每组的元素个数
        for (int gap = num.length / 2; gap > 0; gap /= 2) {
   
   
            // 从第二组开始
            for (int i = gap; i < num.length; i++) {
   
   
                // 记录被操作的值
                int temp = num[i];
                // 移动前面元素
                for (int j = i; j >= 0; j -= gap) {
   
   
                    // 找到前一个位置比自己小的,否则向后移动元素
                    if (j - gap < 0 || num[j - gap] < temp) {
   
   
                        num[j] = temp;
                        break;
                    } else {
   
   
                        num[j] = num[j - gap];
                    }
                }
            }
        }
    }
}

5、快速排序

1)介绍

快速排序(Quicksort)是对冒泡排序的一种改进算法

2)基本思想

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 快速排序
 *
 * @author 王铭颢
 * @Date 2022/7/3 11:48
 */
public class QuickSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
        System.out.println(Arrays.toString(num));
        quickSort(num, 0, num.length - 1);
        System.out.println(Arrays.toString(num));
    }

    private static void quickSort(int[] num, int left, int right) {
   
   
        if (left >= right) {
   
   
            return;
        }
        int l = left;
        int r = right;
        int pivot = num[(l + r) / 2];
        while (l < r) {
   
   
            while (num[l] < pivot) {
   
   
                l++;
            }
            while (num[r] > pivot) {
   
   
                r--;
            }
            if (l > r) {
   
   
                break;
            }
            int temp = num[l];
            num[l] = num[r];
            num[r] = temp;
            l++;
            r--;
        }
        quickSort(num, 0, r);
        quickSort(num, l, right);

    }
}

6、归并排序

1)介绍

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治策略

分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之。

2)思想

3)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 归并排序
 *
 * @author 王铭颢
 * @Date 2022/7/3 22:54
 */
public class MergeSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   12, 32, 45, 64, 83, 64, 23, 65, 10};
        System.out.println(Arrays.toString(num));
        int[] temp = new int[num.length];
        mergeSort(num, temp, 0, num.length - 1);
        System.out.println(Arrays.toString(num));
    }

    public static void mergeSort(int[] num, int[] temp, int left, int right) {
   
   
        if (left < right) {
   
   
            int mid = (left + right) / 2;
            mergeSort(num, temp, left, mid);
            mergeSort(num, temp, mid + 1, right);
            merge(num, temp, left, mid, right);
        }
    }

    public static void merge(int[] num, int[] temp, int left, int middle, int right) {
   
   
        int i = left;
        int j = middle + 1;
        int t = 0;
        // 1.把左右有序放在temp
        while (i <= middle && j <= right) {
   
   
            if (num[i] <= num[j]) {
   
   
                temp[t++] = num[i++];
            } else {
   
   
                temp[t++] = num[j++];
            }
        }
        // 2.把剩余的一方放到temp
        while (i <= middle) {
   
   
            temp[t++] = num[i++];
        }
        while (j <= right) {
   
   
            temp[t++] = num[j++];
        }
        // 3.把temp复制到num
        t = 0;
        for (int ii = left; ii <= right; ii++) {
   
   
            num[ii] = temp[t++];
        }
    }
}

7、基数排序(桶排序)

1)介绍

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort / bin sort)。它是通过键值的各个位的值,将要排序的元素分配至某些 “桶”中,达到排序的作用
  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
  3. 基数排序(Radix Sort)是桶排序的扩展
  4. 基数排序是 1887年赫尔曼 • 何乐礼发明的。将整数按位数切割成不同的数字,然后按每个位数分别比较

2)思想

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。

这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

3)注意事项

  1. 基数排序是对传统桶排序的扩展,速度很快.
  2. 基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时,容易造成 OutOfMemory Error。
  3. 基数排序时稳定的。
  4. 数组不能有负数

4)代码实现

package work.rexhao.sort;

import java.util.Arrays;

/**
 * 基数排序
 *
 * @author 王铭颢
 * @Date 2022/7/3 22:22
 */
public class RadixSort {
   
   
    public static void main(String[] args) {
   
   
        int[] num = new int[]{
   
   12,32,45,64,83,64,23,65,10};
        System.out.println(Arrays.toString(num));
        radixSort(num);
        System.out.println(Arrays.toString(num));
    }

    private static void radixSort(int[] num) {
   
   
        // 对个位
        ArrayQueue aq[] = new ArrayQueue[10];   // 对象数组
        for (int i = 0; i < 10; i++) {
   
   
            aq[i] = new ArrayQueue(num.length);
        }
        for (int i = 0; i < num.length; i++) {
   
   
            aq[num[i] % 10].addQueue(num[i]);
        }
        int count = 0;
        for (int i = 0; i < 10; i++) {
   
   
            while(!aq[i].isEmpty()){
   
   
                num[count++] = aq[i].getQueue();
            }
        }
        // 对十位
        for (int i = 0; i < num.length; i++) {
   
   
            aq[num[i] / 10].addQueue(num[i]);
        }
        count = 0;
        for (int i = 0; i < 10; i++) {
   
   
            while(!aq[i].isEmpty()){
   
   
                num[count++] = aq[i].getQueue();
            }
        }
    }
}
/**
 * 使用数组模拟队列----编写一个ArrayQueue类
 */
class ArrayQueue {
   
   
    private int maxSize; // 数组最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private int[] arr; // 队列的数据

    /**
     * 创建队列的构造器
     */
    public ArrayQueue(int arrMaxSize) {
   
   
        maxSize = arrMaxSize;
        arr = new int[arrMaxSize];
        front = -1;// 指向队列头的前一个位置
        rear = -1;// 指向队列尾
    }

    /**
     * 判断队列是否满
     */
    public boolean isFull() {
   
   
        return rear == maxSize - 1;
    }

    /**
     * 判断队列是否为空
     */
    public boolean isEmpty() {
   
   
        return rear == front;
    }

    /**
     * 添加队列数据
     */
    public void addQueue(int n) {
   
   
        if (isFull()) {
   
   
            System.out.println("队列满,添加失败!");
            return;
        }
        arr[++rear] = n;
    }

    /**
     * 出队列
     */
    public int getQueue() {
   
   
        if (isEmpty()) {
   
   
            // 抛出异常 -- 不需要写return
            throw new RuntimeException("队列空");
        }
        return arr[++front];
    }

    /**
     * 遍历
     */
    public void showQueue() {
   
   
        if (isEmpty()) {
   
   
            System.out.println("队列空!");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
   
   
            System.out.printf("arr[%d] = %d\n", i, arr[i]);
        }
    }

    /**
     * 显示头数据(不取出)
     */
    public int headQueue() {
   
   
        if (isEmpty()) {
   
   
            throw new RuntimeException("队列空");
        }
        return arr[front + 1];
    }
}

四、常用排序总结对比

排序方法 时间-平均 时间-最好 时间-最坏 空间 稳定性
冒泡排序 n^2 n n^2 1 稳定
选择排序 n^2 n^2 n^2 1 不稳定
插入排序 n^2 n n^2 1 稳定
希尔排序 nlogn nlog^2 2 nlog^2 2 1 不稳定
归并排序 nlogn nlogn nlogn n 稳定
快速排序 nlogn nlogn n^2 logn 不稳定
堆排序 nlogn nlogn nlogn 1 不稳定
基数排序 n * k n * k n * k n + k 稳定
目录
相关文章
|
28天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
45 1
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
96 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
29天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
28天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
1月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
103 23
|
1月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
60 20
|
1月前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
53 0
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
46 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
1月前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
44 0
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
50 4