线性时间排序算法

简介:

线性时间排序算法列表

线性时间排序

Name Average Worst Memory Stable

Description

 计数排序

(Counting Sort)

n + k

n + k

n + k

Stable

 Indexes using key values.

 基数排序

(Radix Sort)

n * k

 n * k n + k Stable

 Examines individual bits of keys.

 桶排序

(Bucket Sort)

n + k

n2

n * k

Stable

 Examine bits of keys.

给定含 n 个元素的输入序列,任何比较排序在最坏情况下都需要 Ω(n log n) 次比较来进行排序。合并排序堆排序在最坏情况下达到上界 O(n log n),它们都是渐进最优的排序算法,快速排序在平均情况下达到上界 O(n log n)。

本文介绍的三种以线性时间运行的算法:计数排序基数排序桶排序,都用非比较的一些操作来确定排序顺序。因此,下界 Ω(n log n) 对它们是不适用的。

计数排序(Counting Sort)

计数排序(Counting Sort)假设 n 个输入元素中的每一个都是介于 0 到 k 之间的整数,此处 k 为某个整数。

计数排序的基本思想就是对每一个输入元素 x,确定出小于 x 的元素个数。有了这一信息,就可以把 x 直接放到它在最终输出数组中的位置上。

例如:有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。

算法描述

算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
  3. 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组,将每个元素 i 放在新数组的第 C(i) 项,每放一个元素就将 C(i) 减去 1;

算法复杂度

  • 最差时间复杂度 O(n + k)
  • 平均时间复杂度 O(n + k)
  • 最差空间复杂度 O(n + k)

计数排序不是比较排序,排序的速度快于任何比较排序算法。

计数排序的一个重要性质就是它是稳定的:具有相同值的元素在输出数组中的相对次序与它们在输入数组中的次序相同。

之所以说计数排序的稳定性非常重要,还有一个原因是因为计数排序经常用作基数排序算法的一个子过程,其稳定性对于基数排序的正确性来说非常关键。

代码示例

复制代码
 1   class Program
 2   {
 3     static void Main(string[] args)
 4     {
 5       int[] unsorted = 
 6         { 
 7           5, 9, 3, 9, 10, 9, 2, 4, 13, 10
 8         };
 9 
10       OptimizedCountingSort(unsorted);
11 
12       foreach (var key in unsorted)
13       {
14         Console.Write("{0} ", key);
15       }
16 
17       Console.Read();
18     }
19 
20     static int[] CountingSort(int[] unsorted)
21     {
22       // find min and max value
23       int min = unsorted[0], max = unsorted[0];
24       for (int i = 1; i < unsorted.Length; i++)
25       {
26         if (unsorted[i] < min) min = unsorted[i];
27         else if (unsorted[i] > max) max = unsorted[i];
28       }
29 
30       // creates k buckets
31       int k = max - min + 1;
32       int[] C = new int[k];
33 
34       // calculate the histogram of key frequencies
35       for (int i = 0; i < unsorted.Length; i++)
36       {
37         C[unsorted[i] - min]++;
38       }
39 
40       // recalculate
41       C[0]--;
42       for (int i = 1; i < C.Length; i++)
43       {
44         C[i] = C[i] + C[i - 1];
45       }
46 
47       // sort the array
48       int[] B = new int[unsorted.Length];
49       for (int i = unsorted.Length - 1; i >= 0; i--)
50       {
51         // keep stable
52         B[C[unsorted[i] - min]--] = unsorted[i];
53       }
54 
55       return B;
56     }
57 
58     static void OptimizedCountingSort(int[] unsorted)
59     {
60       // find min and max value
61       int min = unsorted[0], max = unsorted[0];
62       for (int i = 1; i < unsorted.Length; i++)
63       {
64         if (unsorted[i] < min) min = unsorted[i];
65         else if (unsorted[i] > max) max = unsorted[i];
66       }
67 
68       // creates k buckets
69       int k = max - min + 1;
70       int[] C = new int[k];
71 
72       // calculate the histogram of key frequencies
73       for (int i = 0; i < unsorted.Length; i++)
74       {
75         C[unsorted[i] - min]++;
76       }
77 
78       // copy to output array,
79       // preserving order of inputs with equal keys
80       int increment = 0;
81       for (int i = min; i <= max; i++)
82       {
83         for (int j = 0; j < C[i - min]; j++)
84         {
85           // in place, may not stable if you care
86           unsorted[increment++] = i;
87         }
88       }
89     }
90   }
复制代码

基数排序(Radix Sort)

基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数值按相同的有效位进行分组,然后在有效位区间内进行排序。

算法描述

