[算法] 十个经典排序算法

Posted zhizhiyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[算法] 十个经典排序算法相关的知识,希望对你有一定的参考价值。

 动图演示参考: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)结果

技术分享图片

 

以上是关于[算法] 十个经典排序算法的主要内容,如果未能解决你的问题,请参考以下文章

十大经典排序算法总结(基数排序)

十大经典排序算法总结(归并排序)

十大经典排序算法总结(桶排序)

十大经典排序算法总结(希尔排序)

十大经典排序算法总结(快速排序)

十大经典排序算法总结(冒泡排序)