数据结构 - 线性表

2020-09-30  本文已影响0人  Whyn

[TOC]

前言

程序(Program)= 算法(Algorithm)+ 数据结构(Data Structure)

对于常用数据结构,有如下几种:

其中,数组链表队列 都属于 线性表(即由零个或多个数据元素组成的有限序列)

本篇主要对 线性表 进行简介。

:下文中的代码只是用于从底层原理对上层数据结构的实现,未经过严格的单元测试,可能存在严重漏洞,但着重于理解即可。

数组(Array)

定义:数组(Array)指的是用一段地址连续的存储单元依次存储线性表的数据元素。如下图所示:

Array

特性:存取数据时间复杂度为 O(1),插入/删除数据时间复杂度为 O(n)(对于尾元素的插入/删除时间复杂度为 O(1))。

因此,数组适用于插入/删除操作较少,随机访问/获取较多的场景。

基本操作:存储,获取,插入,删除

#pragma once
#ifndef  __ARRAY_H__
#define  __ARRAY_H__
#include <iostream>
#include <stdexcept>

namespace {
    void check(const void *arr, int arrayLength = 0, int pos = 0) throw(std::exception, std::out_of_range) {
        if (arr == nullptr) {
            throw std::exception("Null Pointer Exception!");
        }
        if (pos < 0 || pos >= arrayLength) {
            throw std::out_of_range("index out of range!");
        }
    }
}

template<typename T> void store(T arr[], int arrLength, int pos, T value) throw(std::exception, std::out_of_range) {
    check(arr, arrLength, pos);
    arr[pos] = value;
}

template<typename T> T get(const T arr[], int arrLength, int pos) throw(std::exception, std::out_of_range) {
    check(arr, arrLength, pos);
    return arr[pos];
}

template<typename T> void insert(T arr[], int arrLength, int pos, T value) throw(std::exception, std::out_of_range) {
    check(arr, arrLength, pos);
    for (int i = arrLength - 1; i > pos; --i) {
        arr[i] = arr[i - 1];
    }
    arr[pos] = value;
}

template<typename T> T remove(T arr[], int arrLength, int pos) throw (std::exception, std::out_of_range) {
    check(arr, arrLength, pos);
    T ele = arr[pos];
    for (int i = pos + 1, cur = pos; i < arrLength; ++i) {
        arr[cur++] = arr[i];
    }
    return ele;
}
#endif //  __ARRAY_H__

链表(Lined List)

线性表有两种存储方式:顺序存储结构链式存储结构

顺序存储结构 的典型使用就是 数组

定义链式存储结构顺序存储结构 的不同之处在于:其相邻两个元素在内存中不一定是相邻的,每个链式结点(Node)一般包含两个域:数据域指针域数据域 存储的是数据元素信息,指针域 存储的是下一结点的地址。如下图所示:

LinkedList

因此,链表的一般表现形式如下所示:

template<typename T> struct Node {
    Node(T ele, Node<T> *next) :element(ele), pNext(next) {}
    T element; // 数据域
    struct Node<T> *pNext; // 指针域
};

template<typename T> class LinkedList {

public:
    LinkedList(Node<T> head) {
        this->pHead = new Node<T>(NULL, head);
    }
    LinkedList(const std::initializer_list<T> &nodes) {
        Node<T> *pNext = nullptr;
        for (auto iter = std::rbegin(nodes); iter != std::rend(nodes); ++iter) {
            Node<T> *p = new Node<T>(*iter, pNext);
            pNext = p;
        }
        this->pHead = new Node<T>(NULL, pNext);
    }
    ~LinkedList() {
        Node<T> *pNode = this->pHead->pNext;
        delete this->pHead;
        Node<T> *pTemp = nullptr;
        while (pNode) {
            pTemp = pNode->pNext;
            delete pNode;
            pNode = pTemp;
        }
    }
}
#endif

特性:插入/删除时间复杂度为 O(1)(实际上插入/删除前要先遍历到该插入结点,因此,插入/删除的实际时间复杂度为 O(n),而后续该点的所有插入/删除操作时间复杂度才为O(1))。

因此,对于插入/删除数据越频繁的操作,链表的效率就越明显。

基本操作:存储,获取,插入,删除,清空

// LinkedList.h
#pragma once
#ifndef __LINKED_LIST__
#define __LINKED_LIST__
#include <initializer_list>

template<typename T> struct Node {
    Node(T ele, Node<T> *next) :element(ele), pNext(next) {}
    T element; // 数据域
    struct Node<T> *pNext; // 指针域
};

template<typename T> class LinkedList {

public:
    LinkedList(Node<T> head) {
        this->pHead = new Node<T>(NULL, head);
    }
    LinkedList(const std::initializer_list<T> &nodes) {
        Node<T> *pNext = nullptr;
        for (auto iter = std::rbegin(nodes); iter != std::rend(nodes); ++iter) {
            Node<T> *p = new Node<T>(*iter, pNext);
            pNext = p;
        }
        this->pHead = new Node<T>(NULL, pNext);
    }
    ~LinkedList() {
        this->clear();
        delete this->pHead;
    }
    Node<T>* operator[](int pos) const {
        Node<T> *p = this->pHead;
        int curPos = 0;
        while (curPos++ <= pos) {
            p = p->pNext;
        }
        return p;
    }

public:
    // 插入:在第 pos 位置插入 value
    Node<T>* insert(int pos, T value) {
        Node<T> *pNode = this->pHead;
        int curPos = 0;
        while (pNode && curPos++ < pos) {
            pNode = pNode->pNext;
        }
        Node<T> *p = new Node<T>(value, pNode->pNext);
        pNode->pNext = p;
        return p;
    }
    // 删除:删除第 pos 位置的结点
    bool remove(int pos) {
        int curPos = 0; 
        Node<T> *p = this->pHead;
        while (p && curPos++ < pos) {
            p = p->pNext;
        }
        if (!p) {
            return false;
        }
        Node<T> *pNodePos = p->pNext;
        p->pNext = p->pNext->pNext;
        delete pNodePos;
        return true;
    }
    // 改:设置 pos 位置的值
    bool set(int pos,T value) {
        int curPos = 0;
        Node<T> *pNode = this->pHead;
        while (pNode && curPos++ <= pos) {
            pNode = pNode->pNext;
        }
        if (!pNode) {
            return false;
        }
        pNode->element = value;
        return true;
    }

    // 查:获取 pos 位置的值
    Node<T> *get(int pos) {
        int curPos = 0;
        Node<T> *pNode = this->pHead;
        while (pNode && curPos++ <= pos) {
            pNode = pNode->pNext;
        }
        return pNode;
    }
    // 清空
    void clear() {
        Node<T> *p = this->pHead->pNext;
        while(p && p->pNext != nullptr) {
            Node<T> *temp = p;
            p = p->pNext;
            delete temp;
        }
        delete p;
        this->pHead->pNext = nullptr;
    }

private:
    Node<T> *pHead = nullptr; //头结点
};
#endif

栈(Stack)

定义:栈(Stack)是限定只在表尾进行插入和删除操作的线性表。

特性:栈的操作只有入栈和出栈,并且具备 后进先出(LIFO) 特点。

Stack

基本操作:压栈,出栈
:栈的实现可以基于线性表的 顺序存储结构(即数组) 或者是 链式存储结构(即链表)

顺序栈(采用数组实现)的优点是实现简单,存取时定位方便,缺点是创建时底层数组大小固定,无法扩容。而链栈(采用链表实现)的优点是栈的长度可以无限大,缺点是每个栈元素都额外携带一个指向上一个元素结点的指针域,内存开销大一些。

我们采用链表的形式实现栈。

// Stack.h
#pragma once
#ifndef __STACK_H__
#define __STACK_H__

#include <initializer_list>
#include <stdexcept>

template<typename T> struct Node {
    Node(T ele, Node *prev) :element(ele), prev(prev) {}
    T element;
    Node *prev;
};

template<typename T>
class Stack {
public:
    Stack() = default;
    Stack(const std::initializer_list<T> datas){
        for (auto &data : datas) {
            this->push(data);
        }
    }
    ~Stack() {
        this->clear();
    }
    const T& operator[](int index) const {
        int cur = 0;
        Node<T> *p = top;
        while (p && cur++ < index) {
            p = p->prev;
        }
        return p ? p->element : NULL;
    }
public:
    void clear() {
        Node<T> *p = top;
        Node<T> *temp = nullptr;
        while (p) {
            temp = p->prev;
            delete p;
            p = temp;
        }
    }

    void push(const T &value) {
        Node<T> *p = new Node<T>(value, top);
        top = p;
    }

    T pop() throw(std::exception) {
        if (top == nullptr) {
            throw std::exception("stack is empty!");
        }
        T value = top->element;
        Node<T> *p = top;
        top = top->prev;
        delete p;
        return value;
    }

private:
    Node<T> *top = nullptr; // 栈顶指示器
};
#endif

队列(Queue)

定义:队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

特性:队列只具有入队和出队操作,并且具备 先进先出(FIFO) 特点,从队尾插入,从队头取出。

Queue

基本操作:入队,出队。

队列与栈类似,同样可以由线性表的 顺序存储结构(即数组)链式存储结构(即链表) 来进行实现。

下面以基于链表的方式实现队列:

#pragma once
#ifndef __QUEUE_H__
#define __QUEUE_H__
#include <initializer_list>

template<typename T>
struct Node {
    Node(T ele, Node<T> *next) :element(ele), pNext(next) {}
    T element;
    Node<T> *pNext;
};

template<typename T>
class Queue {
public:
    Queue() = default;
    Queue(std::initializer_list<T> datas) {
        for (auto &data : datas) {
            this->enqueue(data);
        }
    }
    ~Queue() {
        this->clear();
        delete this->pFront;
    }

public:
    void clear() {
        Node<T> *p = nullptr;
        while (this->pFront != this->pRear) {
            this->dequeue();
        }
    }
    Queue& enqueue(const T &value) {
        Node<T> *p = new Node<T>(value, nullptr);
        this->pRear->pNext = p;
        this->pRear = p;
        return *this;
    }

    T dequeue() {
        if (this->pFront == this->pRear) {
            return NULL;
        }
        Node<T> *p = this->pFront->pNext;
        T value = p->element;
        this->pFront->pNext = p->pNext;
        if (p == this->pRear) {
            this->pRear = this->pFront;
        }
        delete p;
        return value;
    }
private:
    Node<T> *pFront = new Node<T>(NULL, nullptr); // 队头指针:指向链表头结点
    Node<T> *pRear = pFront; // 队尾指针
};
#endif

参考

上一篇下一篇

猜你喜欢

热点阅读