# 資料結構 (Data Structure) > 2018.1.11 AndyLee <style> .align-right { text-align: right; } .align-center { text-align: center; color: blue; } </style> ## Array ### 優點: * random access:只要利用index即可在O(1)時間對Array的資料做存取。 * 較Linked list為節省記憶體空間:因為Linked list需要多一個pointer來記錄下一個node的記憶體位置。 ### 缺點: * 新增/刪除資料很麻煩:若要在第一個位置新增資料,就需要O(N)時間把矩陣中所有元素往後移動。同理,若要刪除第一個位置的資料,也需要O(N)時間把矩陣中剩餘的元素往前移動。 * 若資料數量時常在改變,要時常調整矩陣的大小,會花費O(N)的時間在搬動資料(把資料從舊的矩陣移動到新的矩陣)。 ### 適用時機: * 希望能夠快速存取資料。 * **已知欲處理的資料數量**,便能確認矩陣的大小。 * 要求記憶體空間的使用越少越好。 ## Linked list ### 優點: * 新增/刪除資料較Array簡單,只要對O(1)個node(所有與欲新增/刪除的node有pointer相連的node)調整pointer即可,不需要如同Array般搬動其餘元素。 * 若是在Linked list的「開頭」新增node,只要O(1)。 * 若要刪除特定node,或者在特定位置新增node,有可能需要先執行O(N)的「搜尋」。 * Linked list的資料數量可以是動態的,不像Array會有resize的問題。 ### 缺點: * 因為Linked list沒有index,若要找到特定node,需要從頭(ListNode *first)開始找起,搜尋的時間複雜度為O(N)。 * 需要額外的記憶體空間來儲存pointer。 ### 適用時機: * 無法預期資料數量時,使用Linked list就沒有resize的問題。 * 需要頻繁地新增/刪除資料時。 * 不需要快速查詢資料。 --- ### 206. Reverse Linked List ```javascript=j # Definition for singly-linked list. # class ListNode(object): # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution(object): def reverseList(self, head): """ :type head: ListNode :rtype: ListNode """ current = head prev = None while current: next = current.next # give next node a name current.next = prev # reverse the next to previous node prev = current # exchange current and previous node current = next # let current to the next node return prev ``` ### Linked Node 與 Linked List定義 ```clike= // C++ code #include <iostream> using std::cout; using std::endl; class LinkedList; // 為了將class LinkedList設成class ListNode的friend, // 需要先宣告 class ListNode{ private: int data; ListNode *next; public: ListNode():data(0),next(0){}; ListNode(int a):data(a),next(0){}; friend class LinkedList; }; class LinkedList{ private: // int size; // size是用來記錄Linked list的長度, 非必要 ListNode *first; // list的第一個node public: LinkedList():first(0){}; void PrintList(); // 印出list的所有資料 void Push_front(int x); // 在list的開頭新增node void Push_back(int x); // 在list的尾巴新增node void Delete(int x); // 刪除list中的 int x void Clear(); // 把整串list刪除 void Reverse(); // 將list反轉: 7->3->14 => 14->3->7 }; ``` ### PrintList()列舉所有ListNode的value ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f1.png?raw=true) ```clike= // C++ code void LinkedList::PrintList(){ if (first == 0) { // 如果first node指向NULL, 表示list沒有資料 cout << "List is empty.\n"; return; } ListNode *current = first; // 用pointer *current在list中移動 while (current != 0) { // Traversal cout << current->data << " "; current = current->next; } cout << endl; } ``` ### Push_front()開頭新增newNode資料 ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f2.png?raw=true) ```clike= void LinkedList::Push_front(int x){ ListNode *newNode = new ListNode(x); // 配置新的記憶體 newNode->next = first; // 先把first接在newNode後面 first = newNode; // 再把first指向newNode所指向的記憶體位置 } ``` ### Push_back()尾巴新增newNode資料 ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f4.png?raw=true) ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f5.png?raw=true) ```clike= // C++ code void LinkedList::Push_back(int x){ ListNode *newNode = new ListNode(x); // 配置新的記憶體 if (first == 0) { // 若list沒有node, 令newNode為first first = newNode; return; } ListNode *current = first; while (current->next != 0) { // Traversal到最後一個node current = current->next; } current->next = newNode; // 將newNode接在list的尾巴 } ``` ### Delete 刪除資料為int x的node ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f6.png?raw=true) ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f7.png?raw=true) ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f8.png?raw=true) ```clike= // C++ code void LinkedList::Delete(int x){ ListNode *current = first; ListNode *previous = 0; while (current != 0 && current->data != x) { // Traversal linked list previous = current; // 如果current指向NULL current = current->next; // 或是current->data == x } // 即結束while loop if (current == 0) { // list沒有要刪的node, 或是list為empty std::cout << "There is no " << x << " in list.\n"; return; }else if (current == first) { // 要刪除的node剛好在list的開頭 first = current->next; // 把first移到下一個node delete current; // 如果list只有一個node, 那麼first就會指向NULL current = 0; // 當指標被delete後, 將其指向NULL, 可以避免不必要bug return; }else { // 其餘情況, list中有欲刪除的node, previous->next = current->next; // 而且node不為first, 此時previous不為NULL delete current; current = 0; return; } } ``` ### Clear()清除整個Linked list ```clike= // C++ code void LinkedList::Clear(){ while (first != 0) { // Traversal ListNode *current = first; first = first->next; delete current; current = 0; } } ``` ### Reverse()反轉Linked list ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f24.png?raw=true) 有了這三個指標後,要執行的步驟只有兩個: * 將current->next從原本指向preceding更新成指向previous,如圖。執行current->next=previous,就把node(3)的指向node(7)。 * 把三個指標「按照順序」往前移動,然後進行下一個node之pointer調整,如圖。 * previous=current,將previous移動到node(3)。 * current=preceding,將current移動到node(14)。 * preceding=preceding->next,將preceding移動到node(8)。 重複上述步驟,直到preceding更新成NULL,調整Linked list的first所指向的記憶體位置,即完成Linked list之反轉。 ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/BasicDataStructures/LinkedList/Insert_Delete_Reverse/f25.png?raw=true) ```clike= // C++ code void LinkedList::Reverse(){ if (first == 0 || first->next == 0) { // list is empty or list has only one node return; } ListNode *previous = 0; ListNode *current = first; ListNode *preceding = first->next; while (preceding != 0) { current->next = previous; // 把current->next轉向 previous = current; // previous往後挪 current = preceding; // current往後挪 preceding = preceding->next; // preceding往後挪 } // preceding更新成NULL即跳出while loop current->next = previous; // 此時current位於最後一個node, 將current->next轉向 first = current; // 更新first為current } ``` Reference: http://alrightchiu.github.io/SecondRound/linked-list-xin-zeng-zi-liao-shan-chu-zi-liao-fan-zhuan.html --- ## Stack 在以下Array實作Stack程式範例中,StackArray的private data有三項: * int top:記錄於stack中,最上面資料的index。 * int capacity:即為Array的size,也就是實際配置的記憶體大小。 * int *stack:這裡以int的Array來表示Stack。 ```clike= // C++ code #include <iostream> class StackArray{ private: int top; // index of top element int capacity; // allocated memory space of array int *stack; // array representing stack void DoubleCapacity(); // double the capacity of stack public: StackArray():top(-1),capacity(1){ // constructor stack = new int[capacity]; // initial state: top=-1, capacity=1 } void Push(int x); void Pop(); bool IsEmpty(); int Top(); int getSize(); }; ``` DoubleCapacity():因為利用Array來存放資料,所以有可能在不斷新增資料時,碰上一開始分配給Array的記憶體空間(capacity)不夠的情況,可以透過重新配置一個capacity為==兩倍大的Array來解決==。 更理想的做法是,能夠先對欲放進Stack處理的資料數量有個底,在初始化時,就把capacity設定在差不多的範圍,如此一來,不需要重複多次DoubleCapacity(),也不會過分浪費記憶體空間。 本篇文章提供的範例程式碼將capacity初始為11,主要是為了方便測試DoubleCapacity()。 ```clike= void StackArray::DoubleCapacity(){ capacity *= 2; // double capacity int *newStack = new int[capacity]; // create newStack for (int i = 0 ; i < capacity/2; i++) { // copy element to newStack newStack[i] = stack[i]; } delete [] stack; // release the memory of stack stack = newStack; // redirect stack to newStack } ``` Stack的基本處理資料的函式: **Push()** ```clike= void StackArray::Push(int x){ if (top == capacity - 1) { // if stack is full, double the capacity DoubleCapacity(); } stack[++top] = x; // update top and put x into stack } ``` **Pop()** ```clike= void StackArray::Pop(){ if (IsEmpty()) { // if stack is empty, there is nothing to pop std::cout << "Stack is empty.\n"; return; } top--; // update top // stack[top] = 0; // (*1) // stack[top].~T(); // (*2) } ``` **IsEmpty()** ```clike= bool StackArray::IsEmpty(){ // if (top == -1) { // return true; // } // else { // return false; // } return (top == -1); } ``` **Top()** ```clike= int StackArray::Top(){ if (IsEmpty()) { // check if stack is empty std::cout << "Stack is empty.\n"; return -1; } return stack[top]; // return the top element } ``` **getSize()** ```clike= int StackArray::getSize(){ return top+1; // return the number of elements in stack } ``` --- ## Queue Queue是具有**First-In-First-Out**的資料結構,Queue也具有以下特徵: 隊伍有前方(front)以及後方(rear)之分。 * 若要進入隊伍(Push),一定是從rear進入。 * 若要離開隊伍(Pop),一定是從front離開。 使用陣列來實作佇列,我們必須保留兩個旗標,假設front指向佇列的前端,rear向佇列的後端,我們每次從佇列後端加入一個資料,rear就加1指向最後一個資料,每次從佇列前端取出一個資料,front就加1指向佇列的最前端,如下圖所示: <div class="align-center"> ![](http://www.bcwhy.com/jdsf/AlgorithmGossip/images/queneByArray-1.jpg) </div> 這是最簡單的佇列實作,但是由於陣列的大小必須先決定,所以這種線性的結構有個問題,front與rear會到達陣列的後端,而這個陣列就不能再使用了,為了解決這個問題,將陣列當作**環狀**來使用,當front或rear到達陣列後端時,就重新從陣列前端再循環,也就是形成**環狀佇列**,如下圖所示: <div class="align-center"> ![](http://www.bcwhy.com/jdsf/AlgorithmGossip/images/queneByArray-2.jpg) </div> 不過陣列的容量還是受限制,所以這個陣列還是會滿的,當front = rear時,佇列就滿了; Queue的基本操作有五項:**新增佇列、加入資料、顯示前端資料、取出前端資料、顯示所有的佇列元素**。 ```clike= #include <stdio.h> #include <stdlib.h> #define N 10 void createq(int[], int*, int*); void showfront(int[], int, int); void add(int[], int*, int*, int); void del(int[], int*, int*); void showqueue(int[], int, int); int main(void) { int queue[N]; int front, rear; int input, select; createq(queue, &front, &rear); while(1) { printf("\n\n請輸入選項(-1結束):"); printf("\n(1)插入值至佇列"); printf("\n(2)顯示佇列前端"); printf("\n(3)刪除前端值"); printf("\n(4)顯示所有內容"); printf("\n$c>"); scanf("%d", &select); if(select == -1) break; switch(select) { case 1: printf("\n輸入值:"); scanf("%d", &input); add(queue, &front, &rear, input); break; case 2: showfront(queue, front, rear); break; case 3: del(queue, &front, &rear); break; case 4: showqueue(queue, front, rear); break; default: printf("\n選項錯誤!"); } } printf("\n"); return 0; } void createq(int queue[], int* front, int* rear) { int i; for(i = 0; i < N; i++) queue[i] = 0; *front = *rear = 0; } void showfront(int queue[], int front, int rear) { if(front == rear) printf("\n佇列為空!"); else printf("%d", queue[(front+1) % N]); } void add(int queue[], int* front, int* rear, int data) { int f, r; f = *front; r = *rear; r = (r+1) % N; if(f == r) { printf("\n佇列已滿!"); return; } queue[r] = data; *rear = r; } void del(int queue[], int* front, int* rear) { int f, r; f = *front; r = *rear; if(f == r) { printf("\n佇列為空!"); return; } f = (f+1) % N; *front = f; } void showqueue(int queue[], int front, int rear) { int i; printf("\n佇列內容:"); for(i = (front+1) % N; i <= rear; i++) printf("%d ", queue[i]); } ``` ## Binary Tree Traversal ![](https://github.com/alrightchiu/SecondRound/blob/master/content/Algorithms%20and%20Data%20Structures/Tree%20series/BinaryTree_fig/Traversal/f2.png?raw=true) * **Pre-order(VLR)**:當CurrentNode移動到A時,會先對A進行Visiting,接著前往left child進行Visiting,再前往right child進行Visiting。(若child指向NULL則忽略。) * **In-order(LVR)**:當CurrentNode移動到A時,會先對A的left child(B)進行Visiting,接著回到A進行Visiting,再前往right child(C)進行Visiting。(若child指向NULL則忽略。) * **Post-order(LRV)**:當CurrentNode移動到A時,會先對A的left child(B)進行Visiting,再前往right child(C)進行Visiting,接著回到A進行Visiting。(若child指向NULL則忽略。)