天空一声巨响,迪宝闪亮登场。哈哈哈数据结构刷题又来了。实践是检验真理的唯一标准,最近感觉到自己的代码功底越来越深了,看来多写代码确实很有帮助。这次又带来最新的四道题目,来跟我一起去看看把。(顺便提一下,现在我在PTA上浙大《数据结构》这个题库已经排名1/2976了哦!!!)

排序1 排序

这次我们学习了很多高级的排序算法,主要有以下几种:

  • 快速排序
  • 表排序
  • 物理排序
  • 桶排序/基数排序

下面我将尝试用这几种方式对排序1问题进行不同的尝试:

表排序

表排序的话其实就是为每个交换项增加了一个关键字,交换方法还是沿用基本排序算法的。

物理排序

物理排序的话其实在下面Sort with Swap的题目中已有涉及,这种方法在数据范围和数组长度相同时可以几乎达到线性复杂度,这里就不再写了。

桶排序

桶排序或者基数排序的话是用一定的适用场景的(就是在适用场景下才能发挥出桶排序/基数排序的威力,其他情况当然也能用),主要就是用空间换时间,这里也不太适合使用基数排序。

快速排序

下面重点来讲讲这道题的快速排序。快速排序一共分为以下几个步骤:

  • 选主元(左中右取中位数)
  • 子集划分(对撞指针)
  • cutoff阈值(规模较小时直接使用简单排序算法)

快速排序一共用两种实现方式:

  • 直接调用库函数
  • 直接手撕一遍

下面我就来手撕一遍快速排序算法把!
此题代码如下:

  1. #include<iostream>
  2. #include<cmath>
  3. using namespace std;
  4. long int N;
  5. long int *a;
  6. void Median(int l,int r);
  7. int Subset(int l,int r);
  8. void Swap(long int&a,long int&b);
  9. void HSort(long int *H,int n);
  10. void Quicksort(int l,int r);
  11. int main(){
  12. cin >> N;
  13. a = new long int[N];
  14. for(int i = 0;i < N;i++)
  15. cin >> a[i];
  16. Quicksort(0,N-1);
  17. cout<<a[0];
  18. for(int i = 1;i < N;i++)
  19. cout << ' ' << a[i];
  20. return 0;
  21. }
  22. void Swap(long int&a,long int&b){
  23. long int temp = a;
  24. a = b;
  25. b = temp;
  26. }
  27. void Quicksort(int l,int r){
  28. if(l >= r)
  29. return;
  30. else if(r-l == 1){
  31. if(a[l] > a[r])
  32. Swap(a[l],a[r]);
  33. return;
  34. }
  35. //如果小于阈值就直接希尔排序
  36. if(r-l <= 10){
  37. HSort(a+l,r-l+1);
  38. return;
  39. }
  40. Median(l,r); //选主元
  41. int med = Subset(l+1,r-1); //子集划分
  42. Quicksort(l,med-1); //递归排序左子集
  43. Quicksort(med+1,r); //递归排序右子集
  44. }
  45. void Median(int l,int r){
  46. int c = (l+r) / 2;
  47. if(a[l] > a[c])
  48. Swap(a[l],a[c]);
  49. if(a[c] > a[r])
  50. Swap(a[r],a[c]);
  51. if(a[l] > a[c])
  52. Swap(a[l],a[c]);
  53. Swap(a[c],a[r-1]);
  54. }
  55. int Subset(int l,int r){
  56. int z1 = l;
  57. int z2 = r-1;
  58. int flag1 = 0;
  59. while(z1 < r && z2 < r){
  60. if(flag1 == 0){
  61. if(a[z1] > a[r])
  62. flag1 = 1;
  63. else
  64. z1++;
  65. }
  66. else{
  67. if(a[z2] < a[r]){
  68. if(z1 <= z2){
  69. Swap(a[z1],a[z2]);
  70. flag1 = 0;
  71. }
  72. else
  73. break;
  74. }
  75. else
  76. z2--;
  77. }
  78. }
  79. Swap(a[z1],a[r]);
  80. return z1;
  81. }
  82. void HSort(long int *H,int n){
  83. int count = 1;
  84. while((int)pow(2,count) - 1 < n)
  85. count++;
  86. count--;
  87. for(int i = count;i > 0;i--){
  88. int D = (int)pow(2,i) - 1;
  89. for(int j = 0;j < D;j++){
  90. for(int k = j;k < n;k += D){
  91. if(k == j)
  92. continue;
  93. long int t = H[k];
  94. for(int p = j;p < k;p += D){
  95. if(t < H[p]){
  96. for(int s = k;s > p;s -= D){
  97. H[s] = H[s-D];
  98. }
  99. H[p] = t;
  100. break;
  101. }
  102. }
  103. }
  104. }
  105. }
  106. }

