当前位置:   article > 正文

C语言实现七大排序算法:插入、希尔、选择、堆排序、冒泡、快排、归并(史上最全)_编程实现直接插入排序,希尔排序,冒泡排序,快速排序,简单选择排序,堆排序,归并排序

编程实现直接插入排序,希尔排序,冒泡排序,快速排序,简单选择排序,堆排序,归并排序

1.插入排序

1.1思路:

   直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想:

1.2步骤:

1.从第一个元素开始,该元素可以认为已经被排序
2.取下一个元素tem,从已排序的元素序列从后往前扫描
3.如果该元素大于tem,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.tem插入到该元素的后面,如果已排序所有元素都大于tem,则将tem插入到下标为0的位置
6.重复步骤2~5

1.3代码:

  1. void InsertSort(int* a, int n)//直接插入法,升序算法
  2. {
  3. for (int i = 1; i < n; i++)
  4. {
  5. int tmp = a[i];//tmp表示待插入的数据
  6. int end = i - 1;//end为tmp前一个数据
  7. while (end >= 0)//end往前移动,一一判断
  8. {
  9. if (tmp >=a[end])//如果tmp更大,直接跳出循环
  10. break;
  11. if (tmp < a[end])//如果tmp更小,则end再往前移
  12. {
  13. a[end + 1] = a[end];
  14. end--;
  15. }
  16. }
  17. a[end + 1] = tmp;
  18. //代码执行到此位置有两种情况:
  19. //1.待插入元素找到应插入位置(break跳出循环到此)
  20. //2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
  21. }
  22. }

插入排序:时间复杂度:o(n^2);空间复杂度:o (1);

2.希尔排序

2.1思路:

希尔排序,先将待排序列进行预排序,使待排序列接近有序,然后再对该序列进行一次插入排序,此时插入排序的时间复杂度为O(N)。(希尔排序本质上是对直接插入排序的升级,即先经过预排序,使数据接近与有序)

2.2步骤:

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

2.3代码: 

  1. void ShellSort(int* a, int n)//希尔排序
  2. {
  3. int gap = n;
  4. while (gap > 1)
  5. {
  6. gap = gap / 3 + 1;//当gap等于1的时候实际上就是进行一遍快速排序
  7. //gap=gap/2;也可以
  8. for (int i = 0; i < n - gap; i++)
  9. {
  10. int tmp = a[i + gap];
  11. int end = i;
  12. while (end >= 0)
  13. {
  14. if (a[end] <= tmp)
  15. {
  16. break;
  17. }
  18. else
  19. {
  20. a[end + gap] = a[end];
  21. end -= gap;
  22. }
  23. }
  24. a[end + gap] = tmp;
  25. }
  26. }
  27. }

希尔排序:时间复杂度:o(n^1.3);空间复杂度:o(1);

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。
2. gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:
《数据结构(C语言版)》严蔚敏:

《数据结构-用面相对象方法与C++描述》--- 殷人昆

 3.选择排序

3.1思路:

每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。
实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

3.2步骤:

1.在元素集合 array[i]--array[n-1] 中选择关键码最大 ( ) 的数据元素
2.若它不是这组元素中的最后一个 ( 第一个 ) 元素,则将它与这组元素中的最后一个(第一个)元素交换
3.在剩余的 array[i]--array[n-2] array[i+1]--array[n-1] )集合中,重复上述步骤,直到集合剩余 1 个元素

3.3代码:

  1. void SelectSort(int* a, int left,int right)//选择排序,每次遍历同时选出最大值和最小值
  2. {
  3. int mini = left;
  4. int maxi = left;
  5. while (left < right)
  6. {
  7. for (int i = left; i <= right ; i++)
  8. {
  9. if (a[i] < a[mini])
  10. mini = i;
  11. if (a[i] > a[maxi])
  12. maxi = i;
  13. }
  14. Swap(&a[left], &a[mini]);//交换两个数的值
  15. if (left == maxi)//注意:但最左边元素是最大的元素时,此时交换right和maxi就会出错
  16. {
  17. maxi = mini;//让maxi重新指向最大的元素
  18. }
  19. Swap(&a[right], &a[maxi]);
  20. left++;
  21. right--;
  22. }
  23. }

 选择排序:时间复杂度:o(n^2);空间复杂度:o(1);

