雙向鏈表實現線性表

一、概述

1.1 數據結構必知

  • 數據結構的邏輯結構分爲集合結構、線性結構、樹形結構、圖形結構。
  • 數據結構的存儲結構分爲順序結構、鏈式結構、索引結構、散列結構。
  • 常見的八大數據結構有數組(Array)、棧(Stack)、隊列(Queue)、鏈表(Linked List)、樹(Tree)、圖(Graph)、散列表(Hash)。

1.2 關注點

  • 如何方便的存儲(增刪改查、遍歷、排序)數據:有序集合(List)、無序集合(Set)。
  • 如何約束數據的線性關係:棧(Stack,先進後出)、隊列(先進先出)。
  • 如何表示多個數據之間的關係:樹(Tree,一對多)、圖(Graph,多對多)。
  • 如何存儲鍵值類型的數據對:映射(Map)。
  • 如何提高性能:集合容器本身需要根據數據量的多少而選擇合適的數據結構。



二、簡單線性表

2.1 數組和鏈表的關係

編程語言誕生之初,早已經有了指針和內存······

  • 數組:指針指向了一塊若干數據大小的連續內存,可以通過指針或下標去訪問。
  • 鏈表: 指針指向了一塊1個數據大小的內存並且這個內存中保存着指向另一塊1個數據大小的內存的指針,只能通過指針去訪問。

2.2 頭節點的作用

鏈表有無頭節點

設置頭節點只是爲了操作的方便。如果不設置頭節點,從圖中例子的對比可以看到:處理第一個元素是比較麻煩的。

  • 如果採用指針傳參(地址值的值傳遞),指針類型的實參不可能在函數內部指向另一個地址。

  • 如果採用引用傳參(引用的本質是常指針),引用只是變量的別名,在申明時就已經確定了指向並且永遠不能更改其指向。

    int a = 5;
    
    int& ref = a;
    // 等價於
    int* const p = &a;
    

通常的解決方案有:

  • 將在函數內改變指向的指針作爲返回值返回,在調用時更新指針。(如圖中右側示例,並且這也僅僅是爲了處理第一個元素的問題而不得不這麼做)

  • 採用二級指針傳參,將指針的地址值傳入函數即可以在函數內改變指針的指向。

    Node** headContainer = &head;
    
  • 將頭指針設爲全局變量。

  • 將頭指針設爲對象的屬性

2.3 線性表結構約定

在這裏插入圖片描述

2.4 有序集合接口——list.h

#ifndef DEMO_TEST_LIST_H
#define DEMO_TEST_LIST_H

#include <string>
#include <iostream>

template<typename E>
class List {
public:
    virtual ~List();

    virtual int size() = 0;
    
    virtual bool isEmpty() = 0;
    
    /**
     * 向表中添加元素
     * @param element
     */
    virtual void add(E element) = 0;
    
    /**
     * 向表中指定位置插入元素
     * @param index
     * @param element
     */
    virtual void add(int index, E element) = 0;
    
    /**
     * @param index
     * @return 表中指定位置的元素
     */
    virtual E get(int index) = 0;
    
    /**
     * @param element
     * @return 獲取某元素在表中的下標
     */
    virtual int indexOf(E element) = 0;
    
    /**
     * 更改表中指定位置的元素
     * @param index
     * @param element
     */
    virtual void set(int index, E element) = 0;
    
    /**
     * 移除表中某個元素
     * @param element
     */
    virtual void removeByElement(E element) = 0;
    
    /**
     * 移除表中指定下標的元素
     * @param index
     */
    virtual void removeByIndex(int index) = 0;
    
    /**
     * 移除表中全部元素
     */
    virtual void removeAll() = 0;
    
    virtual std::string toString() = 0;
};

template<typename T>
List<T>::~List() {
    std::cout << "默認析構函數";
}

#endif //DEMO_TEST_LIST_H

2.5 隊列接口——queue.h

#ifndef DEMO_TEST_QUEUE_H
#define DEMO_TEST_QUEUE_H

template<typename E>
class Queue {
public:
    /**
     * 將指定的元素插入此隊列
     * @return 是否插入成功
     */
    virtual bool offer(E element) = 0;

    /**
     * 獲取隊列的頭但不移除
     * @return 隊列的頭
     */
    virtual E peek() = 0;
    
    /**
     * 獲取隊列的頭並移除
     * @return 隊列的頭
     */
    virtual E poll() = 0;
};

#endif //DEMO_TEST_QUEUE_H

2.6 棧接口——stack.h

#ifndef DEMO_TEST_STACK_H
#define DEMO_TEST_STACK_H

template<typename E>
class Stack {
public:
    /**
     * 壓棧
     * @param element
     */
    virtual void push(E element) = 0;

    /**
     * 獲取棧頂元素但不出棧
     * @return 棧頂元素
     */
    virtual E peek() = 0;
    
    /**
     * 獲取棧頂元素並出棧
     * @return 棧頂元素
     */
    virtual E pop() = 0;
};

#endif //DEMO_TEST_STACK_H



三、雙向鏈表實現線性表