阈值为10的结果如下所示:
image.png
阈值为30的结果如下所示:
image.png
阈值为70的结果如下所示:
image.png
可以发现,当阈值为30时,排序时间复杂度最小,但是这个也跟问题规模有关,当问题规模相对较小时,快速排序比较慢,这时候可以选择简单排序算法如希尔排序等。所以快速排序的方法可以归纳为:分而治之+对每个分好的子集使用简单排序算法。

统计工龄

这道题挺简单的,没什么难点。
此题代码如下:

  1. #include<iostream>
  2. using namespace std;
  3. long int N;
  4. long int *a;
  5. int main(){
  6. int temp;
  7. cin >> N;
  8. a = new long int[51];
  9. for(long int i = 0;i < N;i++){
  10. cin >> temp;
  11. a[temp]++;
  12. }
  13. for(int i = 0;i < 51;i++){
  14. if(a[i] != 0)
  15. cout<<i<<':'<<a[i]<<endl;
  16. }
  17. return 0;
  18. }

PAT Judge

这道题非常锻炼代码能力。主要的思路就是根据几个指标进行桶排序,或者叫基数排序,而且要采用“次位优先”的方法,还使用到了链表哈希表的相关知识和操作,可以说出的很不错。
此题代码如下:

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. int N,K;
  5. long int M;
  6. int *p;
  7. class Node2{
  8. public:
  9. string name;
  10. int ttr;
  11. int grade;
  12. int s[6];
  13. Node2(){
  14. ttr = 0;name = "";
  15. grade = 0;
  16. for(int i = 1;i <= K;i++)
  17. s[i] = -2;
  18. }
  19. Node2 &operator=(const Node2& n){
  20. if(this == &n)
  21. return *this;
  22. else{
  23. this->name = n.name;
  24. this->ttr = n.ttr;
  25. this->grade = n.grade;
  26. for(int i = 1;i < 6;i++)
  27. this->s[i] = n.s[i];
  28. return *this;
  29. }
  30. }
  31. bool operator==(const Node2& n){
  32. if(this->name != n.name || this->ttr != n.ttr || this->grade != n.grade)
  33. return false;
  34. for(int i = 1;i < 6;i++){
  35. if(this->s[i] != n.s[i])
  36. return false;
  37. }
  38. return true;
  39. }
  40. };
  41. class Node{
  42. private:
  43. Node2 value;
  44. Node* next;
  45. public:
  46. Node(){ next = NULL; }
  47. Node2 Getvalue() { return this->value; }
  48. Node* Getnext() { return this->next; }
  49. void Setvalue(Node2 num) { value = num; }
  50. void Setnext(Node* a) { next = a; }
  51. };
  52. class Nodearray{
  53. public:
  54. Node* head;
  55. Node* tail;
  56. Nodearray(){
  57. head = NULL;
  58. tail = NULL;
  59. }
  60. void Addnum(Node2 num){
  61. if (head == NULL){
  62. head = new Node;
  63. head->Setvalue(num);
  64. head->Setnext(NULL);
  65. tail = head;
  66. }
  67. else{
  68. tail->Setnext(new Node);
  69. (tail->Getnext())->Setvalue(num);
  70. (tail->Getnext())->Setnext(NULL);
  71. tail = tail->Getnext();
  72. }
  73. }
  74. void Delnum(Node2 num){
  75. Node* p1 = head;
  76. Node* p2 = head;
  77. if (head == NULL){
  78. cout << "这是空列表" << endl;
  79. return;
  80. }
  81. else{
  82. if (head->Getvalue() == num){
  83. head = head->Getnext();
  84. return;
  85. }
  86. else
  87. p2 = p2->Getnext();
  88. while (p2 != NULL){
  89. if (p2->Getvalue() == num){
  90. p1->Setnext(p2->Getnext());
  91. cout << "删除成功" << endl;
  92. return;
  93. }
  94. p1 = p1->Getnext();
  95. p2 = p2->Getnext();
  96. }
  97. }
  98. }
  99. };
  100. Node2 *a;
  101. Nodearray *b,*c,*d;
  102. int main(){
  103. cin >> N >> K >> M;
  104. a = new Node2[N+1];
  105. p = new int[K+1];
  106. int mf = 0;
  107. for(int i = 1;i <= K;i++){
  108. cin >> p[i];
  109. mf += p[i];
  110. }
  111. string temp;int k,g;
  112. for(int m = 0;m < M;m++){
  113. cin >> temp >> k >> g;
  114. if(a[stoi(temp)].name == "")
  115. a[stoi(temp)].name = temp;
  116. if(g > a[stoi(temp)].s[k]){
  117. if(a[stoi(temp)].s[k] >= 0)
  118. a[stoi(temp)].grade += g - a[stoi(temp)].s[k];
  119. else if(g >= 0)
  120. a[stoi(temp)].grade += g;
  121. a[stoi(temp)].s[k] = g;
  122. if(g == p[k])
  123. a[stoi(temp)].ttr++;
  124. }
  125. }
  126. //按照总分排序
  127. b = new Nodearray[K+1];
  128. c = new Nodearray[mf+1];
  129. int flag;
  130. for(int i = 1;i <= N;i++){
  131. if(a[i].name == "")
  132. continue;
  133. if(a[i].grade == 0){
  134. flag = 0;
  135. for(int j = 1;j <= K;j++){
  136. if(a[i].s[j] >= 0)
  137. break;
  138. if(j == K)
  139. flag = 1;
  140. }
  141. if(flag == 1)
  142. continue;
  143. }
  144. b[a[i].ttr].Addnum(a[i]);
  145. }
  146. //按照完全对题数排序
  147. Node *p;
  148. for(int i = K;i > -1;i--){
  149. p = b[i].head;
  150. while(p != NULL){
  151. c[(p->Getvalue()).grade].Addnum(p->Getvalue());
  152. p = p->Getnext();
  153. }
  154. }
  155. //按照ID排序
  156. Node *p2;
  157. int count = 1;int lastcount;int lastg = -1;
  158. for(int i = mf;i > -1;i--){
  159. p2 = c[i].head;
  160. while(p2 != NULL){
  161. if((p2->Getvalue()).grade != lastg){
  162. cout<<count<<' ';
  163. lastcount = count;
  164. }
  165. else
  166. cout<<lastcount<<' ';
  167. lastg = (p2->Getvalue()).grade;
  168. cout<<(p2->Getvalue()).name<<' '<<(p2->Getvalue()).grade;
  169. for(int j = 1;j <= K;j++){
  170. if((p2->Getvalue()).s[j] >= 0)
  171. cout<<' '<<(p2->Getvalue()).s[j];
  172. else if((p2->Getvalue()).s[j] == -1)
  173. cout<<" 0";
  174. else
  175. cout<<" -";
  176. }
  177. cout<<endl;
  178. count++;
  179. p2 = p2->Getnext();
  180. }
  181. }
  182. return 0;
  183. }