每个元素值首先被放入一个该值的最右位所对应的桶中,桶内会保持被放入元素值最初的顺序。这使得桶的数量和值的数量能够根据其最右位建立一对一的关系。然后,通过相同的方式重复处理下一位,直到所有的位都已被处理。

  1. 获得值的最右侧的最小的位。
  2. 根据该位的值将数组内的元素值进行分组,但仍然保持元素的顺序。(以此来保持算法稳定性)
  3. 重复上述分组过程,直到所有的位都已被处理。

上述第 2 步中通常可以使用桶排序(Bucket Sort)或计数排序(Counting Sort)算法,因为它们在元素较少时拥有更好的效率。

基数排序中可以选择采用最低有效位基数排序(LSD Radix Sort:Least Significant Digit Radix Sort)或最高有效位基数排序(MSD Radix Sort:Most Significant Digit Radix Sort)。LSD 的排序方式由值的最低位也就是最右边开始,而 MSD 则相反,由值的最高位也就是最左边开始。

例如,如下这个无序的数列需要排序:

  170, 45, 75, 90, 802, 2, 24, 66

使用 LSD 方式从最低位开始(个位)排序的结果是:

  170, 90, 8022, 24, 45, 75, 66

再继续从下一位(十位)继续排序的结果是:

  802, 2, 24, 45, 66, 170, 75, 90

再继续从下一位(百位)继续排序的结果是:

  2, 24, 45, 66, 75, 90, 170, 802

算法复杂度

  • 最差时间复杂度 O(k*n)
  • 最差空间复杂度 O(k*n)

代码示例

复制代码
 1   class Program
 2   {
 3     static void Main(string[] args)
 4     {
 5       int[] unsorted = 
 6         { 
 7           15, 19, 13, 19, 10, 33, 12, 14, 13, 10,
 8         };
 9 
10       RadixSort(unsorted);
11 
12       foreach (var key in unsorted)
13       {
14         Console.Write("{0} ", key);
15       }
16 
17       Console.Read();
18     }
19 
20     static void RadixSort(int[] unsorted)
21     {
22       // our helper array 
23       int[] t = new int[unsorted.Length];
24 
25       // number of bits our group will be long 
26       // try to set this also to 2, 8 or 16 to see if it is quicker or not
27       int r = 4;
28 
29       // number of bits of a C# int 
30       int b = 32;
31 
32       // counting and prefix arrays
33       // (note dimensions 2^r which is the number of 
34       // all possible values of a r-bit number) 
35       int[] count = new int[1 << r];
36       int[] pref = new int[1 << r];
37 
38       // number of groups 
39       int groups = (int)Math.Ceiling((double)b / (double)r);
40 
41       // the mask to identify groups 
42       int mask = (1 << r) - 1;
43 
44       // the algorithm: 
45       for (int c = 0, shift = 0; c < groups; c++, shift += r)
46       {
47         // reset count array 
48         for (int j = 0; j < count.Length; j++)
49           count[j] = 0;
50 
51         // counting elements of the c-th group 
52         for (int i = 0; i < unsorted.Length; i++)
53           count[(unsorted[i] >> shift) & mask]++;
54 
55         // calculating prefixes 
56         pref[0] = 0;
57         for (int i = 1; i < count.Length; i++)
58           pref[i] = pref[i - 1] + count[i - 1];
59 
60         // from a[] to t[] elements ordered by c-th group 
61         for (int i = 0; i < unsorted.Length; i++)
62           t[pref[(unsorted[i] >> shift) & mask]++] = unsorted[i];
63 
64         // a[]=t[] and start again until the last group 
65         t.CopyTo(unsorted, 0);
66       }
67       // a is sorted 
68     }
69   }
复制代码

桶排序(Bucket Sort)

桶排序(Bucket Sort)的工作原理是将数组分解到有限数量的桶里,每个桶再分别进行排序。桶内排序有可能使用其他排序算法或是以递归的方式继续使用桶排序。

算法描述

桶排序的步骤:

  1. 在数组中查找数值的最大值和最小值;
  2. 初始化一个数组当作空桶,长度为 (MaxValue - MinValue + 1)。
  3. 遍历被排序数组,并把数值逐个放入对应的桶中。
  4. 对每个不是空的桶进行排序。
  5. 从不是空的桶里把数值再放回原来的数组中。

算法复杂度

  • 最差时间复杂度 O(n2)
  • 平均时间复杂度 O(n+k)
  • 最差空间复杂度 O(n*k)

当要被排序的数组中的数值是均匀分布时,桶排序的运行时间为线性时间 Θ(n)。桶排序不是比较排序,它不受 Ω(n log n) 下界的影响。

代码示例

