C++实现顺序表(数组和链表)

    在C++中,引入了类的概念,要用类来实现顺序表,一定要注意代码的复用,这样会使实现更加容易。

数组实现顺序表:

        定义三个指针,_first指向数组开始位置,_finish指向最后一个元素的下一个位置,_endofstorage指向数组末尾的下一个位置。如图所示,假设数组中有元素1234567:


构造函数:让三个指针指向空;

typedef int DataType;

class Vector
{
public:
    Vector()
        :_first(NULL)
         ,_finish(NULL)
         ,_endofstorage(NULL)
    {}                                                                                                                               

    Vector(const Vector& v)
    {
        if(Size() > 0)
        {
            size_t size = v.Size();
            DataType* tmp = new DataType[size];
            memcpy(tmp, v._first, size * sizeof(DataType));
            _first = tmp;
            _finish = _first + size;
            _endofstorage = _first + size;
        }
        else
        {
            _first = _finish = _endofstorage = NULL;                                                                                 
        }
    }
    ~Vector()
    {
        delete[] _first;
    }
    size_t Size()const;
    size_t Capacity()const;
    //Vector& operator=(const Vector& v);
    Vector& operator=(Vector v);
    void expand(size_t n);
    void print(const char* msg)const;
    void PushBack(DataType x);
    void PopBack(); 
    void Insert(size_t pos, DataType x);
    void Erase(size_t pos);                                                                                                          
    size_t Find(DataType x);
    void Reserve(size_t n);
private:
    DataType* _first;
    DataType* _finish;                                                                                                               
    DataType* _endofstorage;
};
数组元素的个数:

        由于指针减指针结果为两指针间相差元素的个数,我们可以用_finish-_first来计算数组元素的个数。

size_t Vector::Size()const
{
    return _finish - _first;
}
数组容量的大小:

        计算方法与数组元素个数类似。

size_t Vector::Capacity()const
{
    return _endofstorage - _first;
}

对顺序表进行扩容:

若扩充到可以存放n个数据,

    如果n比原有数组容量大,进行扩容,重新申请空间,将原有数组的数据拷贝到新申请的空间上,在释放原来的数组空间,将指针指向新申请的空间;

    否则,什么都不做。

void Vector::expand(size_t n)
{
    if(n > Capacity())
    {
        size_t size = Size();
        DataType* tmp = new DataType[n];
        memcpy(tmp, _first, Size()*sizeof(DataType));
        delete[] _first;
        _first = tmp;
        _finish = _first + size;
        _endofstorage = _first + n;
    }
    return;
}                                                                                                                                    

赋值运算符的重载:

拿v1=v2来说,v1调用了operator=方法,在该方法中为*this,v2就是传进该方法的参数。

如果v2元素个数大于0,进行赋值操作:

1.若v1的容量小于v2的元素个数,说明v1放不下v2的所有元素,要进行扩容;

2.将v2的元素一次拷贝给v1;(可以自己实现,也可以用memcpy函数)

3.重新确定各个指针指向的位置。

否则,直接将_first和_finish置为NULL。

Vector& Vector::operator=(const Vector& v)
{
    if(v.Size() > 0)
    {
        if(v.Size() > Capacity())
            expand(v.Size());
        memcpy(_first, v._first, v.Size() * sizeof(DataType));
        _finish = _first + v.Size();
        _endofstorage = _first + v.Size();
    }
    else
    {
        _finish = _first;                                                                                                          
    }
    return *this;
}

以上是传统写法。还有一种写法叫现代写法,是传参时直接传vector,这样在调用时,会自动拷贝构造一个与v2数据相同的顺序表,假设为tmp;将*this的各个指针与tmp的交换。这时就赋值成功了。最后在调用完方法后,tmp的生命周期结束,系统会自动调用析构函数。


