PAT甲级题分类汇编——理论

本文为PAT甲级分类汇编系列文章。html

 

理论这一类,是让我以为特别尴尬的题,纯粹是为了考数据结构而考数据结构。看那Author一栏清一色的某老师,就知道教数据结构的老师的思路就是和别人不同。node

题号 标题 分数 大意 Author
1051 Pop Sequence 25 判断一个序列是不是pop序列 CHEN, Yue
1052 Linked List Sorting 25 链表排序 CHEN, Yue
1057 Stack 30 一个有中位数功能的stack CHEN, Yue
1074 Reversing Linked List 25 分段逆转链表 CHEN, Yue
1089 Insert or Merge 25 判断插入排序或归并排序 CHEN, Yue
1097 Deduplication on a Linked List 25 链表去重 CHEN, Yue
1098 Insertion or Heap Sort 25 判断插入排序或堆排序 CHEN, Yue

好几道题在上MOOC的时候就在数据结构题集里面作过,有105一、1074和1089。还有1098,是以前作merge那道的时候想一并作的,但后来由于merge花了太多时间就跳过了。ios

此次讲107四、109八、1051和1057 4道题。没错,不按题号顺序。算法

 

1074:编程

这多是我作的第一道PAT题,也多是第一道卡住的题。其实,全部用5位整数来模拟链表的题,好像都必须建立100000大小的数组,直接寻址才能跑进时间限制, std::map 是不行的。不过呢,若是我一开始是用C作的,确定就直接建数组了,也就没有后面的问题了。数组

这道题的讲解在数据结构课程里有。数据结构

由于是早期做品,因此代码长得比较尴尬,懒得改了,我想睡觉。编程语言

 1 #include <iostream>
 2 #include <vector>
 3 #include <utility>
 4 #include <algorithm>
 5 #include <type_traits>
 6 #include <stack>
 7 #include <iomanip>
 8 
 9 struct Node
10 {
11     int address;
12     int data;
13     int next;
14 };
15 
16 int main(int argc, char const *argv[])
17 {
18     int first, n, k;
19     std::cin >> first >> n >> k;
20     std::vector<Node> data(n);
21     for (int i = 0; i != n; ++i)
22         std::cin >> data[i].address >> data[i].data >> data[i].next;
23     
24     decltype(data) list;
25     int address = first;
26     for (int i = 0; i != n; ++i)
27     {
28         auto pos = std::find_if(std::begin(data), std::end(data), [address](const Node& node) {
29             return node.address == address;
30         });
31         list.emplace_back(*pos);
32         address = pos->next;
33         if (address == -1)
34             break;
35     }
36     n = list.size();
37 
38     auto begin = list.begin();
39     auto end = begin + (list.end() - begin) / k * k;
40     for (; begin != end; begin += k)
41     {
42         std::stack<Node> stack;
43         for (auto iter = begin; iter != begin + k; ++iter)
44             stack.push(*iter);
45         for (auto iter = begin; iter != begin + k; ++iter)
46         {
47             *iter = stack.top();
48             stack.pop();
49         }
50     }
51     for (int i = 0; i != n - 1; ++i)
52         list[i].next = list[i + 1].address;
53     list[n - 1].next = -1;
54     
55     std::cout << std::setfill('0');
56     for (auto& node : list)
57     {
58         std::cout << std::setw(5) << node.address << ' ';
59         std::cout << std::setw(0) << node.data << ' ';
60         if (node.next == -1)
61         {
62             std::cout << "-1";
63             break;
64         }
65         std::cout << std::setw(5) << node.next << std::endl;
66     }
67 
68     return 0;
69 }

 

1098:函数

它的兄弟题目1089在数据结构课程中讲过,包括这道题中也用到的判断插入排序的方法。spa

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 
 5 int main()
 6 {
 7     int size;
 8     std::cin >> size;
 9     std::vector<int> original(size);
10     std::vector<int> partial(size);
11     for (auto& i : original)
12         std::cin >> i;
13     for (auto& i : partial)
14         std::cin >> i;
15 
16     int cur = 1;
17     for (; cur != size && partial[cur] >= partial[cur - 1]; ++cur)
18         ;
19     bool insertion = true;
20     for (auto i = cur; i != size; ++i)
21         if (partial[i] != original[i])
22         {
23             insertion = false;
24             break;
25         }
26     
27     if (insertion)
28     {
29         std::cout << "Insertion Sort" << std::endl;
30         int insert = partial[cur];
31         for (--cur; cur >= 0; --cur)
32             if (partial[cur] > insert)
33                 partial[cur + 1] = partial[cur];
34             else
35                 break;
36         partial[cur + 1] = insert;
37     }
38     else
39     {
40         std::cout << "Heap Sort" << std::endl;
41         int cur = size - 1;
42         auto top = partial[0];
43         for (; cur >= 0; --cur)
44             if (partial[cur] <= top)
45                 break;
46         if (cur >= 0)
47         {
48             std::pop_heap(partial.begin(), partial.begin() + cur + 1);
49             partial[cur] = top;
50         }
51     }
52     auto end = partial.end() - 1;
53     for (auto iter = partial.begin(); iter != end; ++iter)
54         std::cout << *iter << ' ';
55     std::cout << *end;
56 }

其实这道还要稍微简单一点,由于那道题要本身写merge,这道题的heap能够用标准库。<algorithm> 提供了 std::pop_heap 等函数用于堆操做。至于查看堆顶元素,把起始迭代器解引用就好,标准库没有给。

BTW,建堆的操做是O(N)的,证实起来挺组合数学的。

还有呢,我感受这两道题贼尴尬。

 

1051:

判断一个序列是否是一个stack中pop出来的序列。

 1 #include <iostream>
 2 #include <vector>
 3 #include <stack>
 4 
 5 class Stack : public std::stack<int>
 6 {
 7     using super = std::stack<int>;
 8 public:
 9     explicit Stack(int _cap)
10         : capacity_(_cap)
11     {
12         ;
13     }
14     void push(const int& _data)
15     {
16         if (size() == capacity_)
17             throw 0;
18         super::push(_data);
19     }
20 private:
21     int capacity_;
22 };
23 
24 void check(int _cap, const std::vector<int>& _data)
25 {
26     Stack stack(_cap);
27     int pushed = 0;
28     for (auto i : _data)
29     {
30         if (stack.empty())
31         {
32             stack.push(++pushed);
33         }
34         if (stack.top() < i)
35         {
36             for (int j = pushed + 1; j <= i; ++j)
37                 stack.push(j);
38             pushed = i;
39         }
40         if (stack.top() == i)
41         {
42             stack.pop();
43             continue;
44         }
45         if (stack.top() > i)
46             throw 0;
47     }
48 }
49 
50 int main()
51 {
52     int m, n, k;
53     std::cin >> m >> n >> k;
54     std::vector<int> data(n);
55     for (int i = 0; i != k; ++i)
56         try
57         {
58             for (int j = 0; j != n; ++j)
59                 std::cin >> data[j];
60             check(m, data);
61             std::cout << "YES" << std::endl;
62         }
63         catch(...)
64         {
65             std::cout << "NO" << std::endl;
66         }
67 
68     return 0;
69 }

题目不难,这里把代码贴出来,是想做个错误示范。算法自己是正确的,能AC,可是设计是很差的:标准库中的容器类不是用来做基类的,由于其析构函数非虚,我写的 Stack 类直接继承 std::stack<int> 是很差的,尽管在这个类设计中,子类的subclass不须要析构,并且整个程序没有做什么派生类到基类的指针转换。个人本意是想实现adapter,为了偷懒就直接公有继承,理应在 Stack 类中包装一个 std::stack<int> 实例。在工程中,公有继承标准库容器就是错误的。

 

1057:

此次不按题号讲,就是由于这道题要压轴。

初看,不就一个stack和中位数吗,两个我都会写,放到一块儿我也会写。而后我就写了第一个版本,作了一个 std::stack<int> 的adapter,提供了push、pop、median操做,其中median是把stack拷贝、排序、寻址中位数。感受很好,样例也过了,想着一遍AC。

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <algorithm>
 5 
 6 class Stack
 7 {
 8 public:
 9     Stack() = default;
10     void push(int i)
11     {
12         stack.push_back(i);
13     }
14     void pop()
15     {
16         if (stack.empty())
17             std::cout << "Invalid";
18         else
19         {
20             std::cout << stack.back();
21             stack.pop_back();
22         }
23         std::cout << std::endl;
24     }
25     void median()
26     {
27         if (stack.empty())
28             std::cout << "Invalid";
29         else
30         {
31             auto temp = stack;
32             std::sort(temp.begin(), temp.end());
33             int m = 0;
34             if (temp.size() % 2)
35                 m = temp[(temp.size() - 1) / 2];
36             else
37                 m = temp[temp.size() / 2 - 1];
38             std::cout << m;
39         }
40         std::cout << std::endl;
41     }
42 private:
43     std::vector<int> stack;
44 };
45 
46 int main()
47 {
48     int count;
49     std::cin >> count;
50     Stack stack;
51     for (int i = 0; i != count; ++i)
52     {
53         std::string instr;
54         std::cin >> instr;
55         if (instr == "Push")
56         {
57             int i;
58             std::cin >> i;
59             stack.push(i);
60         }
61         else if (instr == "Pop")
62         {
63             stack.pop();
64         }
65         else
66         {
67             stack.median();
68         }
69     }
70 }

然而并无。5个case,3个超时。

是 std::cin 读取字符串太慢了吗?换成C的输入输出,还有2个case超时。

而后我想到输入数据有个比较小的范围是有用的,就建了个数组,写了个乱七八糟的访问控制来防止每次遍历都把每一个元素访问一遍。没用,超时。

