### Review This is my first time documenting the process of solving a homework problem. The main reason I started this project is to push myself to complete coding exercises more thoroughly—without relying on AI assistance. Although I wouldn't call it a full success, this attempt has brought some positive changes: I’ve reduced my dependency on AI and started practicing how to express my thoughts and logic clearly. This article may be a bit messy and casual, but to me, it's a big step toward facing my weaknesses and actively making a change. I'll do my best to continue writing tech notes like this. One step at a time. 這是我第一次紀錄自己解題的過程。 我之所以想開始這個計畫,是為了督促自己更扎實地完成程式練習,而且不依賴 AI 協助。 雖然這次的嘗試稱不上完全成功,但它確實帶來了一些正面的改變:我開始減少對 AI 的依賴,也開始練習如何清楚地表達自己的思考與邏輯。 這篇文章也許有點零碎、風格偏隨性,但對我來說,這是一個重要的起點,是我面對自己弱點、試著改變的一小步。 我會繼續努力寫出更多像這樣的技術筆記,一步一步慢慢來。 --- ## 題目敘述 工程師小金想比較不同的資料結構實作佇列 (Queue) 會有什麼差異,所以想請你幫忙實作具有 IQueue 介面的兩種不同的類別 LinkedQueue 與 ArrayQueue。請注意佇列是一種先入先出 (FIFO) 的資料集合,先加入 (Enqueue) 到佇列的元素會先從佇列被移除 (Dequeue): **實作 LinkedQueue 的建構子、複製建構子、賦值運算、解構子** ```=cpp LinkedQueue(); LinkedQueue(const LinkedQueue&); LinkedQueue& operator=(const LinkedQueue&); ~LinkedQueue(); ``` **實作 ArrayQueue 的建構子(不用實作複製建構子、賦值運算、解構子)** ```=cpp ArrayQueue(); ``` **實作 Empty 方法,回傳佇列是否為空** ```=cpp virtual bool Empty() const = 0; ``` **實作 Enqueue 方法,將元素 val 插入到佇列中** * 佇列是一種先入先出 (FIFO) 的資料集合,先加入 (Enqueue) 到佇列的元素會先從佇列被移除 (Dequeue): ```=cpp virtual void Enqueue(const T&) = 0; ``` **實作 Dequeue 方法,將一個元素從佇列移除** * 佇列是一種先入先出 (FIFO) 的資料集合,先加入 (Enqueue) 到佇列的元素會先從佇列被移除 (Dequeue): * 當佇列為空時,為《未定義行為》 ```=cpp virtual T Dequeue() = 0; ``` **實作 Peek 方法,回傳下一個會被移除的元素值** * 當佇列為空時,為《未定義行為》 ```=cpp virtual const T& Peek() const = 0; ``` 注意: T 不一定為 int,T 型態只保證提供預設建構與複製語意 --- **題目給予的內容** ```=cpp #include <iostream> #include <utility> #include <vector> #include <cassert> // For Test #include <random> // For Test #include <functional> // For Test template<typename T> struct IQueue { virtual ~IQueue() {} virtual bool Empty() const = 0; virtual void Enqueue(const T&) = 0; virtual T Dequeue() = 0; virtual const T& Peek() const = 0; }; template<typename T> struct ListNode { ListNode(T val) : val{std::move(val)}, next{nullptr} {} T val; ListNode* next; }; template<typename T> class LinkedQueue: public IQueue<T> { public: using ElemType = T; LinkedQueue(); LinkedQueue(const LinkedQueue&); LinkedQueue& operator=(const LinkedQueue&); ~LinkedQueue(); bool Empty() const; void Enqueue(const T&); T Dequeue(); const T& Peek() const; private: ListNode<T>* node_; }; template<typename T> class ArrayQueue : public IQueue<T> { public: using ElemType = T; ArrayQueue(); bool Empty() const; void Enqueue(const T&); T Dequeue(); const T& Peek() const; private: std::vector<T> data_; int a_; int b_; }; template<typename TQueue, typename = std::enable_if< std::is_base_of< IQueue<typename TQueue::ElemType>, TQueue>::value>> std::ostream& operator<<(std::ostream& os, const TQueue& p) { TQueue q = p; bool isFirst = true; os << '['; while (!q.Empty()) { if (isFirst) { isFirst = false; } else { os << ", "; } os << q.Dequeue(); } os << ']'; return os; } void Test1(); // Sample1 void Test2(); // LinkedQueue void Test3(); // LinkedQueue [Large Element] void Test4(); // ArrayQueue void Test5(); // ArrayQueue [Large Element] void Test6(); // ArrayQueue and LinkedQueue int main() { std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr); int id; std::cin >> id; void (*f[])() = { Test1, Test2, Test3, Test4, Test5, Test6 }; f[id-1](); } void Test1() { LinkedQueue<int> q1; std::cout << "01) " << q1 << std::endl; q1.Enqueue(3); std::cout << "02) " << q1 << std::endl; q1.Enqueue(5); std::cout << "03) " << q1 << std::endl; q1.Enqueue(7); std::cout << "04) " << q1 << std::endl; std::cout << "05) " << q1.Dequeue() << std::endl; std::cout << "06) " << q1.Peek() << std::endl; q1.Enqueue(9); std::cout << "07) " << q1 << std::endl; ArrayQueue<int> q2; std::cout << "08) " << q2 << std::endl; q2.Enqueue(3); std::cout << "09) " << q2 << std::endl; q2.Enqueue(5); std::cout << "10) " << q2 << std::endl; q2.Enqueue(7); std::cout << "11) " << q2 << std::endl; std::cout << "12) " << q2.Dequeue() << std::endl; std::cout << "13) " << q2.Peek() << std::endl; q2.Enqueue(9); std::cout << "14) " << q2 << std::endl; } namespace Feis { } void Test2() { } void Test3() { } void Test4() { } void Test5() { } void Test6() { } // [YOUR CODE WILL BE PLACED HERE] ``` --- # 實作紀錄 (以下有些雜亂但應該有些幫助) # First Attempt 2025/4/9下午2.~3.: 一開始忘記怎麼用Single Linked,找回記憶後就好了。 array的部分則沒太多問題,昨天上課有提到如何利用兩個位置標示a_、b_來表示陣列的前後,在實作的時候記得要根據動作調整這二者的位置即可。(到後面發現根本好像不是這樣用) ![image](https://hackmd.io/_uploads/HknOuuoRyx.png) (流程大概是這樣) 不過大概卡了十分鐘在q1.Enquene不知道為甚麼只能跑到 2) ,後來發現是我根本沒寫好 copy constructor,根本就在shallow copy(淺複製,意指只有複製指向開頭的指標)。改成Deep Copy(深複製,也就是完整的複製整組串列的內容)就解決了。 ### Deep Copy 實作(非 Circular) ``` =cpp //幫你貼心的把Struct放在這 /* struct ListNode { ListNode(T val) : val{std::move(val)}, next{nullptr} {} T val; ListNode* next; }; */ // Deep Copy Implement template<typename T> LinkedQueue<T>::LinkedQueue(const LinkedQueue& other) { if (other.node_ == nullptr) { node_ = nullptr; } else { // 建立新的第一個節點 node_ = new ListNode<T>(other.node_->val); ListNode<T>* currentSrc = other.node_->next; ListNode<T>* currentDst = node_; while (currentSrc != nullptr) { //接著不斷移動複製直到尾 currentDst->next = new ListNode<T>(currentSrc->val); currentDst = currentDst->next; currentSrc = currentSrc->next; } } } //這個時候還沒改成 circular linked quene ``` ### Outcome First Attempt: TLE(in 2 4 5 6) get 8 point. --- # Second Attempt > D1 原本 2 TLE, 56 MLE,把 node_ 改成 tail,然後讓它指向 head 解決 TEL,但因為有循環實作上要小心一點(destructor 要先斷開循環)。 另外是當 a_超過 vector 大小的一半(vector 前半的東西都已經 dequeue 掉了)就把前面的東西清掉,解決 MLE。 討論區上的推薦修正方向,感謝我的強大同學們。 ## LinkedQuene > 將原本從頭開始的LinkedQuene,改成 Circular 的方式。 也就是將 node_ 指向Tail,並將 Tail 的 next 指向Head。 ![image2](https://hackmd.io/_uploads/r1xXt_oRye.png) ### Copy Constructor 和 Destructor 前者(Copy Constructor)的重點是「取得原始列的Head」: 1. 取得原始列的Head 2. 繼續向後複製直到 Tail (直到發現 → next = head_) 3. 把 newTail->next = newHead,再將原本的 node_ = newTail 後者(Destructor)的重點是「如何拆開循環」: 1. 取得第一個元素位置 2. 把尾端指向Nullptr 3. 接著從頭開始刪除 ### Copy constructor 實作 ``` =cpp //幫你貼心的把Struct放在這 /* struct ListNode { ListNode(T val) : val{std::move(val)}, next{nullptr} {} T val; ListNode* next; }; */ // Copy constructor:複製另一個 circular linked list template<typename T> LinkedQueue<T>::LinkedQueue(const LinkedQueue& other) { if (other.node_ == nullptr) { node_ = nullptr; } else { // 取得原始隊列的 head (tail->next) ListNode<T>* origHead = other.node_->next; // 建立新鏈結的第一個節點 (newHead) ListNode<T>* newHead = new ListNode<T>(origHead->val); ListNode<T>* newTail = newHead; // 從原始 head 的下一個開始複製,直到回到 origHead for (ListNode<T>* current = origHead->next; current != origHead; current = current->next) { newTail->next = new ListNode<T>(current->val); newTail = newTail->next; } // 完成 circular 連結:newTail->next 指向 newHead newTail->next = newHead; // 將本物件的 tail 設為 newTail node_ = newTail; } } ``` ### Destructor 實作 ``` =cpp //新的Destructor template<typename T> LinkedQueue<T>::~LinkedQueue() { if (node_ != nullptr) { // 取得 head(佇列第一個元素) ListNode<T>* head = node_->next; // 斷開環狀連結,讓 tail->next = nullptr node_->next = nullptr; // 依序刪除所有節點 while (head != nullptr) { ListNode<T>* temp = head; head = head->next; delete temp; } } } ``` >(看到這個註解有GPT的味,沒錯你想的是對的) ## Enquene & Dequene 這兩個這我認為較為單純。 前者(Enquene) 1. val 創建一個新的 newnode_ 2. 將 newnode_→next 指向 head(也就是目前node_(Tail)→ next) 3. node_->next = newnode 4. node_ = newnode 後者(Dequene) 1. 利用 tail_ → next取得 head_ 並存起來 2. tail_→ next = head_ → next 3. delete head_ 並回傳值 ### Enqueue 實作 ``` =cpp //幫你貼心的把Struct放在這 /* struct ListNode { ListNode(T val) : val{std::move(val)}, next{nullptr} {} T val; ListNode* next; }; */ // Enqueue:在 circular list 中插入新節點 template<typename T> void LinkedQueue<T>::Enqueue(const T& val) { // 空隊列情形:建立一個新節點,讓其 next 指向自己 if (node_ == nullptr) { node_ = new ListNode<T>(val); node_->next = node_; return; } // 非空:建立新節點,令新節點->next 指向原 head (即 node_->next) ListNode<T>* newNode = new ListNode<T>(val); newNode->next = node_->next; node_->next = newNode; // 更新 tail:newNode 成為新的 tail node_ = newNode; } ``` ### Dequeue 實作 ``` =cpp // Dequeue:取出 head 元素 template<typename T> T LinkedQueue<T>::Dequeue() { assert(node_ != nullptr && "Dequeue called on empty queue."); // head 為 tail->next ListNode<T>* head = node_->next; T ans = head->val; // 若只有一個元素,刪除後設為空 if (node_ == head) { delete head; node_ = nullptr; } else { node_->next = head->next; delete head; } return ans; } ``` Reference: https://hackmd.io/@hank20010209/B1TkvWYO_ --- ## ArrayQuene ``` =cpp std::vector<T> data_; //存資料 int a_; //存開頭的位置 int b_; //陣列最大容量 ``` 原本那個方法不是不行,但會TLE(Time Limit Exceed),所以改了新方法 此部分則是比較單純,幾個原則: 1. 如果目前元素數量 > 陣列最大容量:將容量翻倍,並將原本的內容搬遷過去 2. 一樣用 a_ 指向第一個元素(Head_) ``` =cpp //幫你貼心的把Struct放在這 /* std::vector<T> data_; int a_; int b_; */ template<typename T> void ArrayQueue<T>::Enqueue(const T& val) { // 當陣列已滿,進行擴充(通常以容量翻倍) if (b_ == data_.size()) { std::vector<T> newData(data_.size() * 2); // 按照正確的順序搬移現有元素 for (size_t i = 0; i < b_; i++) { newData[i] = data_[(a_ + i) % data_.size()]; } data_ = std::move(newData); a_ = 0; } // 新元素插入的位置:(head_ + count_) mod capacity size_t tail = (a_ + b_) % data_.size(); data_[tail] = val; b_++; } template<typename T> T ArrayQueue<T>::Dequeue() { // 取出位於 head_ 的元素 T ans = data_[a_]; // 更新 head_ 指向下一個位置(模運算保證循環) a_ = (a_ + 1) % data_.size(); b_--; return ans; } ``` ### Outcome Second Attempt: AC.