数据结构拾遗——排序(时间复杂度O(nlogn)

Posted r088r088

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构拾遗——排序(时间复杂度O(nlogn)相关的知识,希望对你有一定的参考价值。

之前几个排序时间复杂度是n方,接下来这几个速度就要比较快了

ShellSort.h

 1 #pragma once
 2 #include "swap.h"
 3 #include <vector>
 4 template <typename T>
 5 void ShellSort(vector<T> &v) {
 6     increment = v.size();
 7     do 
 8     {
 9         increment = increment / 3 + 1;
10         for (auto i = increment; i < v.size(); i++)
11         {
12             if (v[i] < v[i - increment])
13             {
14                 T tmp = v[i];
15                 for (auto j = i - increment; j >= 0 && tmp > v[j]; j-=increment)
16                 {
17                     v[j + increment] = v[j];
18                 }
19                 v[j + increment] = tmp;
20             }
21         }
22     } while (increment > 1);
23 
24 }

希尔排序

它的做法是:

将间隔的元素看作一个子序列,对其进行插入排序

缩小间隔,继续对子序列排序

直到间隔为1

比如10个元素

将147 10,258,369分别插入排序

然后对13579,2468 10分别插入排序

最后整体插入排序

HeapSort.h

 1 #pragma once
 2 #include "swap.h"
 3 #include <vector>
 4 
 5 template <typename T>
 6 void HeapAdjust(vector<T> &v, int s, int m) {
 7     T tmp;
 8     tmp = v[s];
 9     for (auto i = 2 * (s + 1) - 1; i < v.size(); i += (i + 1) * 2 - 1)
10     {
11         if (i < m && v[i] < v[i + 1])
12             i++;
13         if (tmp >=v[i])
14             break;
15         v[s] = v[i];
16         s = i;
17     }
18     v[s] = tmp;
19 }
20 
21 template <typename T>
22 void HeapSort(vector<T> &v) {
23     for (auto i = v.size() / 2; i >= 0; i--)
24         HeapAdjust(v, i, v.size());
25     for (auto i = v.size(); i > 0; i--)
26     {
27         swapLHW(v, 1, i);
28         HeapAdjust(v, 1, i - 1);
29     }
30 }

堆排序

将数据看成一个堆

堆是具有下列性质的完全二叉树:

每个节点的值都大于等于其左右孩子的节点,且右孩子大于等于左孩子,叫做大顶堆

每个节点的值都小于等于其左右孩子的节点,且右孩子小于等于左孩子,叫做小顶堆

我们这里用大顶堆

总体思想是,

1. 先用HeapAdjust函数将数据修改为大顶堆

2. 然后把第一个(最大的一个)与最后一个互换

3. 排除最后一个,重新进行1步骤,直到只剩一个元素为止

HeapAdjust函数的流程为

假设输入的序列s到m中,除了s以为其他都复合大顶堆

则调整s与子树的位置,使s也符合大顶堆

1. 将s与其左右孩子对比,找到最大的那个

2. 比较如果s小于孩子,则继续找孩子的孩子,直到s大于某个节点的孩子

3.s与该节点换位

MergingSort.h

 1 #pragma once
 2 #include "swap.h"
 3 #include <vector>
 4 
 5 template <typename T>
 6 void Merge(vector<T> &v1, vector<T> &v2, int s, int m, int t) {
 7     int i = s, j = m + 1,k = s;
 8     for (; i <= m && j <= t; k++)
 9     {
10         if (v1[i] <= v1[j])
11             v2[k] = v1[i++];
12         else
13             v2[k] = v1[j++];
14     }
15     if (i < m)
16         for (; i <= m; i++)
17             v2[k] = v1[i];
18     if (j < n)
19         for (; j <= t; j++)
20             v2[k] = v1[j];
21     
22 }
23 
24 template <typename T>
25 void MSort(vector<T> &v1, vector<T> &v2, int s, int t ) {
26     int m;
27     vector<T> vTmp(v1, size);
28     if (s == t)
29         v2[s] = v1[s];
30     else
31     {
32         m = (s + t) / 2;
33         MSort(v1, vTmp, s, m);
34         MSort(v1, vTmp, m + 1, t);
35         Merge(vTmp, v2, s, m, t);
36     }
37 }
38 
39 template <typename T>
40 void MergingSort(vector<T> &v) {
41     MSort(v, v, 1, v.size());
42 }
43 
44 template <typename T>
45 void MergePass(vector<T> &v1, vector<T> &v2, int s, int n) {
46     int i = 0;
47     while (i <= n - 2 * s + 1)    //i+2*s-1 <= n
48     {
49         Merge(v1, v2, i, i + s - 1, i + 2 * s - 1);    //因为i本身算1个
50         i += 2 * s;
51     }
52     if (i < n - s + 1)    //i+s-1<n
53     {
54         Merge(v1, v2, i, i + s - 1, n);
55     }
56     else
57     {
58         for (auto j = i; j <= n; j++)
59         {
60             v2[j] = v1[j];
61         }
62     }
63 }
64 
65 template <typename T>
66 void MergingSort2(vector<T> &v) {
67     vector<T> Vtmp(v.size());
68     int k = 1;
69     while (k < v.size())
70     {
71         MergePass(v, Vtmp, k, v.size());
72         k *= 2;
73         MergePass(Vtmp, v, k, v.size());
74         k *= 2;
75     }
76 }

归并排序

首先是一个递归结构

Msort函数是一个递归函数

它的目的是将输入的数列(v1)的第s个到第t个进行归并排序,放入v2

流程是:

建立一个临时变量tmp

如果s和t是相同的,则将v1[s]存入v2[s]

否则将s与t的数列拆成两段(一半)递归调用Msort,将结果存入tmp

递归返回后使用merge函数将s到t这个子序列按照从小到大的顺序插入v2

所以归并排序的整体思路是

先将数列分成2个一组的子序列

将每组子序列排序

接着将2个一组的子序列合并为4个一组的子序列,并排序

一直到将整个数列合并为一组

merge函数的目的是将输入的数列中的s到m,与m+1到t两个部分合并为一个有序的部分

所以i和j依次代表遍历两个部分的变量

比较v[i]与v[j]

哪个小,推入到新序列,并将i或者j递增

直到i到达m或者j到达t

最后检查一下如果前后两段数组还有剩余,则都插入新序列后面

不过如果使用递归,空间复杂度太高

所以还提供了一个非递归的MergingSort2

只需要一个等大的临时变量tmp

设置一个子序列长度k,先为1

接着while循环,条件是k小于数组长度

然后调用MergePass函数

该函数将v中的元素按照k个为一组,将相邻的2组排序归并为一组

比如k为1,则将1,2排序归并为1组,2,3排序归并为1组……

 然后k*=2

再调用MergePass

最后k*=2

一次循环结束

MergePass函数的目的是将v1中的元素按照s个为一组,每两组归并为1组,存入v2,n为数列长度

首先建立v1的下标i=0

然后是while循环,直到下标i > n - 2 * s + 1退出循环

这里需要说明一下,如果i > n - 2 * s + 1,则i+2*s-1>n,下标将越界

在条件范围内,调用merge函数,将v1中的i到i+s-1与i+s到i+2s归并然后存入v2

i += 2 * s

循环结束后,可能会剩下一些元素没有归并

那么分两种情况:

1. 如果i < n - s + 1说明i+s-1<n,也就是说剩下元素大于s个

可以再进行一次归并,只是归并的第一个部分是s个,第二个要小于s

2. 如果i >= n - s + 1说明i+s-1>=n,也就是说剩下的元素小于等于s个

不够一次归并,所以直接将剩下的元素插入v2的尾部

QuickSort.h

 1 #pragma once
 2 #include "swap.h"
 3 #include <vector>
 4 
 5 template <typename T>
 6 int Partition(vector<T> &v, int low, int high) {
 7     int key = v[low];
 8     while (low < high)
 9     {
10         while (low < high && v[high] >= key)
11         {
12             high--;
13         }
14         swapLHW(v, low, high);
15         while (low < high && v[low] <= key)
16         {
17             low++;
18         }
19         swapLHW(v, low, high);
20     }
21     return low;
22 }
23 
24 template <typename T>
25 void QSort(vector<T> &v,int low, int high) {
26     int mid;
27     if (low < high)
28     {
29         mid = Partition(v, low, high);
30         QSort(v, low, mid - 1);
31         Qsort(v, mid + 1, high);
32     }
33 
34 }
35 
36 template <typename T>
37 void QuickSort(vector<T> &v) {
38     QSort(v, 1, v.size() - 1);
39 }

快速排序

也是一个递归的过程

Qsort将递归调用自身

Qsort函数的目的是将输入序列v的第low个元素放置于这个序列中low到high这个子序列中的正确的位置mid

然后递归调用low,mid和mid+1和high

所以快速排序的流程就是

将待排序的数列中第一个元素放置于他合适的位置

该元素将数列分成两部分

其左侧所有元素都小于等于它,其右侧所有元素都大于等于它

左侧部分继续将第一个元素放在正确的位置

右侧部分同样

最后将每个元素都放在了正确的位置

partition函数是将输入的序列的第low个元素放置于这个序列中low到high这个子序列中的正确的位置,并返回这个位置

具体流程是

while循环直到low不小于high

将v[low]保存在tmp中

比较v[high]的值与tmp的值,如果tmp小则将high递减,直到tmp大于v[high]

将v[high]与v[low]交换

比较v[low]的值与tmp的值,如果tmp大则将low递增,直到tmp小于v[low]

将v[high]与v[low]交换

循环后返回low

 

以上是关于数据结构拾遗——排序(时间复杂度O(nlogn)的主要内容,如果未能解决你的问题,请参考以下文章

排序时间复杂度为O(nlogn)的排序算法

九大排序算法时间复杂度空间复杂度稳定性

归并排序

排序总结C++

O(nlogn) + O(n) 的时间复杂度是不是只是 O(nlogn)?

排序算法时间复杂度O(n^2)冒泡排序选择排序插入排序时间复杂度O(nlogn)快速排序堆排序归并排序其他排序希尔排序计数排序