复制代码
 1   class Program
 2   {
 3     static void Main(string[] args)
 4     {
 5       int[] unsorted = 
 6         { 
 7           15, 19, 13, 19, 10, 33, 12, 14, 13, 10,
 8         };
 9 
10       BucketSort(unsorted);
11 
12       foreach (var key in unsorted)
13       {
14         Console.Write("{0} ", key);
15       }
16 
17       Console.Read();
18     }
19 
20     static void BucketSort(int[] unsorted)
21     {
22       // find the maximum and minimum values in the array
23       int max = unsorted[0]; //start with first element
24       int min = unsorted[0];
25 
26       // start from index 1
27       for (int i = 1; i < unsorted.Length; i++)
28       {
29         if (unsorted[i] < min) min = unsorted[i];
30         else if (unsorted[i] > max) max = unsorted[i];
31       }
32 
33       // create a temporary "buckets" to store the values in order
34       // each value will be stored in its corresponding index
35       // scooting everything over to the left as much as possible
36       // e.g. 34 => index at 34 - minValue
37       List<int>[] buckets = new List<int>[max - min + 1];
38 
39       // initialize the buckets
40       for (int i = 0; i < buckets.Length; i++)
41       {
42         buckets[i] = new List<int>();
43       }
44 
45       // move items to bucket
46       for (int i = 0; i < unsorted.Length; i++)
47       {
48         buckets[unsorted[i] - min].Add(unsorted[i]);
49       }
50 
51       // move items in the bucket back to the original array in order
52       int k = 0; //index for original array
53       for (int i = 0; i < buckets.Length; i++)
54       {
55         if (buckets[i].Count > 0)
56         {
57           for (int j = 0; j < buckets[i].Count; j++)
58           {
59             unsorted[k] = buckets[i][j];
60             k++;
61           }
62         }
63       }
64     }
65   }
复制代码

参考资料







本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/sorting_in_linear_time.html,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
算法 搜索推荐 Shell
带你快速掌握使用c++写一些基本的算法
带你快速掌握使用c++写一些基本的算法
39 0
|
3月前
|
算法 C++ 容器
【C++11新算法】all_of、any_of、none_of算法
【C++11新算法】all_of、any_of、none_of算法
|
4月前
|
传感器 人工智能 算法
图象处理算法(介绍)
图象处理算法(介绍)
|
6月前
|
算法
算法
一、算法 常见的图查找算法包括: 1. 深度优先搜索(DFS):从图中的一个节点开始,沿着一条路径一直深入直到无法再深入为止,然后回溯到上一个节点,继续深入其他路径,直到找到目标节点或遍历完所有节点。 2. 广度优先搜索(BFS):从图中的一个节点开始,先访问它的所有邻居节点,然后再依次访问邻居的邻居节点,直到找到目标节点或遍历完所有节点。 3. Dijkstra算法:用于在带权有向图中找到从一个节点到其他节点的最短路径。该算法通过不断更新节点的最短距离来逐步找到最短路径。 4. A*算法:类似于Dijkstra算法,但在计算最短路径时加入了启发式函数,用于估计目标节点的距离,从而加速搜索过程
367 0
|
8月前
|
存储 并行计算 算法
FlashAttention算法详解
这篇文章的目的是详细的解释Flash Attention,为什么要解释FlashAttention呢?因为FlashAttention 是一种重新排序注意力计算的算法,它无需任何近似即可加速注意力计算并减少内存占用。所以作为目前LLM的模型加速它是一个非常好的解决方案,本文介绍经典的V1版本,最新的V2做了其他优化我们这里暂时不介绍。因为V1版的FlashAttention号称可以提速5-10倍,所以我们来研究一下它到底是怎么实现的。
369 0
|
算法
算法练习——(2)逢7过
中国朋友们聚会时喜欢玩"逢7过"的游戏,老外有个同样的游戏,FlipFlop,它从1计数到100,顺序输出。当遇到3的倍数就要说“Flip”,遇到5的倍数就要说“Flop”,既为3的倍数又为5的倍数则要说“FlipFlop”,说错的话表演节目或罚酒。
137 0
|
存储 算法 搜索推荐
C#算法大全(上)
今天有人想让我搞一期C#算法大全。算法就算法,安排上!
|
人工智能 算法
什么是算法?
当人们提到“算法”一词,往往就会把它们当成专属于“人工智能”的范畴,很多专业的计算机人士也是,提起算法就头疼,不知道如何学习算法,慢慢的对算法就会失去兴趣,算法不仅仅是计算机行业特有的,在我们的生活中也处处存在着算法,算法是专注于解决问题的过程和方法。
154 1
什么是算法?
|
算法
2015年408算法题
故辅助数组 q 的大小为 n+1,各元素的初值均为 0。依次扫描链表中的各结点,同 时检查 q[|data|]的值,如果为 0,则保留该结点,并令 q[|data|]=1;否则,将该结点从链表中删除。
194 0
2015年408算法题