數據結構整理(一) —— 鏈表的各種操作


    馬上要面臨大規模的面試了,用了太久標准庫,已經對數據結構的內部實現快忘了,趁着還有幾天時間,自己又回憶了一下,用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 }
單元測試

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM