当前位置:   article > 正文

数据结构基本排序(c语言)_结构体排序c语言

结构体排序c语言

        在初步学完初阶数据结构的排序后,我发现了一个非常不得了的问题。那就是,在拿出一个排序的名字时(除了几个令人印象深刻的),我根本不能第一时间想起它们的思路啊!!!这就好比上厕所时,感觉要出来但又出不来一样令人难以接受。于是我准备写一篇博客来总结一下。

目录

1.冒泡排序

(1)思路

(2)代码实现 

2.插入排序

(1)思路

(2)下面来举个栗子。 

(3)代码实现

 3.希尔排序

(1)思路

代码优化(实现):

4.直接选择排序

思路:

代码实现:

5.堆排序

思路:

 画图举例:

代码实现:

6.快速排序

1,hoare版本

三数取中:(选取数组最左边数,最右边数,中间的数。三者中第二大的数)

2,挖坑法

 3,前后指针法

 快速排序主体(递归实现)

7.归并排序

思路(图解):

代码实现:


1.冒泡排序

        首先就是我们的老伙计了,相比较其他排序老伙计算是较容易实现的了。

(1)思路

        

思路: 从初始位置开始进行比较,也就是将arr[0]与arr[1]进行比较。如果arr[0]大于arr[1],则将两者值进行交换。然后将arr[1]与arr[2]进行比较........依此类推。每次比较完一轮后,就代表比较后最大的值到达了正确的位置,这时只需要将下轮要比较的总个数-1就行了。

(2)代码实现 

  1. //冒泡排序
  2. void BubbleSort(int* arr, int n)
  3. {
  4. for (int i = 0; i < n; i++)
  5. {
  6. int flag = 0;
  7. for (int j = 1; j < n - i; j++)//i的逐步增加实现了下轮比较总个数的减少
  8. {
  9. if (arr[j-1] > arr[j])
  10. {
  11. swap(&arr[j - 1], &arr[j]);
  12. flag = 1;
  13. }
  14. }
  15. if (0 == flag)//当比较完一轮后,flag==0,则说明数据已经有序了
  16. break;
  17. }
  18. }

2.插入排序

(1)思路

 思路:插入排序就像打扑克摸牌一样,摸完一张牌后,就把它插到比它小的牌和比它大的牌中间。

也就是说,假设[0,end]区间内数据是有序的,接下来插入end+1位置的数。

令tmp = arr[end+1]。然后从arr[end]开始到arr[0]依次与tmp进行比较。(即end--,直到end<0或者.新入数据已插入,为止)。

如果tmp小,则将与tmp进行比较的数据向后“移”。

如果tmp大,则arr[end+1] = tmp。 

(2)下面来举个栗子。 

arr[end]与tmp比较,5>3,所以5向后移。end--.

 

 同理4后移

 

直到2<3,tmp插入,即arr[ end+1] = tmp.

