馬上要面臨大規模的面試了,用了太久標准庫,已經對數據結構的內部實現快忘了,趁着還有幾天時間,自己又回憶了一下,用C++實現出來。所以接下來我在博客中會寫一個“數據結構整理”系列,在面試之前,能回憶多少算多少吧,希望面試官能感受到我曾經是一個對數據結構很熟悉的人。。。
下面進入正題。
鏈表和數組都是線性表,數組就不多說了,說一下鏈表的實現。鏈表的名字很形象,就是一個一個的結點鏈在一起,必須找到一個結點的父節點,才能找到該結點,鏈表一般都有一個頭結點,而且頭結點一般不保存用戶的數據,只是為了方便對鏈表的尋址。另外,可以在頭結點中保存一些額外的信息,例如鏈表的長度,不過為了可讀性,我個人不建議這樣做。
下面是鏈表中一個結點的一般結構:
template <typename T> class Node { public: T data; Node *next; Node(T d) { data = d; next = nullptr; } Node() { next = nullptr; } };
data字段保存結點中需要存儲的信息,next指針保存下一個結點的地址。
一般在鏈表中都有一個頭指針和一個尾指針,頭指針始終指向頭結點,作用是方便對鏈表尋址。尾指針指向鏈表最后一個結點,作用是減少插入數據時消耗的時間。對鏈表的操作一般有獲取長度,插入結點,刪除結點,排序,逆置等,所以鏈表的一般結構如下:
1 template <typename T> 2 class LinkList { 3 private: 4 Node<T>* head; 5 Node<T>* rear; 6 int lenth; 7 public: 8 LinkList(); 9 ~LinkList(); 10 inline int size(); 11 void insert(T data); 12 void remove(T data); 13 void sort(); 14 void reverse(); 15 void getArray(T*& arr); 16 };
一個鏈表初始只有一個頭結點,頭指針和尾指針都指向頭結點,鏈表長度為0。另外由於每個結點都是動態申請的堆內存,在析構函數中應及時釋放,所以下面是構造函數和析構函數的實現:

1 template <typename T> 2 LinkList<T>::LinkList() { 3 head = new Node<T>(); 4 head->next = nullptr; 5 rear = head; 6 lenth = 0; 7 } 8 9 template <typename T> 10 LinkList<T>::~LinkList() { 11 Node<T>* cur = head->next; 12 while(cur != nullptr) { 13 delete head; 14 head = cur; 15 cur = cur->next; 16 } 17 }
向鏈表中插入數據即把新結點添加到鏈表末尾,所以利用尾指針可以很方便的做到。刪除數據時先將待刪除結點的父節點next的指針指向待刪除結點的子節點,然后釋放掉待刪除結點的內存。下面是鏈表結點的插入和刪除操作的實現:

1 template <typename T> 2 void LinkList<T>::insert(T data) { 3 Node<T>* cur = new Node<T>(data); 4 rear->next = cur; 5 rear = cur; 6 lenth++; 7 } 8 9 template <typename T> 10 void LinkList<T>::remove(T data) { 11 Node<T>* before = head; 12 Node<T>* cur = head->next; 13 while(cur != nullptr) { 14 if(cur->data == data) { 15 before->next = cur->next; 16 delete cur; 17 cur = before->next; 18 lenth--; 19 } else { 20 before = cur; 21 cur = cur->next; 22 } 23 } 24 }
鏈表的排序復雜度很高,一般不建議對存在大量數據的鏈表執行排序操作,鏈表的排序是一個對鏈表重建的過程,每次拆下一個結點,按照一定的順序重新組合回去。注意排序完成后,尾指針一定要指向鏈表的最后一個結點。下面是鏈表排序的實現:

1 template <typename T> 2 void LinkList<T>::sort() { 3 Node<T>* p = head->next; 4 head->next = nullptr; 5 rear = head; 6 Node<T>* before = head; 7 Node<T>* after = head->next; 8 9 while(p != nullptr) { 10 if(after == nullptr || 11 (p->data >= before->data && p->data <= after->data)) { 12 before->next = p; 13 p = p->next; 14 before = before->next; 15 before->next = after; 16 before = head; 17 after = head->next; 18 if(rear->next != nullptr) { 19 rear = rear->next; 20 } 21 } else { 22 before = before->next; 23 after = after ->next; 24 } 25 } 26 }
鏈表的逆置也是一個重建鏈表的過程,即改變每個結點的指向,但是頭結點依然是頭結點。顯然,逆置操作之后,尾指針應指向逆置前的第一個數據結點(即頭指針的子結點)。下面是逆置操作的實現:

1 template <typename T> 2 void LinkList<T>::reverse() { 3 if(lenth < 2) { 4 return; 5 } 6 rear = head->next; 7 Node<T>* before = head->next; 8 Node<T>* after = before->next; 9 head->next = nullptr; 10 while(before != nullptr) { 11 before->next = head->next; 12 head->next = before; 13 before = after; 14 if(after != nullptr) { 15 after = after->next; 16 } 17 } 18 }
還有頭文件中的另外兩個函數,獲取長度和轉化為數組。獲取長度很簡單,在插入和刪除時維護長度字段即可,為了提高在for循環中使用size()函數的效率,聲明成inline的。轉化為數組即遍歷鏈表,把數據依次插入數組中即可。注意,C++不應返回一個函數中局部變量的數組,因為函數返回后棧被釋放,非常危險。下面是獲取長度和轉化為數組的實現:

1 template <typename T> 2 int LinkList<T>::size() { 3 return lenth; 4 } 5 6 template <typename T> 7 void LinkList<T>::getArray(T*& arr) { 8 arr = new int[lenth]; 9 Node<T>* cur = head->next; 10 for(int i = 0; cur != nullptr; i++) { 11 arr[i] = cur->data; 12 cur = cur->next; 13 } 14 }
最后附上單元測試的代碼:

1 void test_linklist() { 2 int number[] = {4, 2, 6, 7, 1, 4, 7}; 3 LinkList<int>* list = new LinkList<int>(); 4 for(int i = 0; i < 7; i++) { 5 list->insert(number[i]); 6 } 7 8 int* arr = nullptr; 9 list->getArray(arr); 10 cout << "init:" << endl; 11 for(int i = 0; i < list->size(); i++) { 12 cout << arr[i] << endl; 13 } 14 delete arr; 15 16 list->sort(); 17 list->getArray(arr); 18 cout << "after sort:" << endl; 19 for(int i = 0; i < list->size(); i++) { 20 cout << arr[i] << endl; 21 } 22 delete arr; 23 24 list->reverse(); 25 list->getArray(arr); 26 cout << "after reverse:" << endl; 27 for(int i = 0; i < list->size(); i++) { 28 cout << arr[i] << endl; 29 } 30 delete arr; 31 32 list->remove(4); 33 list->getArray(arr); 34 cout << "after remove 4:" << endl; 35 for(int i = 0; i < list->size(); i++) { 36 cout << arr[i] << endl; 37 } 38 delete arr; 39 40 delete list; 41 }