【DS】第二章-順序表和鏈表

第二章 順序表和鏈表

順序表

什麼是順序表

  順序表是物理地址全部連續的存儲數據的方式。順序表分爲動態順序表以及動態的順序表,靜態的順序表一般很少使用,因爲其大小一旦固定不能再進行改變。
  順序表在開發中十分經常使用,因爲其方便簡單,並且易於操作。數組就是順序表的一種,因爲其在邏輯結構上是線性的,在物理結構上是連續的。由於十分常用在Cpp的STL庫中封裝了以順序表爲數據結構的vector容器供我們使用。

實現

  順序表的實現十分類似於vector類的實現。

#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include <stdlib.h>
#include <Windows.h>
using namespace std;
#define N 100
//順序表的實現
//十分類似於我們實現過的vector類,畢竟vector就是順序表
template<class T>
class SeqList
{
public:
    //構造函數
    SeqList()
    :_array(nullptr)
    ,_size(0)
    ,_capacity(0)
    {}
    //析構函數
    ~SeqList()
    {
        if(_array != nullptr)
        {
            delete _array;
        }
    }
    //pos前插入
    void Insert(size_t pos, const T& value)
    {
        //判斷pos是否合法
        if(pos > _size || pos < 0)
        {
            return;
        }
        //擴容
        Expend();
        for(size_t i = _size; i > pos; i--)
        {
            _array[i] = _array[i - 1];
        }
        _array[pos] = value;
        _size++;
    }
    //尾插
    void Push_back(const T& value)
    {
        //擴容
        Expend();
        _array[_size] = value;
        _size++;
    }
    //頭插
    void Push_front(const T& value)
    {
        //擴容
        Expend();
        //所有元素向後挪一個位置
        for(size_t i = _size; i > 0; i--)
        {
            _array[i] = _array[i - 1];
        }
        _array[0] = value;
        _size++;
    }
    //尾刪
    void Pop_back()
    {
        //刪除前要先判斷,有元素才能刪
        if(_size > 0)
        {
            _size--;
        }
    }
    //頭刪
    void Pop_front()
    {
        //有元素才能刪
        if(_size > 0)
        {
            for (size_t i = 1; i < _size; i++)
            {
                _array[i - 1] = _array[i];
            }
            _size--;
        }
    }
    //刪除pos當前位置數據
    void Erase(size_t pos)
    {
        //pos不合法
        if(pos >= _size || pos < 0)
        {
            return;
        }
        for(size_t i = pos; i < _size - 1; i++)
        {
            _array[pos] = _array[pos + 1];
        }
        _size--;
    }
    //查找
    size_t Find(const T& value)
    {
        for(size_t i = 0; i < _size; i++)
        {
            if(_array[i] == value)
            {
                return i;
            }
        }
        return -1;
    }
    //二分查找
    size_t BinaryFind(const T& value)
    {
        //左閉右開區間
        size_t high = _size, low = 0;
        while(high > low)
        {
            size_t mid = (high + low) / 2;
            if(_array[mid] == value)
            {
                return mid;
            }
            else if(_array[mid] > value)
            {
                high = mid;    
            }
            else
            {
                low = mid + 1;
            }
        }
    }
    //修改
    void Modify(size_t pos, const T& value)
    {
        if(pos < 0 || pos >= _size)
        {
            return;
        }
        _array[pos] = value;
    }
    //打印
    void Print()
    {
        for(size_t i = 0; i < _size; i++)
        {
            cout << _array[i] << " ";
        }
        cout << endl;
        //cout << _size << endl;
    }
    //當前元素個數
    size_t Size()
    {
        return _size;
    }
    //某個位置的值
    T& operator[](size_t pos)
    {
        assert(pos >=0 && pos < _size);
        return _array[pos];
    }
    //冒泡排序
    void BubbleSort()
    {
        for(int i = 0; i < _size - 1; i++)
        {
            bool flag = false;
            for(int j = 0; j < _size - i - 1; j++)
            {
                if(_array[j] > _array[j + 1])
                {
                    swap(_array[j], _array[j + 1]);
                    flag = true;
                }
            }
            if(flag == false)
            {
                break;
            }
        }
    }
    void RemoveAll()
    {
        _size = 0;
    }
private:
    //T _array[N];//靜態順序表,利用數組,不可變,十分不靈活
    T* _array;//動態順序表,利用指針動態開闢
    size_t _size;//長度
    size_t _capacity;//容量
    //擴容
    void Expend()
    {
        if(_size == _capacity)//滿了
        {
            size_t newCapacity = (_capacity == 0 ? 5 : 2 * _capacity);
            //創建更大空間,拷貝,釋放原有空間
            _array = (T*)realloc(_array, newCapacity * sizeof(T));
            //申請失敗
            assert(_array);
            //更新容量
            _capacity = newCapacity;
        }
    }
};

順序表的優缺點

  順序表優點
  1、根據下標隨機訪問時間複雜度O(1)。
  2、不會產生內存碎片。
  3、代碼簡單。
  4、在尾插時事件複雜度爲O1
  順序表缺點:
  1、在中間插入時時間複雜度爲On,最壞情況下要移動整個順序表完成頭插。
  2、增容申請新空間進行數據拷貝再釋放舊空間,有這不小消耗。
  3、增容一次空間爲原有空間兩倍,可能會造成空間大量浪費。

鏈表

什麼是鏈表

  順序表是物理地址連續的數據存儲方式,而鏈表與之相反,鏈表存儲數據的物理地址不一定連續,因此它不能像順序表那樣通過直接尋址的方式訪問到數據,它通過指針來連接和組織數據,因此也使得它和順序表有着截然不同的特徵,並且相比順序表來說或許更難理解一些。