3.1 雙向鏈表圖示

注:和圖中不完全相符

  • 代碼中的鏈表用firstlast表示首尾元素的指針(也即head指針和tail指針)
  • 節點用prevnext表示前驅和後繼指針。

雙向鏈表圖示

3.2 節點代碼——node.h

#ifndef DEMO_TEST_NODE_H
#define DEMO_TEST_NODE_H


template<typename E>
struct Node {
    E data;

    Node<E>* prev;
    Node<E>* next;
};

#endif //DEMO_TEST_NODE_H

3.3 雙向鏈表代碼——linked_list.h

#ifndef DEMO_TEST_LINKED_LIST_H
#define DEMO_TEST_LINKED_LIST_H

#include "node.h"
#include "list.h"
#include "stack.h"
#include "queue.h"

// 實現toString()需要用到
using namespace std;

template<typename E>
class LinkedList : public List<E>, public Stack<E>, public Queue<E> {
private:
    int length;
    Node<E>* first;
    Node<E>* last;

public:
    LinkedList() : length(0), first(nullptr), last(nullptr) {}

    int size() override {
        return this->length;
    }

    bool isEmpty() override {
        return this->length == 0;
    }

    void add(E element) override {
        // 新建節點
        Node<E>* node = new Node<E>();
        node->data = element;
        node->next = nullptr;

        if (this->first == nullptr) {
            // 空鏈表
            node->prev = nullptr;

            this->first = node;
            this->last = node;
        } else {
            // 非空鏈表
            node->prev = this->last;
            this->last->next = node;

            this->last = node;
        }
        this->length++;
    }

    void add(int index, E element) override {
        if (index > this->length - 1) {
            // 下標越界
            throw "Index Out Of Bounds";
        }
        // 新建節點
        Node<E>* node = new Node<E>();
        node->data = element;

        if (index == 0) {
            // 插入到第一個位置
            node->prev = nullptr;
            node->next = this->first;
            this->first->prev = node;

            this->first = node;
        } else if (index == this->length - 1) {
            // 插入到最後一個位置
            node->next = nullptr;
            node->prev = this->last;
            this->last->next = node;

            this->last = node;
        } else {
            // 尋找插入位置
            Node<E>* toInsert = nullptr;
            if (index <= this->length / 2) {
                Node<E>* p = this->first->next;
                int i = 1;
                while (i != index) {
                    i++;
                    p = p->next;
                }
                toInsert = p;
            } else if (index > this->length / 2) {
                Node<E>* p = this->last->prev;
                int i = this->length - 2;
                while (i != index) {
                    i--;
                    p = p->prev;
                }
                toInsert = p;
            }
            // 此處toInsert必定不爲空指針
            Node<E>* before = toInsert->prev;
            before->next = node;
            node->prev = before;

            node->next = toInsert;
            toInsert->prev = node;
        }
        this->length++;
    }


    E get(int index) override {
        if (index > this->length - 1) {
            // 下標越界
            throw "Index Out Of Bounds";
        }
        Node<E>* toGet = nullptr;
        if (index <= this->length / 2) {
            // 從前向後查詢
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toGet = p;
        } else if (index > this->length / 2) {
            // 從後向前查詢
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toGet = p;
        }
        return toGet->data;
    }

    int indexOf(E element) override {
        Node<E>* p = this->last;
        int i = this->length - 1;
        while (p != nullptr) {
            if (p->data == element) {
                break;
            }
            i--;
            p = p->prev;
        }
        return i;
    }

    void set(int index, E element) override {
        Node<E>* toSet = nullptr;
        if (index > this->length - 1) {
            // 下標越界
            throw "Index Out Of Bounds";
        } else if (index <= this->length / 2) {
            // 從前向後查詢
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toSet = p;
        } else if (index > this->length / 2) {
            // 從後向前查詢
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toSet = p;
        }
        // 此處toSet必定不會爲空指針
        toSet->data = element;
    }

    void removeByElement(E element) override {
        Node<E>* toRemove = nullptr;

        // 查詢待刪除的節點
        Node<E>* p = this->first;
        while (p != nullptr) {
            if (p->data == element) {
                toRemove = p;
                break;
            }
            p = p->next;
        }
        if (toRemove != nullptr) {
            // 如果查詢到先連接待刪除節點的前驅和後繼,然後釋放待刪除節點的內存
            if (toRemove == this->first) {
                // 待刪除的是第一個元素
                Node<E>* after = toRemove->next;
                after->prev = nullptr;

                this->first = after;
            } else if (toRemove == this->last) {
                // 待刪除的是最後一個元素
                Node<E>* before = toRemove->prev;
                before->next = nullptr;

                this->last = before;
            } else {
                // 待刪除的是中介的元素
                Node<E>* before = toRemove->prev;
                Node<E>* after = toRemove->next;

                before->next = after;
                after->prev = before;
            }
            delete toRemove;
            this->length--;
        }
    }

