下面写一些关于排序的一些基本方法:
1.冒泡排序
思路:就像“冒泡泡”一样,从头开始,两个两个数作比较,将两个数中较大(较小)的数向后移,再比较下面两个数,依次将较大(较小)的数向后移,最终,将一组数中最大(最小)的数,移动到最后面,再从头开始,移下一个数
14 /*************************************
15 *
16 *冒泡排序
17 *时间复杂度为O(N^2)
18 *空间复杂度为O(1)
19 *稳定性:稳定排序
20 *
21 *************************************/
22 void BubbleSort(int array[],size_t size)
23 {
24 if(size <= 1)
25 {
26 //只有0个或者1个元素
27 return ;
28 }
29 //[0,bound)元素表示有序元素,[bound,size)表示待排序元素
30 size_t bound = 0;//边界
31 for(; bound < size ; ++bound)
32 {
33 size_t cur = size - 1;
34 for(; cur > bound ; --cur)
35 {
36 if(array[cur] < array[cur - 1])//cur从最后一个元素往前走,将最小的元素往前冒
37 {
38 Swap(&array[cur],&array[cur - 1]);
39 }
40 }
41 }
42 return ;
43 }
2.选择排序
思路:选择序列中最小(最大)的数放到最前面,然后再在剩下的序列中选择最小(最大)的数放到第二个位置,依次往下
45 /*************************************
46 *
47 *选择排序
48 *时间复杂度为O(N^2)
49 *空间复杂度为O(1)
50 *稳定性:不稳定排序
51 *
52 *************************************/
53 void SelectSet(int array[],size_t size)
54 {
55 if(size <= 1)
56 {
57 return ;
58 }
59 //[0,bound)为有序区间
60 //[bound,size)为待排序区间
61 size_t bound = 0;//相当于是擂台
62 for(; bound < size ;++bound)
63 {
64 size_t cur = bound + 1;
65 for(; cur < size ; ++cur)
66 {
67 if(array[bound] > array[cur])
68 {
69 Swap(&array[bound],&array[cur]);
70 }
71 }
72 }
73 return ;
74 }
3.插入排序
思路:将要排序的元素插入到已经排序好的序列中
76 /*************************************
77 *
78 *插入排序
79 *把有序区间当作线性表,把当前bound指向的元素插入到线性表中
80 *
81 *时间复杂度为O(N^2)
82 *空间复杂度为O(1)
83 *稳定性:稳定排序
84 *选择排序特点:
85 *a.当数组元素个数少的时候,执行效率快
86 *b.若数组基本有序,执行效率也块
87 *
88 *************************************/
89 void InsertSort(int array[],size_t size)
90 {
91 if(size <= 1)
92 {
93 return ;
94 }
95 //[0,bound)为有序区间
96 //[bound,size)为待排序区间
97 //插入排序把前面的有序区间当作线性表
98 //再将bound位置的元素插入到线性表的合适位置中
99 size_t bound = 1;
100 for(; bound < size ;++bound)
101 {
102 //此时存起来的意义是为了方便后面的搬运,
103 //一旦 array[bound]元素被单独保存起来
104 //array[bound]的值就可以被修改了
105 int bound_value = array[bound];
106 //从此处的cur是辅助我们进行搬运的下标会从后往前遍历,
107 //找到合适的位置,bound_value的位置
108 size_t cur = bound;
109 for(; cur > 0; --cur)
110 {
111 if(array[cur - 1] > bound_value)
112 {
113 //进行搬运
114 array[cur] = array[cur - 1];
115 }
116 else
117 {
118 //说明已经找到了合适的位置
119 break;
120 }
121 }
122 array[cur] = bound_value;
123 }
124 return ;
125 }
126
4.堆排序
思路:利用堆的性质,大堆(根节点为最大值),小堆(根节点为最小值),先将序列构建成二叉树,再通过移动每个子树来完成排序
详细移动过程见大佬博文
128 /*************************************
129 *
130 *堆排序
131 *时间复杂度为O(N*logN)
132 *空间复杂度为O(1)
133 *稳定性:不稳定排序
134 *
135 *************************************/
136 #if 1
137 void AdjustDown(int array[],size_t size,size_t index)
138 {
139 size_t parent = index;
140 size_t child = 2*parent+1;
141 while(child < size)
142 {
143 if(child+1 < size && array[child+1] > array[child])
144 {
145 child = child+1;
146 }
147 //经历了上面的判定之后,child就指向了左右子树中较大的那个
148 if(array[parent] < array[child])
149 {
150 Swap(&array[parent],&array[child]);
151 }
152 parent = child;
153 child = 2*parent+1;
154 }
155 return ;
156 }
157
158 //方法一:把新元素放到堆数组的末尾,上浮式(从前往后遍历)
159 void HeapCreate(int array[],size_t size)
160 {
161 if(size <= 1)
162 {
163 return ;
164 }
165 size_t i = (size-1-1)/2;
166 for(; i > 0;--i)
167 {
168 AdjustDown(array,size,i);
169 }
170 AdjustDown(array,size,0);
171 }
172 #endif
173
174
175 void AdjustUp(int array[],size_t size,size_t index)
176 {
177 (void)size;
178 size_t child = index;
179 size_t parent = (child - 1)/2;
180 while(child > 0)
181 {
182 if(array[parent] < array[child])
183 {
184 Swap(&array[parent],&array[child]);
185 }
186 else
187 {
188 break ;
189 }
190 child = parent;
191 parent = (child - 1)/2;
192 }
193 }
194
212 void HeapPop(int array[],size_t heap_size)
213 {
214 if(heap_size <= 1)
215 {
216 return ;
217 }
218 Swap(&array[0],&array[heap_size-1]);
219 AdjustDown(array,heap_size-1,0);
220 }
221
222 void HeapSort(int array[],size_t size)
223 {
224 if(size <= 1)
225 {
226 return ;
227 }
228 //1.基于数组建立一个堆(若是升序,用大堆)
229 HeapCreate(array,size);
230 //2.循环地删除堆顶元素,将所有元素删除完毕,排序完成
231 size_t i = 0;
232 for(; i < size-1 ; ++i)
233 {
234 //第二个参数表示数组中哪部分区间是符合堆地规则
235 //第一次删除前:[0,size)都是堆
236 //第二次删除前:[0,size-1)都是堆
237 //第三次删除前:[0,size-2)都是堆
238 HeapPop(array,size-1);//删除堆顶元素
239 }
240 }
5.Shell排序
主要是为了缩小移动步长,先将元进行分组,再在组内进行排序,再将各组进行合并,此时,再进行分组操作,再进行组内排序,再合并,这样将移动步长一步步缩小
详细过程见大佬博客
242 /*************************************
243 *
244 *Shell排序
245 *时间复杂度:取决于步长序列,对于希尔序列为O(N^2)
246 *若选择最优序列,时间复杂度最好能够达到O(N^1.3)
247 *空间复杂度为O(1)
248 *稳定性:不稳定排序
249 *
250 *************************************/
251 void ShellSort(int array[],int64_t size)
252 {
253 if(size <= 1)
254 {
255 return ;
256 }
257 int64_t gap = size/2;//此时使用希尔序列
258 for(; gap > 0;gap/=2)//第一重循环,进行插入排序,此循环执行的顺序相当于先处理第一组的第一个
259 {
260 int64_t bound = gap;//此处相当于插入排序中的bound=1
261 for(; bound < size ; ++bound)
262 {
263 //此时 bound_value 就是待插入元素
264 int bound_value = array[bound];
265 //第三重循环——线性表的查找和搬运(同插入排序)
266 int64_t cur = bound;
267 //此处 cur-=gap就是在找到同组元素的上一个元素
268 for(; cur >= gap;cur-=gap)
269 {
270 if(array[cur-gap] > bound_value)
271 {
272 //进行搬运
273 array[cur] = array[cur-gap];
274 }
275 else
276 {
277 break ;
278 }
279 }
280 array[cur] = bound_value;
281 }
282 }
283 return ;
284 }
285
6.归并排序
思路:先将每个子序列有序,再让子序列段间有序
详细见大佬博客
287 /*************************************
288 *
289 *归并排序(递归版本)
290 *时间复杂度为O(N*logN)
291 *空间复杂度为O(N)
292 *稳定性:稳定排序
293 *应用:用来归并链表
294 *
295 *************************************/
296 //合并过程
297 //第一个区间[begin,mid)
298 //第二个区间[mid,end)
299 void _MergeArray(int array[],int64_t begin,int64_t mid,int64_t end,int* tmp)
300 {
301 int64_t cur1 = begin;
302 int64_t cur2 = mid;
303 int64_t tmp_index = begin;//下标
304 while(cur1 < mid && cur2 < end)
305 {
306 if(array[cur1] < array[cur2])
307 {
308 tmp[tmp_index++] = array[cur1++];
309 //上面这条代码相当与下面的三条代码
310 //tmp_index[tmp_index] = array[cur1];
311 //++cur1;
312 //++tmp_index;
313 }
314 else
315 {
316 tmp[tmp_index++] = array[cur2++];
317 }
317 }
318 }
319 while(cur1 < mid)
320 {
321 tmp[tmp_index++] = array[cur1++];
322 }
323 while(cur2 < end)
324 {
325 tmp[tmp_index++] = array[cur2++];
326 }
327 memcpy(array + begin , tmp + begin , sizeof(int)*(end - begin));
328 }
329
330 //[begin,end)
331 void _MergeSort(int array[],int64_t begin,int64_t end,int* tmp)
332 {
333 if(end - begin <= 1)
334 {
335 //当前元素个数为0或者为1
336 return ;
337 }
338 //下面为(begin + end)/2的升级版本,防止溢出
339 int64_t mid = begin + (end - begin)/2;
340 //此时有两个区间
341 //[begin,mid)
342 //[mid,end)
343 _MergeSort(array,begin,mid,tmp);
344 _MergeSort(array,mid,begin,tmp);
345 //要先保证左右区间均为有序区间后才能进行合并
346 _MergeArray(array,begin,mid,end,tmp);
347 return ;
348 }
349
350 void MergeSort(int array[],int64_t size)
351 {
352 if(size <=1)
353 {
354 return ;
355 }
356 int * tmp = (int*)malloc(sizeof(int)*size);
357 //创建与原数组一样大的缓冲区临时空间,用来辅助完成合并元素
358 //使用一个函数来辅助完成递归操作
359 //[0,size)
360 _MergeSort(array,0,size,tmp);
361 free(tmp);
362 }
363
7.归并排序
364 /*************************************
365 *
366 *归并排序(非递归版本)
367 *时间复杂度为O(N*logN)
368 *空间复杂度为O(N)
369 *稳定性:稳定排序
370 *应用:用来归并链表
371 *
372 *************************************/
373 void MergeSortByLoop(int array[],int64_t size)
374 {
375 if(size <= 1)
376 {
377 return ;
378 }
379 int* tmp = (int*)malloc(sizeof(int)* size);
380 int64_t gap = 1;
381 for(; gap < size ; gap *= 2)
382 {
383 int64_t i = 0;
384 for(; i < size; i +=2 * gap)
385 {
386 //每次循环其实就是在处理两个相邻的区间
387 int64_t begin = i;
388 int64_t mid = i + gap;
389 int64_t end = i + 2 * gap;
390 if(mid > size)
391 {
392 mid = size;
393 }
394 if(end > size)
395 {
396 end = size;
397 }
398 //[begin,mid),[mid,end)
399 _MergeArray(array,begin,mid,end,tmp);
400 }
401 }
402 free(tmp);
403 }
404
8.快速排序
思路:取一个基准值(一般为第一个数),将小于基准值的数放在基准值左边,大于基准值的数放在基准值右边,此时左边的数都比中间的基准值小,右边的数都比中间的基准值大,再将左右两边的子序列按照这样的方式再排序
具体实现将小于基准值的数放在左边,大于基准值的数放在右边的方法,见大佬博客
405 /*************************************
406 *
407 *快速排序
408 *时间复杂度:最坏为O(N^2)序列为完全逆序
409 平均为O(N*logN)
410 *空间复杂度为O(logN)
411 *稳定性:不稳定排序
412 *
413 *************************************/
414 //快速排序的改进
415 //1.三值取中确定基准值
416 //2.当区间比较小的时候,就可以使用插入排序,直接对这个区间进行排序,从而有效的减少递归次数
417 //3.当递归深度达到一定的程度时,使用堆排序对待排序区间进行排序
418
419 //方法一:交换法
420 int64_t Partion1(int array[],int64_t begin,int64_t end)
421 {
422 //1.先定义好区间的边界
423 int64_t left = begin;
424 int64_t right = end - 1;
425 //2.取最后一个元素作为基准值key
426 int key = array[right];
427 while(left < right)
428 {
429 //3.从左到右找到一个大于基准值key的元素
430 while(left < right && array[left] <= key)
431 {
432 //要么 left 与 right 重合,要么找到了一个大于基准值key的元素,才最退出循环
433 ++left;
434 }
435 //4.从右往左找到一个小于基准值key的元素
436 while(left < right && array[right] > key)
437 {
438 //要么 left 与 right 重合,要么找到了一个小于基准值key的元素,才最退出循环
439 --right;
440 }
441 //5.进行交换
442 if(left < right)
443 {
444 Swap(&array[left],&array[right]);
445 }
446 }
447 //6.此时是将 left指向的值和最后一个元素(基准值)进行交换
448 //此时 left 指向的值一定大于基准值
449 // a)若是因为++left导致的循环退出,由于 right 在上一次循环的交换中已经指向了一个大于等于基准值的元素
450 // b)若是因为--right导致的循环退出,由于在刚刚的 left 查找过程中 left 已经找到了一个大于等于基准值的元素
451 // 因此,最终的结论是:left指向的值一定大于基准值
452 //因为此时 left 与 right 重合了,因此下面是写 left 还是 right 与最后一个元素交换都可以
453 Swap(&array[left],&array[end - 1]);
454 return left;
455
456 }
457
458 //方法二:挖坑法
459 int64_t Partion2(int array[],int64_t begin,int64_t end)
460 {
461 //1.先定义好边界
462 int64_t left = begin;
463 int64_t right = end - 1;
464 //2.取最后一个元素作为基准值
465 int key = array[right];
466 while(left < right)
467 {
468 //3.从左到右找到一个大于基准值
469 while(left < right && array[left] <= key)
470 {
471 ++left;
472 }
473 if(left < right)
474 {
475 //将找到的大于基准值的元素填到 right 指向的坑里
476 //随着填坑动作的完成,left指向的位置也就可以被别人覆盖
477 //left也就成为了一个坑
478 array[right--] = array[left];
479 }
480 //4.从右往左找到一个小于基准值
481 while(left < right && array[right] >= key)
482 {
483 --right;
484 }
485 if(left < right)
486 {
487 array[left++] = array[right];
488 }
489 }
490 array[left] = key;
491 return left;
492 }
493
494 //方法三:双指针前移法(仅供参考)
495 int64_t Partion3(int array[],int64_t begin,int64_t end)
496 {
497 int64_t cur = begin;
498 int64_t pre = begin - 1;
499 int key = array[end - 1];
500 while(cur < end)
501 {
502 if(array[cur] < key && ++pre != cur)
503 {
504 Swap(&array[cur],&array[pre]);
505 }
506 ++cur;
507 }
508 if(++pre != end)
509 {
510 Swap(&array[pre],&array[end - 1]);
511 }
512 return pre;
513 }
514
515 void _QuickSort(int array[],int64_t begin,int64_t end)
516 {
517 if(end - begin <= 1)
518 {
519 return ;
520 }
521 //Partion函数的作用,是对当前[begin,end)区间进行整理
522 //整理成以某个基准key为中心,左侧元素小于基准值
523 //右边元素大于基准值
524 //返回值表示的含义是基准值所在的下标
525 int64_t mid = Partion1(array,begin,end);
526 _QuickSort(array,begin,mid);
527 _QuickSort(array,mid + 1,end);
528 }
529
530 void QuickSort(int array[],int64_t size)
531 {
532 _QuickSort(array,0,size);
533 }
534
9.快速排序
535 /*************************************
536 *
537 *快速排序(非递归版本)
538 *借助一个棧
539 *时间复杂度:最坏为O(N^2)序列为完全逆序
540 平均为O(N*logN)
541 *空间复杂度为O(logN)
542 *稳定性:不稳定排序
543 *
544 *************************************/
545 void QuickSortByLoop(int array[],int64_t size)
546 {
547 if(size <= 1)
548 {
549 return ;
550 }
551 SeqStact stack;
552 SeqStactInit(&stack);
553 int64_t begin = 0;
554 int64_t end = size;
555 SeqStactPush(&stack,begin);
556 SeqStactPush(&stack,end);
557 while(stack.size > 0)//棧不为空
558 {
559 SeqStactTop(&stack,&end);
560 SeqStactPop(&stack);
561 SeqStactTop(&stack,&begin);
562 SeqStactPop(&stack);
563 if(end - begin <= 1)
564 {
565 continue;
566 }
567 int64_t mid = Partion1(array,begin,end);
568 SeqStactPush(&stack,begin);
569 SeqStactPush(&stack,mid);
570 SeqStactPush(&stack,mid + 1);
571 SeqStactPush(&stack,end);
572 }
573
574 }