鏈表
  鏈表有着以下幾種種類:
  1、帶頭鏈表,不帶頭鏈表。
  2、單向鏈表,雙向鏈表。
  3、循環鏈表,不循環鏈表。
  他們組合搭配起來一共有8種組合,
  由於鏈表的特性它可以很好地結局一些順序表的缺點,比如他不需要擴容,並且更方便插入等等,但是在一些方面上它也有着不如順序表的地方,關於優缺點我們放在最後再進行分析,接下來實現一個簡單的帶頭單向不循環鏈表

實現

  在Cpp的STL中有一個list容器,其中實現了一個帶頭雙向循環鏈表,這裏我們簡化進行實現帶頭單向不循環鏈表,思路更加簡單(帶頭雙向循環鏈表在Cpp章節中在模擬實現list時也有實現)。

#include <iostream>
using std::cout;
using std::endl;
template<class T>
struct ListNode
{
    //構造函數
    ListNode(const T& value = T())
        :data(value)
        ,next(nullptr)
    {

    }
    T data;//數據域
    ListNode* next;//指針域,指向下一個節點
};
template<class T>
class List
{
public:
    //構造函數
    List()
        :_head(new ListNode<T>)
        ,_size(0)
    {
    }
    ~List()
    {
        //依次刪除所有節點以釋放所有空間
        while(_size > 0)
        {
            Pop_Front();
        }
        delete _head;
    }
    //頭插
    void Push_Front(const T& value)
    {
        InsertPos(0, value);
    }
    //尾插
    void Push_Back(const T& value)
    {
        InsertPos(_size, value);
    }
    //打印所有數據
    void PrintAll()
    {
        ListNode<T>* temp = _head->next;
        while(temp != nullptr)
        {
            cout << temp->data << " ";
            temp = temp->next;
        }
        cout << endl;
    }
    //在下標爲pos的元素前進行插入
    void InsertPos(size_t pos, const T& value)
    {
        if(pos < 0 || pos > _size)
        {
            cout << "InsertPos: pos error" << endl;
            return;
        }
        //這裏的插入時間複雜度本應是O1,但是由於鏈表不便於尋址因此又需要線性的時間進行尋址
        ListNode<T>* node = FindForPos(pos);
        if(node == nullptr)
        {
            return;
        }
        ListNode<T>* newNode = new ListNode<T>(value);
        newNode->next = node->next;
        node->next = newNode;
        _size++;
    }
    //刪除下標爲pos的元素
    void ErasePos(size_t pos)
    {
        if(pos < 0 || pos >= _size)
        {
            cout << "ErasePos: pos error" << endl;
            return;
        }
        ListNode<T>* node = FindForPos(pos);
        if(node == nullptr)
        {
            return;
        }
        ListNode<T>* temp = node->next;
        node->next = temp->next;
        delete temp;
        _size--;
    }
    //尾刪
    void Pop_Back()
    {
        ErasePos(_size - 1);
    }
    //頭刪
    void Pop_Front()
    {
        ErasePos(0);
    }
    //返回鏈表長度
    size_t Size()
    {
        return _size;
    }
private:
    //返回下標爲pos的元素的前一個元素,下標爲0的元素則返回頭節點
    ListNode<T>* FindForPos(size_t pos)
    {
        ListNode<T> *temp = _head;
        for(int i = 0; i < pos; i++)
        {
            //不存在,長度不夠
            if(temp == nullptr)
            {
                cout << "FindForPos: pos error" << endl;
                return nullptr;
            }
            temp = temp->next;
        }
        return temp;
    }
private:
    ListNode<T>* _head;
    size_t _size;
};

鏈表的優缺點

  鏈表的優點:
  1、鏈表在任意位置插入和刪除時時間複雜度都能達到O1
  2、插入一個節點開闢一個空間,不牽扯擴容問題。
  鏈表的缺點:
  1、以節點爲單位存儲數據,並且還要存儲指針可能會浪費更多空間。
  2、不支持隨機訪問,因此在某一位置插入儘管插入只需要O1但是找到這個位置需要On
  3、思路實現略複雜。

鏈表反轉問題

  給一個單鏈表,反轉這個單鏈表。
  https://leetcode-cn.com/problems/reverse-linked-list/description/
  反轉鏈表有很多種方式,我們可以尾刪所有結點並同時將他們重新頭插回鏈表,但這樣的思路消耗實在太高,我們每次尾插都不得不遍歷整張單鏈表去找到尾,因此對其簡化,這裏提供兩種思路,
  第一種,後插頭刪法。這種方法是通過從第二個節點開始遍歷整個鏈表,依次將節點移向頭部的方法。圖解如下:
反轉鏈表
  題解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
        {
            return head;
        }
        ListNode* oldHead = head;
        ListNode* temp = oldHead->next;
        while(oldHead->next != NULL)
        {
            oldHead->next = temp->next;
            temp->next = head;
            head = temp;
            temp = oldHead->next;
        }
        return head;
    }
};

  2、第二種:向後轉法。第二種思路更爲直觀,我們就直接將每個結點中的next的指向改變即可。
反轉鏈表
  題解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
        {
            return head;
        }
        ListNode* prev = head;
        ListNode* cur = head->next;
        ListNode* next = cur;
        head->next = NULL;
        while(cur != NULL)
        {
            next = cur->next;
            cur->next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
};

  鏈表有多種多樣的問題,我會另開章節逐個歸納,就不再此繼續羅列了。

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