    void removeByIndex(int index) override {
        Node<E>* toRemove = nullptr;

        //查詢待刪除的節點
        if (index > this->length - 1) {
            // 下標越界
            throw "Index Out Of Bounds";
        } else if (index <= this->length / 2) {
            // 從前向後查詢
            Node<E>* p = this->first;
            int i = 0;
            while (i != index) {
                i++;
                p = p->next;
            }
            toRemove = p;
        } else if (index > this->length / 2) {
            // 從後向前查詢
            Node<E>* p = this->last;
            int i = this->length - 1;
            while (i != index) {
                i--;
                p = p->prev;
            }
            toRemove = p;
        }
        if (toRemove != nullptr) {
            // 如果查詢到先連接待刪除節點的前驅和後繼,然後釋放待刪除節點的內存
            if (toRemove == this->first) {
                // 待刪除的是第一個元素
                Node<E>* after = toRemove->next;
                after->prev = nullptr;

                this->first = after;
            } else if (toRemove == this->last) {
                // 待刪除的是最後一個元素
                Node<E>* before = toRemove->prev;
                before->next = nullptr;

                this->last = before;
            } else {
                // 待刪除的是中介的元素
                Node<E>* before = toRemove->prev;
                Node<E>* after = toRemove->next;

                before->next = after;
                after->prev = before;
            }
            delete toRemove;
            this->length--;
        }
    }

    void removeAll() override {
        Node<E>* p = this->first;
        while (p != nullptr) {
            Node<E>* temp = p->next;
            delete p;
            if (temp != nullptr) {
                p = temp;
            } else {
                break;
            }
        }
        this->first = nullptr;
        this->last = nullptr;
        this->length = 0;
    }


    std::string toString() override {
        std::string left = "{";
        std::string right = "}";
        std::string prop1 = "\"length\": \"" + to_string(this->length).append("\"");

        std::string prop2 = "\"elements\": [";
        Node<E>* p = this->first;
        while (p != nullptr) {
            prop2.append("\"")
                 .append(to_string(p->data))
                 .append("\"");
            if (p->next != nullptr) {
                prop2.append(", ");
            }
            p = p->next;
        }
        prop2.append("]");

        return left.append(prop1)
                   .append(", ")
                   .append(prop2)
                   .append(right)
                   .append("\n");
    }

    bool offer(E element) override {
        // 將表頭作爲隊頭
        this->add(0, element);
        return true;
    }

    E peek() override {
        // 將表尾作爲棧頂和隊尾
        return this->last->data;
    }

    E poll() override {
        // 將表爲作爲隊尾
        E result = this->last->data;
        this->removeByIndex(this->length - 1);
        return result;
    }

    void push(E element) override {
        // 將表尾作爲棧頂
        this->add(element);
    }


    E pop() override {
        // 將表尾作爲棧頂
        E result = this->last->data;
        this->removeByIndex(this->length - 1);
        return result;
    }
};

#endif //DEMO_TEST_LINKED_LIST_H



四、測試

4.1 測試文件——main.cpp

#include <iostream>

#include <ctime>
#include <iostream>
#include "linked_list.h"

using namespace std;

// 測試add()
void init(List<int>* list) {
    srand(time(NULL));
    for (int i = 0; i < 10; i++) {
        list->add(rand() % 100);
    }
    cout << list->toString() << endl;

    list->add(0, 99);
    cout << "add(0, 99)\n" << list->toString() << endl;

    list->add(5, 999);
    cout << "add(5, 999)\n" << list->toString() << endl;

    list->add(9, 9999);
    cout << "add(9, 9999)\n" << list->toString() << endl;
}

// 測試removeByElement()
void test1(List<int>* list) {
    list->removeByElement(99);
    list->removeByElement(999);
    list->removeByElement(9999);
    cout << "removeByElement 99, 999, 9999\n" << list->toString() << endl;
}

// 測試removeByIndex()
void test2(List<int>* list) {
    list->removeByIndex(0);
    cout << "removeByIndex(0)\n" << list->toString() << endl;

    list->removeByIndex(5);
    cout << "removeByIndex(5)\n" << list->toString() << endl;

    list->removeByIndex(9);
    cout << "removeByIndex(9)\n" << list->toString() << endl;
}

// 測試removeAll()
void test3(List<int>* list) {
    list->removeAll();
    cout << "removeAll()\n" << list->toString() << endl;
}

// 測試set()
void test4(List<int>* list) {
    list->set(0, 77);
    list->set(5, 777);
    list->set(9, 7777);
    cout << "set(0, 77), set(5, 777), set(9, 7777)\n" << list->toString() << endl;
}

// 測試indexOf()
void test5(List<int>* list) {
    int a = list->indexOf(99);
    int b = list->indexOf(999);
    int c = list->indexOf(9999);
    cout << "indexOf(99)=" << a << ", indexOf(999)=" << b << ", indexOf(9999)=" << c << endl;
}


int main() {
    List<int>* list = new LinkedList<int>();
    init(list);

//    test1(list);
//    test2(list);
//    test3(list);
//    test4(list);
//    test5(list);

    return 0;
}

4.2 測試截圖

test1
test2
test3
test4
test5

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章