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...

正好提到樹了,下一篇就寫樹吧。

相關文章
相關標籤/搜索