数据结构与算法——线性排序

简介: 前面已经说完了几种非线性排序,它们分别是时间复杂度为 O(n2) 、适合小规模数据的冒泡排序、选择排序、插入排序,和应用较广泛的时间复杂度为 O(nlogn) 的希尔排序、归并排序、快速排序。其实这几种排序都有一个特性,那就是它们都是基于数据的比较和移动,而今天介绍的这几种线性排序,他们的时间复杂度都是 O(n) ,因为不涉及到数据的比较和移动。

1. 回顾


前面已经说完了几种非线性排序,它们分别是时间复杂度为 O(n2) 、适合小规模数据的冒泡排序、选择排序、插入排序,和应用较广泛的时间复杂度为 O(nlogn) 的希尔排序、归并排序、快速排序。其实这几种排序都有一个特性,那就是它们都是基于数据的比较和移动,而今天介绍的这几种线性排序,他们的时间复杂度都是 O(n) ,因为不涉及到数据的比较和移动。


2. 桶排序


桶排序的思路是:将要排序的数据按照一定的范围划分到有序的桶内,在桶内进行排序,然后依次从桶中将数据取出来,这样整个数据就有序了。

例如我们要排序一组大小在 [0 - 50] 的数据,可以划分 5 个桶,每个桶存放的数据范围为:[1 - 10]、[11-20]、[21 - 30] 以此类推,就像下图这样:

8c7cd1ae890b44376c2334c8f71d0a2.png

然后在将桶内的数据排序,依次取出来,整个数据就有序了。

实际上,桶排序的应用场景十分的有限,对数据的要求比较苛刻。首先,它要求每个桶的数据范围是有序的,就像上面那样依次排列,这样才能够保证从桶中取出的数据仍然保持有序,其次,要保证数据较为均匀的划分到各个桶中,如果出现数据集中到某个或几个桶内,那么时间复杂度就会下降。极端情况下,如果数据全部划分到一个桶内,就变成了非线性排序了。

下面我模拟了一个简单的桶排序:

public class BucketSort {
    //测试场景:数组中有10000个数据,范围在0-100000之间
    //使用100个桶,每个桶存放的数据范围为:0-999, 1000-1999, 2000-2999,依次类推
    public static void bucketSort(int[] data){
        //新建100个桶,使用ArrayList作为桶
        ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(100);
        for (int i = 0; i < 100; i++) {
            buckets.add(new ArrayList<>());
        }
        //遍历数组,将数据放置到桶中
        for (int i = 0; i < data.length; i++) {
            buckets.get(data[i] / 1000).add(data[i]);
        }
        //在桶内部进行排序
        int k = 0;
        for (int i = 0; i < buckets.size(); i++) {
            ArrayList<Integer> list = buckets.get(i);
            Integer[] integers = list.toArray(new Integer[10]);
            Arrays.sort(integers);
            //拷贝到data中
            for (int j = 0; j < integers.length; j++) {
                data[k ++] = integers[j];
            }
        }
    }
}

2. 计数排序


计数排序其实是桶排序的一种特殊情况,假如要排序的数据范围不大,例如为 n ,那么我们可以划分 n 个桶,每个桶内存放数值相同的数据,然后再遍历取出来,这样整个数据就有序了。

例如我们需要根据年龄给 10 万人排序,年龄的范围其实不大,我们可以假设年龄最小为 0,最大为 120,那么我们直接划分 121 个桶,遍历数组,将年龄相同的数据存放到同一个桶中,然后取出来,就完成了排序。是不是很简单呢?

这里我就不代码演示了,和上面的桶排序非常的类似,就是没有了桶内排序的这个部分,你可以自己尝试写一下。


3. 基数排序


再来看看基数排序,假如我们要对 10 万个手机号码进行排序,应该怎么做呢?手机号码有 11 位,太长,不适合用桶排序或是计数排序。借助于稳定排序,我们可以从号码的最后一位开始比较,比较 11 次。因为借助的是稳定排序,所以前面的排序顺序不会被打乱,我用了一个简单的字符数组来模拟这个过程:

a29b5096446cd5b2d33237cdac89326.png

相关文章
|
3月前
|
算法 Java 索引
数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
46 0
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
306 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
48 1
|
21天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
134 77
|
21天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
40 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
21天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
40 9
|
21天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
33 7
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
92 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
107 21
|
2月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

热门文章

最新文章