数据结构之线性表List

数据结构之线性表List

与线性表相关的最重要的概念是“位置”,应该把线性表看成是数学序列的表现。线性表中常见的术语如下。

术语含义
长度length线性表当前存储的元素数目
表头head线性表的开始节点
表尾tail线性表的结尾节点

抽象数据类型ADT的创建

首先使用C++中的抽象类表示法正式定义线性表的ADT,抽象类指成员函数都被声明为“纯虚的”(pure virtual)的类。这里定义抽象类List,使得继承List的任何线性表实现都必须支持这些函数,并使用函数所规定的参数和返回类型。
通过将其写成一个C++模板增加线性表ADT的灵活性。

template <typename E> class List{
    private:
        void operator =(const List&){}
        List (const List&) {}
    public:
        List () {}
        virtual ~List() {}

        virtual void clear() = 0;
        virtual void insert(const E& item) = 0 ;
        virtual void append(const E& item) = 0;
        virtual E remove() = 0;
        virtual void moveToStart() = 0;
        virtual void moveToEnd() = 0;
        virtual void prev() = 0;
        virtual void next() = 0;
        virtual int length() const = 0;
        virtual int currPos() const = 0;
        virtual void moveToPos(int pos) = 0;
        virtual const E& getValue() const = 0;

};

顺序表array based list的实现

顺序表称为AList,继承了抽象类List,因此实现了List的所有成员函数。
顺序表把表中的元素定义为存储在数组的相邻单元中,因此在表尾插入和删除元素的操作只需要花费Θ(1)的时间。但是如果要在表头插入一个元素,意味着现有的n个元素都要向后移动一个位置,花费的时间就为Θ(n)。从表头删除一个元素也是如此。

#include "list.h"
#include <iostream>
using namespace std;

void Assert(int n, string s) {
	if (n)
		return;
	else
		cout << s<<endl;
}


template <typename E>
class AList : public List<E>
{
private:
    int maxSize;
    int listSize;
    int curr;
    E *listArray;

public:
    AList(int size = defaultSize) //这里可以设置初始化时顺序表的大小
    {
        maxSize = size;
        listSize = curr = 0;
        listArray = new E[maxSize];
    }

    ~AList() { delete[] listArray; }

    void clear()
    {
        delete[] listArray;
        listSize = curr = 0;
        listArray = new E[maxSize];
    }

    void insert(const E &it)
    {
        assert(listSize < maxSize, "List capacity exceeded");
        for (int i = listSize; i > curr; i--)
            listArray[i] = listArray[i - 1];
        listArray[curr] = it;
        listSize++;
    }

    void append(const E &it)
    {
        assert(listSize < maxSize, "List capacity exceeded");
        listArray[listSize++] = it;
    }

    E remove()
    {
        assert((curr) > 0 && (curr < listSize), "No element");
        E it = listArray[curr];
        for (int i = curr; i < listSize - 1; i++)
            listArray[i] = listArray[i + 1];
        listSize--;
        return it;
    }
    void moveToStart() { curr = 0; }
    void moveToEnd() { curr = listSize; }
    void prev()
    {
        if (curr != 0)
            curr--;
    }
    void next()
    {
        if (curr < listSize)
            curr++;
    }

    int length() const { return listSize; }
    int currPos() const { return curr; }
    void moveToPos(int pos)
    {
        assert((pos >= 0) && (pos <= listSize), "pos out of range");
        curr = pos;
    }
    const E &getValue() const
    {
        assert((curr >= 0) && (curr < listSize), "no current element");
        return listArray[curr];
    }
};

链表link list的实现

第二种实现线性表的方法是利用指针,这种表示法称为链表(link list)。和基于array的Alist不同,基于指针的Llist可以动态的为表中的新元素分配存储空间。

结点类link的实现

链表由一系列被称为结点node的对象组成,结点类同样被后面的栈和队列的链接实现方式重用。
结点的定义Link类包括存储元素值的element和存储下一个结点指针的next,这种只有下一个结点指针称为单链表(singly linked list),与之对应的是包括next和prev两个方向指针的双链表(doubly linked list)。

template <typename E> class Link{
public:
    E element;
    Link *next;
    Link(const E& elemval, Link* nextval=NULL){
        element = elemval;
        next = nextval;
    }
    Link (Link* nextval=NULL){
        next = nextval;
    }
};

单链表中curr指针应当指向当前元素,但实际指向的是当前元素的前一个。这是因为在插入元素时,如果将curr指向逻辑位置的元素,将不能便利地找到逻辑位置的前一个元素并修改其next指针。
如下图a,curr指针的逻辑位置是标号23和标号12的结点之间的竖线,而在这一位置插入一个标号为10的结点将得到图b。
在这里插入图片描述
为了解决当链表为空时remove和insert函数需要增加处理特殊情况的代码的问题,可以增加一个特殊的表头结点(header node),这一结点与表中的其他元素一样,但值被忽略,不被当作实际元素。
带有表头结点的单链表的初始状态

LList类的实现

LList定义如下

#include "list.h"
#include "Link.h"
#include <iostream>
using namespace std;


void Assert(int n, string s) {
	if (n)
		return;
	else
		cout << s<<endl;
}


template <typename E> class LList: public List<E> {
private:
    Link<E>* head;
    Link<E>* tail;
    Link<E>* curr;
    int cnt;

    void init(){
        curr = tail = head = new Link<E>;
        cnt = 0;
    }
    void removeall(){
        while(head!=nullptr){
            curr = head;
            head = head->next;
            delete curr;
        }
    }

public:
    LList(int size = defaultSize){
        init();
    }
    ~LList(){
        removeall();
    }
    void print() const;
    void clear() {removeall(); init();}
    void insert(const E& it){
        curr ->next = new Link<E>(it,curr->next);
        if(tail == curr) tail = curr->next;
        cnt++;
    }
    void append(const E& it){
        tail = tail->next = new Link<E>(it,nullptr);
        cnt++;
    }

    E remove(){
        Assert(curr->next!=nullptr,"no element");
        E it = curr->next->element;
        Link<E>* ltemp = curr->next;
        if(tail == curr->next) tail = curr;
        curr->next = curr->next->next;
        delete ltemp;
        cnt--;
        return it;
    }

    void moveToStart()
    {curr = head;}

    void moveToEnd(){
        curr = tail;
    }
    void prev(){
        if(curr == head) return;
        Link<E>* temp = head;
        While(temp->next != curr)
            temp = temp->next;
        curr = temp;
    }
    void next(){
        if(curr!=tail) curr = curr->next;
    }
    int length()const{return cnt;}
    int currPos() const{
        Link<E>* temp = head;
        int i;
        for (i=0;curr!=temp;i++)
            temp = temp->next;
        return i;
    }
    void moveToPos(int pos){
        Assert((pos>=0)&&(pos<=cnt),"position out of range");
        curr = head;
        for(int i=0;i<pos;i++)
            curr = curr->next;
    }

    const E& getValue() const{
        Assert(curr->next != nullptr,"No value");
        return curr->next->element;
    }
};

线性表两种实现方式的比较

顺序表的缺点是它的容量在创建时被固定了,而链表的元素个数没有限制。
然而顺序表对空间的利用率更高,链表则需要在存储元素值之外存储一个指针。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值