排序

 

下面写一些关于排序的一些基本方法:

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 }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值