4.冒泡排序

4.1思路:

左边大于右边交换一趟排下来最大的在右边

4.2代码:

  1. void BubbleSort(int* a, int n)//冒泡排序
  2. {
  3. for (int i = 0; i < n; i++)
  4. {
  5. for (int j = 1; j < n - i; j++)
  6. {
  7. if (a[j] < a[j - 1])
  8. Swap(&a[j], &a[j - 1]);//交换两个数的值
  9. }
  10. }
  11. }

冒泡排序:时间复杂度:o(n^2);空间复杂度:o(1); 

5.堆排序

5.1思路:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。堆排序需要建立在对堆数据结构十分了解的基础上。

由于篇幅原因这里直接呈现代码,如果读者对堆不太熟悉的,我推荐大家去看这篇博客堆排序

 5.2代码:

  1. void AdjustDown(int* a, int n, int parent)//向下调整算法
  2. {
  3. int child = parent * 2 + 1;
  4. while (child < n)
  5. {
  6. if (child+1<n&&a[child + 1] > a[child])
  7. child++;
  8. if (a[child] > a[parent])
  9. {
  10. Swap(&a[child], &a[parent]);
  11. parent = child;
  12. child = parent * 2 + 1;
  13. }
  14. else
  15. break;
  16. }
  17. }
  18. void HeapSort(int* a, int n)
  19. {
  20. for (int i = (n - 1 - 1) / 2; i >= 0; i--)//建堆
  21. AdjustDown(a, n, i);
  22. int end = n - 1;
  23. while (end >= 0)//排序
  24. {
  25. Swap(&a[0], &a[end]);
  26. AdjustDown(a, end, 0);
  27. end--;
  28. }
  29. }

堆排序:时间复杂度o(n*logn);空间复杂度o(1); 

6.快速排序 

思路:

快速排序是 Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止 。快速排序大致上可以分为三种思路,也可以分为递归与非递归。

6.1hoare版本(左右指针法)

思路:
1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

代码: 

  1. int PartSort1(int* a, int begin, int end)
  2. {
  3. int keyi = begin;
  4. int key = a[begin];
  5. while (begin < end)
  6. {
  7. while (begin < end && a[end] >= key)
  8. end--;
  9. while (begin < end && a[begin] <= key)
  10. begin++;
  11. Swap(&a[end], &a[begin]);
  12. }
  13. Swap(&a[end], &a[keyi]);
  14. return end;
  15. }
  16. void QuickSort(int* a, int begin, int end)
  17. {
  18. if (begin >= end)
  19. return;
  20. int keyi=PartSort1(a, begin, end);
  21. QuickSort(a, begin, keyi - 1);
  22. QuickSort(a, keyi + 1, end);
  23. }

6.2打洞法

思路:
挖坑法思路与hoare版本(左右指针法)思路类似
1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑
2、还是定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走) 

代码: 

  1. int PartSort2(int* a, int begin, int end)
  2. {
  3. int key= a[begin];
  4. int hole = begin;
  5. while (begin < end)
  6. {
  7. while (begin < end && a[end] >= key)
  8. end--;
  9. a[hole] = a[end];
  10. hole = end;
  11. while (begin < end && a[begin] <= key)
  12. begin++;
  13. a[hole] = a[begin];
  14. hole = begin;
  15. }
  16. a[hole] = key;
  17. }
  18. void QuickSort(int* a, int begin, int end)
  19. {
  20. if (begin >= end)
  21. return;
  22. int keyi=PartSort2(a, begin, end);
  23. QuickSort(a, begin, keyi - 1);
  24. QuickSort(a, keyi + 1, end);
  25. }

6.3前后指针法

思路:
1、选出一个key,一般是最左边或是最右边的。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。