Sort with Swap

这道题就是赤裸裸的物理排序了,先想清楚答案是怎么计算得到的,再考虑一些特殊情况(比如一开始0就在索引为0的位置等),这道题帮助大家巩固表排序物理排序
此题代码如下:

  1. #include<iostream>
  2. using namespace std;
  3. long int N;
  4. long int *a;
  5. long int *vis;
  6. int main(){
  7. cin >> N;
  8. if(N == 1){
  9. cout<<'0';
  10. return 0;
  11. }
  12. a = new long int[N];
  13. vis = new long int[N];
  14. for(int i = 0;i < N;i++)
  15. vis[i] = 0;
  16. for(int i = 0;i < N;i++)
  17. cin >> a[i];
  18. int count = N-2;
  19. if(a[0] == 0)
  20. count += 2;
  21. for(int i = 0;i < N;i++){
  22. if(vis[i] == 0){
  23. if(a[i] == i){
  24. vis[i] = 1;
  25. count -= 1;
  26. continue;
  27. }
  28. int temp = i;
  29. while(a[temp] != i){
  30. vis[temp] = 1;
  31. temp = a[temp];
  32. }
  33. vis[temp] = 1;
  34. count++;
  35. }
  36. }
  37. cout<<count;
  38. return 0;
  39. }

总结

这次的刷题记录是排序专场,我们学习到了几种高级排序方法,各有千秋,强烈建议所有排序大家都手写一次,这样子以后用到的时候直接CV就行(这也是为自己的代码库增加新代码的方式嘛!)。通过这次刷题,我又获得了新的体悟:1)你现在写的代码有可能可以为将来节省不少时间,之后再遇到直接CV即可;2)在实现算法之前多想想,不要只是急于实现,刷题到这个地步时也要想想如何高效实现,包括时间空间复杂度和代码量等等
欢迎对ECE/CS/AI感兴趣的小伙伴关注我,如果你对我的内容有什么建议的话,或者你也对算法和数据结构感兴趣的话,可以单独找我讨论,也欢迎在评论区留下你的声音。