线性表 —— 线性表的数组描述和链表描述

线性表

抽象基类

这个抽象基类实际上约定了一个线性表应该具有那些功能。

//
// Created by MAC on 2020/9/26.
//

#ifndef DATASTRUCTURE_LINEARLIST_H
#define DATASTRUCTURE_LINEARLIST_H

template<class T>
class LinearList{
    
    
protected:
    int length;

public:
    LinearList() {
    
    
        length = 0;
    }

    virtual ~LinearList() {
    
    }

    virtual T& get(int index) const = 0;

    virtual void insert(int index,const T& t) = 0;

    virtual void push_back(const T& t) = 0;

    virtual void push_front(const T& t) = 0;

    virtual void pop_back() = 0;

    virtual void pop_front() = 0;

    virtual void erase(int index) = 0;

    virtual void clear() = 0;

    bool empty(){
    
    
        return length==0;
    }

    int size(){
    
    
        return length;
    }
};

#endif //DATASTRUCTURE_LINEARLIST_H

迭代器

迭代器提供了一种通用的方式去访问和操控容器对象。

类的层次设计

  • 作为私有内部嵌套类定义在类的内部(私有是为了不让外部的调用者显式操纵迭代器类);
  • 迭代器的方法(如++,--,*等等作为公有方法暴露给外面);
private:
    class arrayList_iterator{
    
    
    protected:
        T* pos;
    public:
        arrayList_iterator(T *pos = nullptr) : pos(pos) {
    
    }
		//……………………
    };
  • 对外部调用者而言使用别名
    typedef arrayList_iterator iterator;

  • 另外其他一些迭代器的接口在外部类的公有方法中给出(它们并不是迭代器对象自己调用的,故而不是在内部类定义),例如(begin();end()等等)。

    // 迭代器函数
    iterator begin(){
    
    
        return iterator(arr);
    }
    iterator end(){
    
    
        return iterator(arr+this->length);
    }

使用方法

  • 声明迭代器
    下面这句话相当于定义了属于线性表类的一个成员变量,不过这个变量的类型是我们自己写的类类型,实际上创建了一个新的对象。
    待它的生命周期结束会自动释放空间。
ArrayList<T>::iterator it = arrayList.begin();
  • 读写操作
    就像操控指针一样操控迭代器对象即可,因为我们已经重载了那些操作指针的运算符。
    迭代器实际上是对指针的再封装
T t = *it; // 读
T tt;
*it = tt; // 写
it++; // 移动

// …………

变长数组描述

关于构造函数、析构函数、赋值、复制

这四个函数最关键的就是动态分配数组。

关于扩容、缩容

  • 扩容机制
    在数组容量不够的时候,将数组容量扩大一倍。

  • 缩容机制
    为了内存空间的高效使用,不能只扩容,不缩小容量。
    所以,在线性表的元素的数量小于等于容量的1/4时,减小一半的空间。

性能分析

随机查询的函数效率高: g e t ( ) 、 s e t ( ) get()、set() get()set()的函数性能为 O ( 1 ) O(1) O(1)
插入和删除尾部的元素效率高,插入和删除头部的元素效率低,性能为 O ( n ) O(n) O(n)
并且,在变化容量的时候,比较耗时。

完整代码及测试

//
// Created by MAC on 2020/9/26.
//

#ifndef DATASTRUCTURE_ARRAYLIST_H
#define DATASTRUCTURE_ARRAYLIST_H


#include "LinearList.h"
#include <iostream>

using std::cout;
using std::cerr;
using std::endl;
using std::bad_alloc;
using std::min;

template<class T>
class ArrayList : public LinearList<T>{
    
    
private:
    class arrayList_iterator{
    
    
    protected:
        T* pos;
    public:
        arrayList_iterator(T *pos = nullptr) : pos(pos) {
    
    }

        // 测试相等
        bool operator==(const arrayList_iterator &rhs) const {
    
    
            return pos == rhs.pos;
        }

        bool operator!=(const arrayList_iterator &rhs) const {
    
    
            return pos != rhs.pos;
        }

        // 解引用运算符
        T& operator*(){
    
    
            return *pos;
        }

        T* operator->(){
    
    
            return pos;
        }

        // 前置加
        arrayList_iterator& operator++(){
    
    
            ++pos;
            return *this;
        }
        // 后置加
        arrayList_iterator operator++(int){
    
    
            iterator old = *this;
            ++pos;
            return old;
        }
    };

public:
    typedef arrayList_iterator iterator;
    ArrayList(int capacity = DEFAULT_SIZE):capacity(capacity){
    
    
        if(capacity<=0){
    
    
            throw "Illegal parameter.";
        }
        arr = new T[capacity];
    }

    virtual ~ArrayList() {
    
    
        delete [] arr;
    }

    ArrayList(const ArrayList<T> & b);

    ArrayList<T>& operator=(const ArrayList<T> & b);

    T& get(int index) const override ;

    T& operator[] (int index){
    
    
        check(index);
        return arr[index];
    }

    const T& operator[] (int index) const{
    
    
        check(index);
        return arr[index];
    }

    void insert(int index, const T &t)  override ;

    void push_back(const T &t) override {
    
    
        insert(this->length,t);
    }

    void push_front(const T &t) override {
    
    
        insert(0,t);
    }

    void erase(int index) override ;

    void pop_back() override {
    
    
        erase(this->length-1);
    }

    void pop_front() override {
    
    
        erase(0);
    }

    void clear() override ;

    void resize(int new_capacity);

    // 输出检测
    void printAll() const {
    
    
        for(int i=0;i<this->length;i++){
    
    
            cout<<arr[i]<<" ";
        }
        cout<<endl;
    }

    // 迭代器函数

    iterator begin(){
    
    
        return iterator(arr);
    }

    iterator end(){
    
    
        return iterator(arr+this->length);
    }


private:
    static const int DEFAULT_SIZE = 10;
    int capacity;
    T *arr = nullptr;

    void check(int index) const;

    void expand_capacity(){
    
    
        if(this->length==capacity){
    
    
//            cout<<"调用扩容函数"<<endl;
            resize(2*capacity);
        }
    }

    void reduce_capacity(){
    
    
        if(this->length<capacity/4 && capacity/2>=10){
    
    
//            cout<<"调用缩小容量函数"<<endl;
            resize(capacity/2);
        }
    }
};

template<class T>
ArrayList<T>::ArrayList(const ArrayList<T> & b){
    
    
    if(this==&b) return;
    if(arr!= nullptr) delete [] arr;
    arr = new T[b.capacity];
    capacity = b.capacity;
    this->length = b.length;
    for(int i=0;i<this->length;i++){
    
    
        arr[i] = b.arr[i];
    }
}

template<class T>
ArrayList<T>& ArrayList<T>::operator=(const ArrayList<T> & b){
    
    
    if(this==&b) return *this;
    if(arr!= nullptr) delete [] arr;
    arr = new T[b.capacity];
    capacity = b.capacity;
    this->length = b.length;
    for(int i=0;i<this->length;i++){
    
    
        arr[i] = b.arr[i];
    }
    return *this;
}



template<class T>
T& ArrayList<T>::get(int index) const {
    
    
    check(index);
    return arr[index];
}


template<class T>
void ArrayList<T>::check(int index) const {
    
    
    if(index<0 || index>=this->length){
    
    
        throw "Index out of bounds";
    }
}


template<class T>
void ArrayList<T>::insert(int index, const T &t) {
    
    
    if(index<0 || index>this->length){
    
    
        throw "Illegal parameter.";
    }
    expand_capacity();
    if(index==this->length){
    
    
        arr[this->length] = t;
    }else{
    
    
        for(int pos = this->length;pos>index;pos--){
    
    
            arr[pos] = arr[pos-1];
        }
        arr[index] = t;
    }
    this->length++;
}


template<class T>
void ArrayList<T>::erase(int index) {
    
    
    check(index);
    if(index<this->length-1){
    
    
        for(int pos=index+1;pos<=this->length-1;pos++){
    
    
            arr[pos-1] = arr[pos];
        }
    }
    arr[--this->length].~T(); // 如果这里不释放的话,T元素的内部指针指向的那块空间就再也释放不了
    reduce_capacity();
}

template<class T>
void ArrayList<T>::clear() {
    
    
    delete [] arr;
    arr = new T[DEFAULT_SIZE];
    capacity = DEFAULT_SIZE;
    this->length = 0;
}

template<class T>
void ArrayList<T>::resize(int new_capacity){
    
    
    if(new_capacity<0){
    
    
        throw "Illegal parameter.";
    }
    try{
    
    
        T* a = new T[new_capacity];
        for(int i=0;i<std::min(new_capacity,this->length);i++){
    
    
            a[i] = arr[i];
        }
        delete [] arr;
        arr = a;
        capacity = new_capacity;
        this->length = min(new_capacity,this->length);

    } catch (bad_alloc e) {
    
    
        cerr<<"Out of memoty."<<endl;
        exit(1);
    }
}
#endif //DATASTRUCTURE_ARRAYLIST_H

  • 测试代码
//
// Created by MAC on 2020/9/26.

#include "ArrayList.h"
using namespace std;

struct Person{
    
    
    string name;
    int age;
    Person() {
    
    }
    Person(const string &name, int age) : name(name), age(age) {
    
    }
    friend ostream & operator<<(ostream&os,const Person& person){
    
    
        os<<person.name<<"  "<<person.age<<"    ";
        return os;
    }
};

int main(){
    
    
    ArrayList<Person> arrayList;
    arrayList.push_back(Person("Green",10));
    arrayList.push_back(Person("Brown",12));
    arrayList.push_back(Person("White",15));
    arrayList.push_back(Person("Red",16));

    for(ArrayList<Person>::iterator it = arrayList.begin();it!=arrayList.end();it++){
    
    
        cout<<*it<<endl;
    }
    return 0;
}

链表描述

链表的种类

  • 单向链表
    每个节点的存储 元素和指向下一个节点的指针。
  • 双向链表
    每个节点的存储 元素,指向下一个节点的指针 以及 指向前一个节点的指针。
  • 循环链表
    头结点的pre指针指向尾结点,尾结点的next指针指向头结点

为了避免对头结点何尾结点特殊分析,我们增加两个特殊的哨兵节点,head和tail。

性能分析

随机访问,效率很低: O ( n ) O(n) O(n)
但是不论是插入和删除,效率都很高: O ( 1 ) O(1) O(1)
另外,由于没有扩容机制——所有节点都是按需分配的,所以性能没有波动。
空间上,一方面,没有开辟一块连续的内存空间,高效使用了计算机内存。
另一方面,每个节点为了能联系下一个节点,有必须存储下一个节点的地址。

完整代码及测试

//
// Created by MAC on 2020/10/4.
//

#ifndef DATASTRUCTURE_LINKEDLIST_H
#define DATASTRUCTURE_LINKEDLIST_H

#include "LinearList.h"

template<class T>
class LinkedList : public LinearList<T>{
    
    
private:

    class Node{
    
    
    public:
        T val;
        Node* next = nullptr, *previous = nullptr;
        Node() {
    
    }
        Node(T val) : val(val) {
    
    }
    };

    class linkedList_iterator{
    
    
    private:
        Node* pos;
    public:
        linkedList_iterator(Node *pos = nullptr) : pos(pos) {
    
    }

        bool operator==(const linkedList_iterator &rhs) const {
    
    
            return pos == rhs.pos;
        }

        bool operator!=(const linkedList_iterator &rhs) const {
    
    
            return pos != rhs.pos;
        }

        T& operator*(){
    
    
            return pos->val;
        }

        T* operator->(){
    
    
            return &(pos->val);
        }

        linkedList_iterator& operator++(){
    
    
            pos = pos->next;
            return *this;
        }

        linkedList_iterator operator++(int){
    
    
            Node* temp = pos;
            pos = pos->next;
            return iterator(temp);
        }
    };


    Node*head,*tail;
    void check(int index) const;

public:
    typedef linkedList_iterator iterator;
    LinkedList() ;

    virtual ~LinkedList() ;

    LinkedList(const LinkedList<T>&) = delete ;

    LinkedList<T>& operator=(const LinkedList<T>&) = delete ;

    virtual T &get(int index) const;

    virtual void insert(int index, const T &t) ;

    virtual void push_back(const T &t) ;

    virtual void push_front(const T &t) ;

    virtual void pop_back() ;

    virtual void pop_front() ;

    virtual void erase(int index) ;

    virtual void clear() ;

    // 输出检测
    void printAll() const {
    
    
        Node* cur = head->next;
        while(cur!=tail){
    
    
            cout<<cur->val<<" ";
            cur = cur->next;
        }
        cout<<endl;
    }

    iterator begin(){
    
    
        return iterator(head->next);
    }

    iterator end(){
    
    
        return iterator(tail);
    }

};

template<class T>
LinkedList<T>::LinkedList() {
    
    
    head = new Node;
    tail = new Node;
    head->next = tail;
    tail->previous = head;
}

template<class T>
LinkedList<T>::~LinkedList() {
    
    
    Node *cur = head,*temp;
    while(cur!= nullptr){
    
    
        temp = cur->next;
        delete cur;
        cur = temp;
    }
}

template<class T>
T& LinkedList<T>::get(int index) const {
    
    
    check(index);
    Node *cur = head->next;
    for(int i=0;i<index;i++){
    
    
        cur = cur->next;
    }
    return cur->val;
}

template<class T>
void LinkedList<T>::insert(int index, const T &t) {
    
    
    if(index==this->length){
    
    
        push_back(t);
        return;
    }
    check(index);
    Node* cur = head;
    for(int i=0;i<index;i++){
    
    
        cur = cur->next;
    }
    Node* newNode = new Node(t);
    newNode->next = cur->next;
    cur->next->previous = newNode;
    newNode->previous = cur;
    cur->next = newNode;
    this->length++;
}

template<class T>
void LinkedList<T>::push_back(const T &t) {
    
    
    Node* newNode = new Node(t);
    newNode->previous = tail->previous;
    tail->previous->next = newNode;
    newNode->next = tail;
    tail->previous = newNode;
    this->length++;
}

template<class T>
void LinkedList<T>::push_front(const T &t) {
    
    
    Node* newNode = new Node(t);
    newNode->next = head->next;
    head->next->previous = newNode;
    newNode->previous = head;
    head->next = newNode;
    this->length++;
}

template<class T>
void LinkedList<T>::erase(int index) {
    
    
    check(index);
    Node* temp = head;
    for(int i=0;i<index;i++){
    
    
        temp = temp->next;
    }
    Node* cur = temp->next;
    temp->next = cur->next;
    cur->next->previous = temp;
    delete cur;
    this->length--;
}

template<class T>
void LinkedList<T>::pop_back() {
    
    
    Node* cur = tail->previous;
    tail->previous = cur->previous;
    cur->previous->next = tail;
    delete cur;
    this->length--;
}

template<class T>
void LinkedList<T>::pop_front() {
    
    
    Node* cur = head->next;
    head->next = cur->next;
    cur->next->previous = head;
    delete cur;
    this->length--;
}

template<class T>
void LinkedList<T>::clear() {
    
    
    Node *cur = head->next,*temp;
    while(cur!=tail){
    
    
        temp = cur->next;
        delete cur;
        cur = temp;
    }
    this->length = 0;
}

template<class T>
void LinkedList<T>::check(int index) const {
    
    
    if(index<0 || index>=this->length){
    
    
        throw "Index out of bounds";
    }
}
#endif //DATASTRUCTURE_LINKEDLIST_H

数组描述与链表描述的对比

  • 在对随机访问有着高性能要求的场景,使用数组描述。
  • 在随机访问稀疏,插入删除频繁的场景,使用链表。
  • 在存储元素是一个很大的对象时,比较适合使用链表。

猜你喜欢

转载自blog.csdn.net/qq_44846324/article/details/108920234
今日推荐