如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。

然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

 

代码: 

  1. int PartSort3(int* a, int begin, int end)
  2. {
  3. int key = a[begin];
  4. int prev = begin;
  5. int cur = begin + 1;
  6. while (cur <= end)
  7. {
  8. if (a[cur] < key)
  9. {
  10. prev++;
  11. Swap(&a[cur], &a[prev]);
  12. }
  13. cur++;
  14. }
  15. Swap(&a[begin], &a[prev]);
  16. return prev;
  17. }
  18. void QuickSort(int* a, int begin, int end)
  19. {
  20. if (begin >= end)
  21. return;
  22. int keyi=PartSort3(a, begin, end);
  23. QuickSort(a, begin, keyi - 1);
  24. QuickSort(a, keyi + 1, end);
  25. }

6.4非递归

思想:

非递归的思想可以借助数据结构栈来实现,我们仍然利用先前所写的partsort函数,三者思路均可。只是在QuickSort函数里面不使用递归。

代码:

  1. int PartSort1(int* a, int begin, int end)//这里我们利用第一种hoare思路
  2. {
  3. int keyi = begin;
  4. int key = a[begin];
  5. while (begin < end)
  6. {
  7. while (begin < end && a[end] >= key)
  8. end--;
  9. while (begin < end && a[begin] <= key)
  10. begin++;
  11. Swap(&a[end], &a[begin]);
  12. }
  13. Swap(&a[end], &a[keyi]);
  14. return end;
  15. }
  16. void QuickSortNonR(int* a, int begin, int end)
  17. {
  18. ST st;//ST是一个栈,建立一个栈
  19. InitStack(&st);//初始化栈
  20. PushStack(&st, end);//将右边界入栈
  21. PushStack(&st, begin);//将左边界入栈
  22. while (!StackEmpty(&st))//当栈不为空
  23. {
  24. int left = StackTop(&st);//栈顶元素为左边界
  25. PopStack(&st);//弹出栈顶元素
  26. int right = StackTop(&st);//栈顶元素为右边界
  27. PopStack(&st);//弹出栈顶元素
  28. int keyi = PartSort1(a, left, right);//进行一趟排序
  29. if (right > keyi + 1)
  30. {
  31. PushStack(&st, right);//再将右边区间入栈,类比递归的思想
  32. PushStack(&st, keyi + 1);
  33. }
  34. if (left < keyi - 1)
  35. {
  36. PushStack(&st, keyi - 1);//再将左边区间入栈
  37. PushStack(&st, left);
  38. }
  39. }
  40. DestroyStack(&st);//销毁栈
  41. }

7.归并排序

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

 

 

  1. void _MergeSort(int* a, int left, int right, int* tmp)//递归版本
  2. {
  3. /*if (left == right)
  4. return;*/
  5. if (right - left + 1 <= 10)//最小区间优化
  6. {
  7. QuickSort(a, left, right);
  8. return;
  9. }
  10. int mid = (left + right) / 2;//将区间一分为二
  11. _MergeSort(a, left, mid, tmp);
  12. _MergeSort(a, mid + 1, right, tmp);
  13. int start1 = left, end1 = mid;
  14. int start2 = mid + 1, end2 = right;
  15. int i = left;
  16. while (start1 <= end1 && start2 <= end2)
  17. {
  18. if (a[start1] <=a[start2])
  19. tmp[i++] = a[start1++];
  20. else
  21. tmp[i++] = a[start2++];
  22. }
  23. while (start1 <= end1)
  24. {
  25. tmp[i++] = a[start1++];
  26. }
  27. while (start2 <= end2)
  28. {
  29. tmp[i++] = a[start2++];
  30. }
  31. memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
  32. }
  33. void MergeSort(int* a, int n)
  34. {
  35. int* tmp = (int*)malloc(sizeof(int) * n);
  36. _MergeSort(a, 0, n - 1, tmp);
  37. free(tmp);
  38. }

排序复杂度汇总

 

 

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号