Vector& Vector::operator=(Vector v)
{
    swap(_first, v._first);
    swap(_finish, v._finish);
    swap(_endofstorage, v._endofstorage);
    return *this;
}                                                                                                                                    

往顺序表的某位置插入元素:

首先,检查插入位置是否合法;

其次,判断顺序表容量是否为空,若为空,扩容;

          判断顺序表是否满了,若满了,扩容;

再次,移动表中元素,以便新的元素插入合适的位置;

最后,插入元素。

void Vector::Insert(size_t pos, DataType x)
{
    assert(pos <= Size());
    if(Capacity() == 0)
        expand(1);
    if(Size() == Capacity())
        expand(2*Capacity());
    DataType* p = _first + pos;
    memmove(p+1, p, (_finish - p)*sizeof(DataType));
    *p = x;
    _finish++;
    return;
}                                                                                                                                    

删除某位置元素:

首先,判断顺序表是否为空,为空则直接返回;不为空,进行下面的操作;

其次,将要删除元素之后的元素向前移动一个位置;

最后,将_finish--。

void Vector::Erase(size_t pos)
{
    if(Size() == 0)
        return;
    assert(pos < Size());
    DataType* p = _first + pos;
    memmove(p, p+1, (_finish - p - 1)*sizeof(DataType));
    _finish--;
    return;
}                                                                                                                                    

查找元素位置:

定义一个指针指向第一个元素,遍历每个元素,并判断是否为要查找的元素,若是,则返回该指针-_first得到的值;直到遍历完都没有返回,即为没有找到该元素,返回(size_t)-1.

size_t Vector::Find(DataType x)                                                                                                      
{
    DataType* cur = _first;
    while(cur != _finish)
    {
        if(*cur == x)
            return cur - _first;
        cur++;
    }
    return (size_t)-1;
}

PushBack:

在Size()位置插入元素。

void Vector::PushBack(DataType x)
{
   // if(Capacity() == 0)
   //     expand(1);
   // if(Capacity() == Size())
   //     expand(2*Capacity());
   // *_finish++ = x;
   Insert(Size(), x);
    return;
}

PopBack:

考虑到代码的复用性,可以理解为删除Size()-1位置上的元素。

void Vector::PopBack()
{
//    if(Size() == 0)
//        return;
//    _finish--;
    Erase(Size() - 1);
    return;
}

带头结点的双向循环链表的实现:

如图,分别是带头结点的含有4个元素的双向循环链表和带头节点的空链表:


先定义结点,在这个中要有数据,指向下一个结点的指针,和指向前一个结点的指针。

typedef int DataType; 
struct ListNode
{
   ListNode* _next;
   ListNode* _prev;
   DataType _data;

   ListNode(DataType x)
       :_next(NULL)
        ,_prev(NULL)
        ,_data(x)
   {}  
};

然后定义链表的类:

构造函数:

        由于该链表是一个带头结点的链表,在构造函数中对链表进行初始化时要创建一个头结点,并把指针都指向自己。这样就初始化为了一个双向循环的空链表。

拷贝构造:

        这里采用深拷贝的方式进行。先用构造函数中的方法先对链表(假设A)进行初始化,然后遍历要拷贝的链表(假设B),将遍历的元素后插到链表A中。直到遍历完成。

析构函数:

        析构函数要对申请的所有空间进行释放,否则会造成内存泄漏。也就是说要释放每个结点,但要注意的是,再将所有节点释放完之前,不能释放头结点。

