zoukankan      html  css  js  c++  java
  • [算法] 十个经典排序算法

     动图演示参考:https://www.cnblogs.com/onepixel/articles/7674659.html

    基数排序参考:https://blog.csdn.net/double_happiness/article/details/72452243

    1、常见的排序算法

    2、算法分析

    3、算法的实现

    1)排序类

     1 #ifndef _SORT_H_
     2 #define _SORT_H_
     3 
     4 #include<vector>
     5 
     6 class Sort {
     7 public:
     8     //交换排序:冒泡和快排
     9     void bubbleSort(std::vector<int> &nums);
    10     void quickSort(std::vector<int> &nums, int left, int right);
    11     
    12     //插入排序:简单插入和希尔排序
    13     void insertSort(std::vector<int>&nums);
    14     void shellSort(std::vector<int>&nums);
    15     
    16     //选择排序:简单选择排序和堆排序
    17     void selectSort(std::vector<int>&nums);
    18     void heapSort(std::vector<int>&nums);
    19 
    20     //归并排序:二路归并和多路归并
    21     void mergeSort2(std::vector<int>&,int,int);
    22 
    23     //计数排序
    24     void countingSort(std::vector<int>&);
    25     //桶排序——计数排序的进阶版
    26     void bucketSort(std::vector<int>&);
    27     //基数排序
    28     void RadixSort(std::vector<int>&nums);
    29 private:
    30     void swap_ele(int &a, int &b) {
    31         int tmp = a;
    32         a = b;
    33         b = tmp;
    34     }
    35 };
    36 
    37 #endif // !_SORT_H_

    2)排序算法的具体实现

      1 /**
      2 * 冒泡排序是一种简单的排序算法。
      3 * 它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。
      4 * 走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
      5 * 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
      6 * 时间复杂度n*n
      7 * 稳定性:稳定
      8 */
      9 void Sort::bubbleSort(std::vector<int>&nums) {
     10     for (int i = 0; i < nums.size() - 1; ++i) {
     11         for (int j = 0; j < nums.size() - 1 - i; ++j) {
     12             if (nums[j] > nums[j + 1])
     13                 swap_ele(nums[j], nums[j + 1]);
     14         }
     15     }
     16 }
     17 
     18 /**
     19 * 快速排序
     20 * 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,
     21 * 则可分别对这两部分记录继续进行排序,以达到整个序列有序。
     22 * 利用了分治的思想
     23 * 时间复杂度 n*logn
     24 * 稳定性:不稳定
     25 */
     26 void Sort::quickSort(std::vector<int> &nums, int left, int right) {
     27     if (left >= right)
     28         return;
     29     int i = left, j = right;
     30     int base = nums[left];
     31     while (i < j) {
     32         while (i<j&&nums[j]>=base)//注意等号
     33             --j;
     34         if (i < j)
     35             nums[i] = nums[j];
     36         while (i < j&&nums[i] <= base)//注意等号
     37             ++i;
     38         if (i < j)
     39             nums[j] = nums[i];
     40     }
     41     nums[i] = base;
     42     quickSort(nums, left, i - 1);
     43     quickSort(nums, i + 1, right);
     44 }
     45 
     46 /**
     47 * 插入排序
     48 * 从第一个元素开始,该元素可以认为已经被排序;
     49 * 取出下一个元素,在已经排序的元素序列中从后向前扫描;
     50 * 如果该元素(已排序)大于新元素,将该元素移到下一位置;
     51 * 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
     52 * 将新元素插入到该位置后;
     53 * 重复步骤2~5。
     54 * 时间复杂度n*n
     55 * 稳定性:稳定
     56 */
     57 void Sort::insertSort(std::vector<int>&nums) {
     58     int tmp,pos;
     59     for (int i = 1; i < nums.size(); ++i) {
     60         tmp = nums[i];
     61         pos = i - 1;
     62         while (pos >= 0 && nums[pos] > tmp) {
     63             nums[pos + 1] = nums[pos];
     64             --pos;
     65         }
     66         nums[pos + 1] = tmp;
     67     }
     68 }
     69 
     70 /**
     71 * 希尔排序
     72 * 希尔排序又叫做缩小增量排序,首先选择一个增量increment,比较距离差为increment的元素,对他们进行简单插入排序
     73 * 然后缩小增量,直到计算增量为1的情况
     74 * 增量的选择对排序的效果至关重要,希尔提出的是increment/2向下取整,直到incremtn==1,Knuth提出取increment/3+1,直到为1
     75 * 我们选用的是Knuth的方法
     76 * 时间复杂度低于n*n
     77 * 稳定性:有的稳定有的不稳定——不稳定
     78 */
     79 void Sort::shellSort(std::vector<int>&nums) {
     80     int n = nums.size();
     81     if (n < 2)return;
     82     int increment = n / 3 + 1;
     83     int tmp,i,j;
     84     while (increment > 0) {
     85         for ( i = increment; i < n; ++i) {
     86             tmp = nums[i];
     87             for (j = i - increment; j >= 0 && nums[j] > tmp; j -= increment) {//简单插入排序
     88                 nums[j + increment] = nums[j];
     89             }
     90             nums[j + increment] = tmp;
     91         }
     92         //更新increment
     93         if (increment == 1)break;//已经计算过1的情况,全部都有顺序了
     94         increment = increment / 3 + 1;
     95     }
     96 }
     97 
     98 /**
     99 * 简单选择排序
    100 * 分成两部分:已经排序序列和未排序序列,前者初始为空;
    101 * 通过扫描,找到为排序序列中的最大或者最小元素,放到未排序序列的起始位置,也是已经排序序列的末尾位置
    102 * 时间复杂度为n*n
    103 * 稳定性:不稳定
    104 */
    105 void Sort::selectSort(std::vector<int>&nums) {
    106     int Min, min_pos;
    107     int n = nums.size();
    108     for (int i = 0; i < n - 1; ++i) {
    109         Min = nums[i];
    110         min_pos = i;
    111         for (int j = i + 1; j < n ; ++j) {
    112             if (nums[j] < Min) {
    113                 Min = nums[j];
    114                 min_pos = j;
    115             }
    116         }
    117         nums[min_pos] = nums[i];
    118         nums[i] = Min;
    119     }
    120 }
    121 
    122 /**
    123 * 堆排序
    124 * 利用堆的特性,父节点一定比子节点大(小),这样每次取出根节点,就是最大(小)的
    125 * 时间复杂度:对于n个节点的堆,对每个元素执行pop()操作,时间复杂度是n*logn
    126 * 稳定性:不稳定
    127 */
    128 void Sort::heapSort(std::vector<int>&nums) {
    129     //首先堆化
    130     std::make_heap(nums.begin(), nums.end());
    131     //然后进行排序——排序中用到了pop_heap
    132     std::sort_heap(nums.begin(), nums.end());
    133 }
    134 
    135 /**
    136 * 2路归并排序
    137 * 采用分治的思想,不断把序列划分成两部分,分别对两部分进行排序
    138 * 然后把两部分合并成一个完整的有序序列
    139 * 时间复杂度:n*logn ??如何计算
    140 * 稳定性:稳定
    141 */
    142 void Sort::mergeSort2(std::vector<int>&nums, int start, int end) {
    143     std::vector<int> tmp_v;
    144     if (start < end) {
    145         int mid = start + (end - start) / 2;
    146         mergeSort2(nums, start, mid);
    147         mergeSort2(nums, mid + 1, end);
    148         //将结果合并到tmp_v数组中
    149         int i = start, j = mid + 1;
    150         while (i <= mid||j <= end) {
    151             if (i > mid||(j<=end&& nums[i] > nums[j])) {//注意j的界限的判断
    152                 tmp_v.push_back(nums[j++]);
    153             }
    154             else if (j > end || (i<=mid&&nums[j] >= nums[i])) {//注意等号,这里就决定了归并排序是稳定的
    155                 tmp_v.push_back(nums[i++]);
    156             }
    157         }
    158         //从tmp_v拷贝回nums
    159         for (i = start; i <= end; ++i) {
    160             nums[i] = tmp_v[i - start];
    161         }
    162     }
    163 }
    164 
    165 /**
    166 * 计数排序
    167 * 找出数组nums中最大的数K,建立辅助数组V,V的长度是K+1
    168 * 遍历nums,统计nums[i]的出现次数,并填入V[nums[i]]中
    169 * 遍历V,用V的下标i填充nums,直到V[i]为0
    170 * 计数排序具有一定的局限性,首先只能针对整数,并且在最大值不算太大并且序列比较集中的时候效率很高
    171 * 最好的时间复杂度n+K,最坏的时间复杂度n+K,平均时间复杂度:n+K
    172 * 额外空间复杂度:K,K为最大值
    173 * 稳定性:稳定
    174 */
    175 void Sort::countingSort(std::vector<int>&nums) {
    176     int n = nums.size();
    177     if (n < 2)return;
    178     int Max = nums[0];
    179     int i;
    180     for (i = 1; i < n; ++i) {
    181         if (nums[i] > Max)
    182             Max = nums[i];
    183     }
    184     std::vector<int>V(Max + 1, 0);
    185     for (i = 0; i < n; ++i) {
    186         ++V[nums[i]];
    187     }
    188     int idx = 0;
    189     for (i = 0; i < V.size(); ++i) {
    190         while (V[i] > 0) {
    191             nums[idx++] = i;
    192             --V[i];
    193         }
    194     }
    195 }
    196 
    197 /**
    198 * 桶排序——计数排序的进阶版
    199 * 思想是将数组nums中的元素通过映射函数分到数量有限的桶里
    200 * 然后再使用其他的排序算法把每个桶里的数据进行排序
    201 * 最后把各个桶中的记录列出来即可得到有序序列
    202 * 桶排序的效率取决于两方面:一是桶的数目尽可能大;二是映射函数尽量能够使n个数据平均分配
    203 * 可以发现,计数排序就是桶排序的特殊情况,桶的数目最大,并且每个桶中只有一个数据的情况
    204 * 最好的时间复杂度:有n个桶的时候,每个桶都只有一个元素,不用排序,时间复杂度为n
    205 * 最坏的时间复杂度:只有1个桶,这取决于采用的排序方法,时间复杂度是n*n或者n*logn
    206 * 平均时间复杂度:假设有k个桶,时间复杂度是n+k 
    207 * 额外的空间复杂度n
    208 * 稳定性:取决于单个桶中采用的排序算法
    209 */
    210 void Sort::bucketSort(std::vector<int>&nums) {
    211     int n = nums.size();
    212     if (n < 2)return;
    213     int Default_Size = 5;
    214     int Max=nums[0], Min=nums[0];
    215     int i;
    216     for (i = 1; i < n; ++i) {
    217         if (nums[i] > Max)
    218             Max = nums[i];
    219         if (nums[i] < Min)
    220             Min = nums[i];
    221     }
    222     int BucketNum = (Max - Min) / Default_Size + 1;
    223     std::vector<std::vector<int>>buckets(BucketNum);
    224 
    225     for (i = 0; i < n; ++i) {
    226         //映射函数采用(nums[i]-Min)/Default_Size
    227         buckets[(nums[i] - Min) / Default_Size].push_back(nums[i]);
    228     }
    229     //对每个桶中的元素进行快速排序
    230     int j;
    231     for (j = 0; j < buckets.size(); ++j) {
    232         if(buckets[j].size()>1)
    233             quickSort(buckets[j],0,buckets[j].size()-1);//这里采用的方法决定了桶排序是不是稳定的
    234     }
    235     //按照顺序取出元素
    236     int idx = 0;
    237     for (j = 0; j < buckets.size(); ++j) {
    238         for (i = 0; i < buckets[j].size(); ++i)
    239             nums[idx++] = buckets[j][i];
    240     }
    241 }
    242 
    243 /**
    244 * 基数排序
    245 * 把nums中所有的数的位数看成是相同长度的
    246 * 从个位开始比较各个数的大小
    247 * 统计出每位出现的次数,然后根据次数计算出起始位置
    248 * 根据起始位置,把nums[i]映射到bucket中
    249 * 用bucket的结果覆盖nums,计算更高位
    250 * 时间复杂度 n
    251 * 额外的空间 n
    252 * 稳定性:稳定
    253 */
    254 
    255 void Sort::RadixSort(std::vector<int>&nums) {
    256     int n = nums.size();
    257     if (n < 2)return;
    258     int Max = nums[0];
    259     int i;
    260     for (i = 0; i < n; ++i) {
    261         if (nums[i] > Max)
    262             Max = nums[i];
    263     }
    264     std::vector<int>pos(10,0);
    265     std::vector<int>bucket(n+1);//桶用来记录一次排序后的结果
    266     int exp = 1,idx=0;
    267     while (Max /exp) {
    268         for (i = 0; i < n; ++i) {
    269             ++pos[(nums[i] / exp) % 10];//pos[i]用来记录每一位出现的次数
    270         }
    271         for (i = 1; i < 10; ++i) {
    272             pos[i] += pos[i - 1];//这个时候pos[i]记录的是 当前位是i的数字 在桶中的起始位置,注意是起始位置
    273         }
    274         for (i = 0; i < n; ++i) {//给nums[i]重新排序
    275             idx = (nums[i] / exp) % 10;
    276             bucket[pos[idx]++] = nums[i];
    277             //这一步是关键
    278             //pos[idx]代表当前这一位的数字是idx的起始位置,每次用完起始位置之后,要向后移动
    279             //这也决定了基数排序是稳定的
    280         }
    281         for (i = 1; i <= n; ++i) {
    282             nums[i-1] = bucket[i];//重新给nums赋值
    283         }
    284         if (INT_MAX / exp < 10) {//exp已经到了整数极限
    285             break;
    286         }
    287         exp *= 10;//计算更高位
    288         for (i = 0; i < 10; ++i) {
    289             pos[i] = 0;//还原pos
    290         }
    291     }
    292 }

    3)排序算法的调用

     1 template <typename T>
     2 void printVector(std::vector<T>nums) {
     3     for (int i = 0; i < nums.size(); ++i) {
     4         std::cout << nums[i] << " ";
     5     }
     6     std::cout << std::endl;
     7 }
     8 int main()
     9 {
    10     Sort mysort;
    11     std::vector<int> nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    12     std::vector<int> res;
    13     std::cout << "冒泡排序前:";
    14     printVector(nums);
    15     std::cout << "冒泡排序后:";
    16     mysort.bubbleSort(nums);
    17     printVector(nums);
    18     std::cout << "---------------------------------" << std::endl;
    19 
    20     nums= { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    21     std::cout << "快速排序前:";
    22     printVector(nums);
    23     std::cout << "快速排序后:";
    24     mysort.quickSort(nums,0,nums.size()-1);
    25     printVector(nums);
    26     std::cout << "---------------------------------" << std::endl;
    27 
    28     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    29     std::cout << "插入排序前:";
    30     printVector(nums);
    31     std::cout << "插入排序后:";
    32     mysort.insertSort(nums);
    33     printVector(nums);
    34     std::cout << "---------------------------------" << std::endl;
    35 
    36     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    37     std::cout << "希尔排序前:";
    38     printVector(nums);
    39     std::cout << "希尔排序后:";
    40     mysort.shellSort(nums);
    41     printVector(nums);
    42     std::cout << "---------------------------------" << std::endl;
    43 
    44     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    45     std::cout << "简单选择排序前:";
    46     printVector(nums);
    47     std::cout << "简单选择排序后:";
    48     mysort.selectSort(nums);
    49     printVector(nums);
    50     std::cout << "---------------------------------" << std::endl;
    51 
    52     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    53     std::cout << "堆排序前:";
    54     printVector(nums);
    55     std::cout << "堆排序后:";
    56     mysort.heapSort(nums);
    57     printVector(nums);
    58     std::cout << "---------------------------------" << std::endl;
    59 
    60     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    61     std::cout << "2路归并排序前:";
    62     printVector(nums);
    63     std::cout << "2路归并排序后:";
    64     mysort.mergeSort2(nums,0,nums.size()-1);
    65     printVector(nums);
    66     std::cout << "---------------------------------" << std::endl;
    67 
    68     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    69     std::cout << "计数排序前:";
    70     printVector(nums);
    71     std::cout << "计数排序后:";
    72     mysort.countingSort(nums);
    73     printVector(nums);
    74     std::cout << "---------------------------------" << std::endl;
    75 
    76     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    77     std::cout << "桶排序前:";
    78     printVector(nums);
    79     std::cout << "桶排序后:";
    80     mysort.bucketSort(nums);
    81     printVector(nums);
    82     std::cout << "---------------------------------" << std::endl;
    83 
    84     nums = { 9,4,23,67,234,6,3,2,7,43,2134,643,23 };
    85     std::cout << "基数排序前:";
    86     printVector(nums);
    87     std::cout << "基数排序后:";
    88     mysort.bucketSort(nums);
    89     printVector(nums);
    90     std::cout << "---------------------------------" << std::endl;
    91 
    92     return 0;
    93 }

    4)结果

  • 相关阅读:
    配置SecondaryNameNode
    hadoop 根据secondary namenode恢复namenode
    Hadoop如何修改HDFS文件存储块大小
    hadoop1.2.1 datanode 由于权限无法启动 expected: rwxr-xr-x
    CentOS 7 下,如何设置DNS服务器
    Eclipse+pydev环境搭建
    Python numpy
    Leetcode#54 Spiral Matrix
    Leetcode#53 Maximum Subarray
    Leetcode#40 Combination Sum II
  • 原文地址:https://www.cnblogs.com/zhizhiyu/p/10255677.html
Copyright © 2011-2022 走看看