(3)代码实现

  1. //插入排序
  2. void insertSort(int* arr, int n)
  3. {
  4. for (int i = 0; i < n - 1; ++i)//多趟
  5. {
  6. //单趟
  7. int end = i;
  8. int tmp = arr[end + 1];
  9. while (end >= 0)
  10. {
  11. if (arr[end] > tmp)
  12. {
  13. arr[end + 1] = arr[end];
  14. --end;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. }
  21. arr[end + 1] = tmp;
  22. }
  23. }

 3.希尔排序

        希尔排序与插入排序思路是一样的,只是希尔排序在插排的基础上加了一层优化。(预排序)

        预排序的目的呢,当然就是使数据接近有序啦。

(1)思路

        那,预排序到底是什么东东呢。

其实就是将间隔为gap的数据分为一组,然后进行插入排序。

 如此,较大的数据就会更快地到后面,较小的数据就会更快地到前面。

代码实现:只需要在插入排序的基础之上稍加改动就行了。

  1. void ShellSort(int* arr, int n)
  2. {
  3. for (int gap = 3; gap > 0; gap--)
  4. {
  5. for (int j = 0; j < gap; ++j)
  6. {
  7. for (int i = j; i < n - gap; i += gap)
  8. {
  9. int end = i;
  10. int tmp = arr[end + gap];
  11. while (end >= 0)
  12. {
  13. if (arr[end] > tmp)
  14. {
  15. arr[end + gap] = arr[end];
  16. end -= gap;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. arr[end + gap] = tmp;
  24. }
  25. }
  26. }
  27. }

 但,看着上面的代码感觉循环太多了,令人很不爽。于是就有了下面的优化。

代码优化(实现):

  1. //希尔排序
  2. void ShellSort(int* arr, int n)
  3. {
  4. int gap = n;
  5. while(gap > 1)
  6. {
  7. gap = gap / 3 + 1;//使gap最终变为1
  8. for (int i = 0; i < n - gap; i++)//gap组数据依次多组并排
  9. {
  10. int end = i;
  11. int tmp = arr[end + gap];
  12. while (end >= 0)
  13. {
  14. if (arr[end] > tmp)
  15. {
  16. arr[end + gap] = arr[end];
  17. end -= gap;
  18. }
  19. else
  20. {
  21. break;
  22. }
  23. }
  24. arr[end + gap] = tmp;
  25. }
  26. }
  27. }

 优化思路

排完红色一组的第一个数据后,i++,去排绿组的第一个数据,再i++,去排蓝组的第一个数据,再i++,去排红组第二个数据.........

4.直接选择排序

思路:

 其实就是将数据一个一个一个地比较,选出最小的数据,放在最左边。既然,都选出了最小的,为什么我们不再选一个最大的放在最右边呢。这么一来,新的优化思路就出现啦!

 代码实现(有bug):

  1. void SelectSort(int* arr, int n)
  2. {
  3. int begin = 0, end = n - 1;
  4. while (begin < end)
  5. {
  6. //选出小的放begin
  7. //选出大的放end
  8. int max = begin, min = begin;
  9. for (int i = begin + 1; i <= end; i++)
  10. {
  11. if (arr[i] < arr[min])
  12. {
  13. min = i;
  14. }
  15. if (arr[i] > arr[max])
  16. {
  17. max = i;
  18. }
  19. }
  20. swap(&arr[begin], &arr[min]);
  21. swap(&arr[end], &arr[max]);
  22. ++begin;
  23. --end;
  24. }
  25. }

上面的代码虽然已经差不多实现出来了,但是还有一个非常容易忽略的坑。就是

当最大值正好在最左侧,min与最左侧值交换后,min就变成了所谓的“最大”。导致排序出错。这个坑只会在同时选max,min时出现,也就是在优化的思路中出现,如果只是简单地选出min放左边并不会受到影响。

 所以就需要对max进行修正。

代码实现:

  1. //选择排序
  2. void SelectSort(int* arr, int n)
  3. {
  4. int begin = 0, end = n - 1;
  5. while (begin < end)
  6. {
  7. //选出小的放begin
  8. //选出大的放end
  9. int max = begin, min = begin;
  10. for (int i = begin + 1; i <= end; i++)
  11. {
  12. if (arr[i] < arr[min])
  13. {
  14. min = i;
  15. }
  16. if (arr[i] > arr[max])
  17. {
  18. max = i;
  19. }
  20. }
  21. swap(&arr[begin], &arr[min]);
  22. //修正max
  23. if (max == begin)
  24. max = min;
  25. swap(&arr[end], &arr[max]);
  26. ++begin;
  27. --end;
  28. }
  29. }

5.堆排序

        堆排序与其他的排序还是有很大不同的,并且如果要理解堆排序,最最重要的方法就是画图啦。如果不画图,那就相当于把刀扔在旁边,直接用空手去劈榴莲。为了方便手不铁的我,所以接下来我就多用画图来解释了。

思路:

        思路:刚开始的数据是无序的,所以就需要将它们建成大堆或者小堆(以下使用大堆)

                   我们可以把父节点与子节点比较,确保最大的数位于父节点位置。

向下调整函数: 

  1. //向下调整(确保最大的数位于父节点位置)
  2. void Adjustdown(Type* arr, int sz, int parent)
  3. {
  4. int minchild = parent * 2 + 1;//初始为左孩子
  5. while (minchild < sz)
  6. {
  7. //取左右孩子中最大的
  8. if (arr[minchild] < arr[minchild + 1] && minchild + 1 < sz)
  9. {
  10. minchild++;//变为右孩子
  11. }
  12. if (arr[minchild] > arr[parent])
  13. {
  14. swap(&arr[parent], &arr[minchild]);
  15. parent = minchild;
  16. minchild = parent * 2 + 1;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. }

 因为叶子节点本身就是一个天然的堆,所以,只需要从倒数第一个非叶子节点(最后一个节点的父亲)开始调整。

  1. //建堆
  2. for (int i = (n - 1 - 1) / 2; i >= 0; i--)//倒数第一个非叶子节点(最后一个节点的父亲)开始调整
  3. { //由child = parent*2+1 推出parent = (child-1/2 即 (n-1 -1)/2
  4. Adjustdown(arr, n, i);
  5. }

以下一组数据{9,1,2,4,8,6,7,5,3}为例。 

(1)i = (n-1 -1)/2 = 3,所以将arr[3]与子节点比较,即4与5,3比较。因为5>4,所以4与5交换位置。

(2)i--, 所以将arr[2]与子节点比较,即2与6,7比较。因为7>6>2,所以2与7交换位置。

(3)同理,得到以下结果。

 

这么一来,大堆就建好啦。

然后是选数,将根节点位置的数与最后的叶子节点交换,再进行向下调整,确保根节点位置数在剩下的数中最大,最后size--,让最后一个排好的数不进入之后的排序。每次交换使得最大的数到达正确的位置,如此反复,堆排序就完成啦! 

  1. //选数
  2. int i = 1;
  3. while (i < n)
  4. {
  5. swap(&arr[0], &arr[n - i]);//每次交换使得最大的数到达正确的位置
  6. Adjustdown(arr, n - i, 0);
  7. i++;
  8. }

 画图举例:

交换根节点位置的数与最后叶子节点位置的数

向下调整

 

 交换根节点位置的数与最后叶子节点位置的数

向下调整

 

  交换根节点位置的数与最后叶子节点位置的数

向下调整

 

依此类推

 最终实现堆排序

代码实现:

  1. //堆排序
  2. void Heap_sort(int* arr, int n)
  3. {
  4. //建堆
  5. for (int i = (n - 1 - 1) / 2; i >= 0; i--)//倒数第一个非叶子节点(最后一个节点的父亲)开始调整
  6. { //由child = parent*2+1 推出parent = (child-1/2 即 (n-1 -1)/2
  7. Adjustdown(arr, n, i);
  8. }
  9. //选数
  10. int i = 1;
  11. while (i < n)
  12. {
  13. swap(&arr[0], &arr[n - i]);//每次交换使得最大的数到达正确的位置
  14. Adjustdown(arr, n - i, 0);
  15. i++;
  16. }
  17. }

6.快速排序

        从名字可以看出来,快速排序的主要特点就是,快!

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,之后又有一些大佬对其进行了优化,还有一些大佬又用别的思路将它实现了。所以,接下来有三种不同版本的快排实现思路。(啊啊啊,写死我啦)

1,hoare版本

思路:

 总的来说就是,R找比key小的数,找到之后,R不动。L开始行动找比key大的数,找到后,交换两数位置,R继续行动......两者相遇后将相遇位置的值与key交换。如此,6左边全是比6小的数,右边全是比6大的数。(以图中为例)即,6到达最终的位置。再用递归来实现快排(在最后快排主体)。

因为考虑到代码效率的问题,key使用三数取中的方法来选取的效率,比直接key=arr[0]的效率高。所以,下面我就使用三数取中了哦。 

三数取中:(选取数组最左边数,最右边数,中间的数。三者中第二大的数)

  1. //三数取中
  2. int GetMidIndex(int* arr, int left, int right)
  3. {
  4. int mid = (right + left) / 2;
  5. if (arr[mid] > arr[left])
  6. {
  7. if (arr[mid] < arr[right])
  8. {
  9. return mid;
  10. }
  11. else if (arr[left] > arr[right])
  12. {
  13. return left;
  14. }
  15. else
  16. {
  17. return right;
  18. }
  19. }
  20. else
  21. {
  22. if (arr[mid] > arr[right])
  23. {
  24. return mid;
  25. }
  26. else if (arr[left] < arr[right])
  27. {
  28. return left;
  29. }
  30. else
  31. {
  32. return right;
  33. }
  34. }
  35. }

代码实现:

  1. //快速排序hoare版本[left,right]
  2. int PartSort1(int* arr, int left, int right)
  3. {
  4. //三数取中
  5. int mid = GetMidIndex(arr, left, right);
  6. swap(&arr[mid], &arr[left]);
  7. int keyi = left;
  8. while (left < right)
  9. {
  10. //R找小
  11. while (left < right && arr[right] >= arr[keyi])//因为right在变化,所以要时时刻刻判断left<right
  12. {
  13. --right;
  14. }
  15. //L找大
  16. while (left < right && arr[left] <= arr[keyi])//因为left在变化,所以要时时刻刻判断left<right
  17. {
  18. ++left;
  19. }
  20. if (left < right)
  21. swap(&arr[left], &arr[right]);
  22. }
  23. swap(&arr[left], &arr[keyi]);
  24. //返回相遇点
  25. return left;
  26. }

2,挖坑法

思路:

挖坑法的思路与 hoare版本是相似的,R找比key大的数,填到左边的坑,形成新的坑位然后L行动,找比key小的数,填到右边的坑,形成新坑位......两者相遇后,把key填入相遇点。

代码实现:

  1. int PartSort2(int* arr, int left, int right)
  2. {
  3. // 三数取中
  4. int mid = GetMidIndex(arr, left, right);
  5. swap(&arr[left], &arr[mid]);
  6. int key = arr[left];
  7. int hole = left;
  8. while (left<right)
  9. {
  10. //右边找大,填左坑
  11. while (left < right && arr[right] >= key)//因为right在变化,所以要时时刻刻判断left<right
  12. {
  13. --right;
  14. }
  15. arr[hole] = arr[right];
  16. hole = right;
  17. //左边找小,填右坑
  18. while (left < right && arr[left] <= key)因为left在变化,所以要时时刻刻判断left<right
  19. {
  20. ++left;
  21. }
  22. arr[hole] = arr[left];
  23. hole = left;
  24. }
  25. arr[hole] = key;
  26. //返回相遇点
  27. return hole;
  28. }

 3,前后指针法

思路:

 初始时,prev指针指向序列开头,cur指针指向prev指针的后一个位置。

然后判断cur指针指向的数是否小于key,若小于,则prev指针后移一位,cur指向的内容与prev指向的内容交换(prev与cur在同一位置时不变),然后cur++。若大于,直接cur++。

当cur越界时将prev指向内容与key进行交换。

 代码实现:

  1. // 快速排序前后指针法
  2. int PartSort3(int* a, int left, int right)
  3. {
  4. // 三数取中
  5. int mid = GetMidIndex(a, left, right);
  6. swap(&a[left], &a[mid]);
  7. int key = a[left];
  8. int prev = left;
  9. int cur = left + 1;
  10. //cur找小,prev紧跟
  11. while (prev <= cur)
  12. {
  13. //cur甩开prev后,并遇到小时交换
  14. if (a[cur] < key && ++prev != cur)
  15. {
  16. swap(&a[prev], &a[cur]);
  17. }
  18. ++cur;
  19. }
  20. swap(&a[left], a[prev]);
  21. return prev;
  22. }

 快速排序主体(递归实现)

思路:

首先对初始数组排序使6到达最终位置再对6的左边数组进行排序使3到达最终位置再对6右边数组进行排序使8到达最终位置。再对3左边数组.........最终实现有序。(二叉树结构)

  1. //快速排序[begin,end]
  2. void QuickSort(int* arr, int begin, int end)
  3. {
  4. //为空或只有一个时返回
  5. if (begin >= end)
  6. return;
  7. if (end - begin <= 8)
  8. {
  9. insertSort(arr + begin, end - begin + 1);//当数组较小时,使用插入排序,提高效率
  10. }
  11. else
  12. {
  13. int keyi = PartSort1(arr, begin, end);//以上三种方法任选一个
  14. //
  15. QuickSort(arr, begin, keyi - 1);
  16. //
  17. QuickSort(arr, keyi + 1, end);
  18. }
  19. }

7.归并排序

思路(图解):

代码实现:

  1. //归并排序
  2. void mergeSort(int* arr, int begin, int end, int* tmp)
  3. {
  4. //分解
  5. //数组中数据个数为1或者0时返回
  6. if (begin >= end)
  7. return;
  8. int mid = (begin + end) / 2;
  9. //
  10. mergeSort(arr, begin, mid, tmp);
  11. //
  12. mergeSort(arr, mid + 1, end, tmp);
  13. //合并
  14. int begin1 = begin, end1 = mid;
  15. int begin2 = mid + 1, end2 = end;
  16. int i = begin;
  17. //将小的放入tmp数组
  18. while (begin1 <= end1 && begin2 <= end2)
  19. {
  20. if (arr[begin1] <= arr[begin2])
  21. tmp[i++] = arr[begin1++];
  22. else
  23. tmp[i++] = arr[begin2++];
  24. }
  25. //将大的尾插到tmp数组
  26. while (begin1 <= end1)
  27. {
  28. tmp[i++] = arr[begin1++];
  29. }
  30. while (begin2 <= end2)
  31. {
  32. tmp[i++] = arr[begin2++];
  33. }
  34. //将tmp拷贝回原来数组
  35. memcpy(arr + begin, tmp + begin, (end - begin + 1) * sizeof(int));
  36. }
  37. void MergeSort(int* arr, int n)
  38. {
  39. int* tmp = (int*)malloc(sizeof(int) * n);
  40. if (tmp == NULL)
  41. {
  42. printf("malloc");
  43. return;
  44. }
  45. mergeSort(arr, 0, n - 1, tmp);
  46. free(tmp);
  47. tmp = NULL;
  48. }

 啊!!!终于写完啦,最后写地脑壳疼·,如果大家发现了我哪里写错了欢迎斧正哦!文中动图来源于网络。

欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉!!!!!

木大木大木大木大木大木大木大木大木大木大!!!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/674178
推荐阅读
相关标签
  

闽ICP备14008679号