class List
{
    typedef ListNode Node;
public:
    List()
        :_head(new Node(DataType()))
    {
        _head->_next = _head;
        _head->_prev = _head;
    }
    List(List& l)
        :_head(new Node(DataType()))
    {
        _head->_next = _head;
        _head->_prev = _head;
        Node* cur = l._head->_next;
        while(cur != l._head)
        {
            PushBack(cur->_data);    
            cur = cur->_next;
        }
    }
    void PushBack(DataType x);
    void PushFront(DataType x);
    void PopBack();                                                                                                                    
    void PopFront();
    Node* Find(DataType x);
    void Insert(Node* pos, DataType x);
    void Erase(Node* pos);
    List& operator=(const List& l); 
    ~List()
    {
        Node* cur = _head->_next;
        while(cur != _head)
        {
            _head->_next = cur->_next;
            cur->_next->_prev = _head;
            delete cur;
            cur = _head->_next;
        }
        delete _head;
        _head = NULL;
    }
private:
    Node* _head;

};                                                                                                                                     

在某位置之前插入元素:

首先,检查pos是否合法;

其次,在链表中查找pos位置cur,并注意保存pos位置的前一个位置pre;

最后,创建结点并将结点连接到pre和cur之间的位置;

void List::Insert(Node* pos, DataType x)
{
    assert(pos);
    Node* cur = _head->_next;
    Node* pre = _head;
    while(cur != _head)
    {
        if(cur == pos)
        {
            break;
        }
        pre = cur;
        cur = cur->_next;
    }
    //注意如果用Node tmp(x);该函数调用完后,会自动调用析构函数,新创建的结点就析构了;此时如果去访问链表的每个节点,
    //不但会访问不到还可能会访问内存冲突
     Node* tmp = new Node(x);
     pre->_next = tmp;
     tmp->_prev = pre;
     tmp->_next = cur;
     cur->_prev = tmp;
     return;
}

删除某位置结点:

首先,检查pos是否合法;

其次,检查要删除的结点是否是头结点,如果是,直接返回;

再次,在该链表中查找该节点,并将该节点的前一个结点和后一个结点连接起来;

最后,再释放pos位置的结点。

void List::Erase(Node* pos)
{
    assert(pos);
    if(pos == _head)
    {
        return;
    }
    Node* cur = _head->_next;
    Node* pre = _head;
    while(cur != _head)
    {
        if(cur == pos)
        {
            pre->_next = cur->_next;
            cur->_next->_prev = pre;
            delete pos;
            return;
        }
        pre = cur;
        cur = cur->_next;
    }                                                                                                                                  
    return;
}

查找某元素位置:

        遍历一次链表,同时对比每个节点的值与要查找的元素是否相同,若相同,则返回该节点;否则,继续查找下一个结点。直到把链表遍历完,若还没找到,则返回NULL。

ListNode* List::Find(DataType x)
{
    Node* cur = _head->_next;
    while(cur != _head)
    {
        if(cur->_data == x)
            return cur;
        cur = cur->_next;
    }
    return NULL;
}                                                                                                                                      

PushBack:

        相当于在链表的末尾插入元素,即在头结点的前面插入元素。

void List::PushBack(DataType x)
{
    Insert(_head, x);
    return;
}

PushFront:

        类似的,相当于在头结点的后面插入元素。

void List::PushFront(DataType x)
{
    Insert(_head->_next, x);                                                                                                           
    return;
}

PopBack:

        删除头结点的前一个结点。

void List::PopBack()
{
    Erase(_head->_prev);
    return;
}

PopFront:

        删除头结点的下一个结点。

void List::PopFront()
{   
    Erase(_head->_next);                                                                                                               
    return;
}

赋值运算符的重载:

首先,要将this指向的链表的结点全部释放(除了头结点);

其次,类似于拷贝构造,将要拷贝的链表的每个元素后插到this指向的链表;

最后,返回*this。

List& List::operator=(const List& l)
{
    Node* cur = _head->_next;                                                                                                          
    Node* pre = _head;
    Node* next = cur->_next;
    while(cur != _head)
    {
        pre->_next = next;
        next->_prev = pre;
        delete cur;
        cur = next;
        next = next->_next;
    }
    cur = l._head->_next;
    while(cur != l._head)
    {
        PushBack(cur->_data);
        cur = cur->_next;
    }
    return *this;
}    


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值