我意识到线性结构不能解决这个问题,否则也对不起这30分了。因而我就想到用 std::map 来存储。先放代码:

  1 #include <iostream>
  2 #include <string>
  3 #include <stack>
  4 #include <map>
  5 
  6 class Median
  7 {
  8 public:
  9     Median() = default;
 10     int get()
 11     {
 12         return median;
 13     }
 14     void insert(int i)
 15     {
 16         if (median_count == 0)
 17         {
 18             median = i;
 19             median_count = 1;
 20         }
 21         else if (i < median)
 22             ++small[i], ++small_count;
 23         else if (i > median)
 24             ++large[i], ++large_count;
 25         else
 26             ++median_count;
 27         adjust();
 28     }
 29     void erase(int i)
 30     {
 31         if (i < median)
 32         {
 33             --small[i], --small_count;
 34             if (small[i] == 0)
 35                 small.erase(i);
 36         }
 37         else if (i > median)
 38         {
 39             --large[i], --large_count;
 40             if (large[i] == 0)
 41                 large.erase(i);
 42         }
 43         else
 44             --median_count;
 45         adjust();
 46     }
 47 private:
 48     std::map<int, int, std::greater<int>> small;
 49     int small_count = 0;
 50     std::map<int, int> large;
 51     int large_count = 0;
 52     int median;
 53     int median_count = 0;
 54     void small_to_median()
 55     {
 56         median = small.begin()->first;
 57         median_count = small.begin()->second;
 58         small.erase(small.begin());
 59         small_count -= median_count;
 60     }
 61     void large_to_median()
 62     {
 63         median = large.begin()->first;
 64         median_count = large.begin()->second;
 65         large.erase(large.begin());
 66         large_count -= median_count;
 67     }
 68     void median_to_small()
 69     {
 70         small[median] = median_count;
 71         small_count += median_count;
 72     }
 73     void median_to_large()
 74     {
 75         large[median] = median_count;
 76         large_count += median_count;
 77     }
 78     void adjust()
 79     {
 80         if (median_count == 0)
 81         {
 82             if (small_count < large_count)
 83                 large_to_median();
 84             else if (small_count)
 85                 small_to_median();
 86         }
 87         else if (small_count + median_count < large_count)
 88         {
 89             median_to_small();
 90             large_to_median();
 91         }
 92         else if (small_count >= median_count + large_count)
 93         {
 94             median_to_large();
 95             small_to_median();
 96         }
 97     }
 98 };
 99 
100 class Stack
101 {
102 public:
103     Stack() = default;
104     void push(int i)
105     {
106         stack_.push(i);
107         median_.insert(i);
108     }
109     void pop()
110     {
111         if (stack_.empty())
112             std::cout << "Invalid";
113         else
114         {
115             int i = stack_.top();
116             stack_.pop();
117             std::cout << i;
118             median_.erase(i);
119         }
120         std::cout << std::endl;
121     }
122     void median()
123     {
124         if (stack_.empty())
125             std::cout << "Invalid";
126         else
127         {
128             std::cout << median_.get();
129         }
130         std::cout << std::endl;
131     }
132 private:
133     std::stack<int> stack_;
134     Median median_;
135 };
136 
137 int main()
138 {
139     int count;
140     std::cin >> count;
141     Stack stack;
142     for (int i = 0; i != count; ++i)
143     {
144         std::string instr;
145         std::cin >> instr;
146         if (instr == "Push")
147         {
148             int i;
149             std::cin >> i;
150             stack.push(i);
151         }
152         else if (instr == "Pop")
153             stack.pop();
154         else
155             stack.median();
156     }
157 }

这个算法挺复杂的。客户维护一个 Stack 实例,它维护一个 Median 实例,两个都是我本身写的类。Median 中包含两个 std::map<int, int> 实例,分别储存比中位数小的和比中位数大的数。核心算法是 Median::adjust() ,它经过调整两边内容,维护中位数的正确性。不想细说了,看代码吧,只涉及到 std::map 的一些基本操做。

由于两次调整之间只有一个操做,因此能够保证调整一次就行了。

值得吐槽的一点是,C++的输入输出在这道题中比C慢了100ms。然而,算法不对,输入输出再快,也要超时。PAT好像没什么卡输入输出时间的题,毕竟世上除了C和C++还有好多编程语言,要考虑它们的感觉。

 

写完了发现其余博客里没有用这么烦的方法,好像用了什么树状数组,我不会。

 1 try
 2 {
 3     learn_queue.push("树状数组");
 4 }
 5 catch(...)
 6 {
 7     std::cout << "快去睡觉" << std::endl;
 8 }
 9 
10 --------------------------------------------------------------------------------
11 快去睡觉
12 Program exited with code 0...

正好提到树了,下一篇就写树吧。

相关文章
相关标签/搜索