数据结构之List

数据结构之 List

写在前面

和vector/array一样,链表/列表(list)也是线性表中的一种,即每个元素的直接前驱和直接后继都唯一的线性逻辑结构。

链表/列表List

  • 这里依然是放在一起讲,链表是通常的数据结构课上常见的一种结构,而列表是STL中已经封装好的一个模板类。所以我们在学习链表的时候,完全可以看看STL中的源码,对比学习,借鉴一些比较好的思想。值得注意的是,本文中的列表只按照STL的思想来,但代码是自己实现的,和源代码并不一致。
  • 与vector一样,列表也是由集合S中的元素依次排列的一个序列,这些元素分散地存储在存储空间中,因此难以直接根据地址直接访问到每一个元素。为此,每个元素存储时需携带一些额外的信息:它的前驱、后继等。
  • 同样地,参考vector,可以定义列表的前驱、后继、直接前驱、直接后继、前缀、后缀等。
  • 根据所包含额外信息的不同,链表可分为
    • 单链表:只包含指向后继结点的指针
    • 双链表:包含指向前驱结点和后继节点的指针
    • 单循环链表:最后一个结点的后继指针指向头结点
    • 双循环链表:最后一个结点的后继指针指向头结点,第一个结点的前驱指针指向最后一个结点
  • 在已经封装好的列表中,是按照双链表来设置结点对象的,用的时候按需取用就好。

list的实现

结点的图形化表示

结点构造

结点类
#ifndef LISTNODE_H
#define LISTNODE_H

typedef int Rank;
#define ListNodePosi(T) ListNode<T>*

template <typename T>
struct ListNode {
    // member variable
    T data;
    ListNodePosi(T) pred; //前驱
    ListNodePosi(T) succ; //后继
    // member function
    ListNode() {}
    ListNode(T e, ListNodePosi(T) p = NULL, ListNodePosi(T) s = NULL)
        : data(e), pred(p), succ(s) {}

    ListNodePosi(T) insertAsPred(T const& e);
    ListNodePosi(T) insertAsSuss(T const& e);
};

template <typename T>
ListNodePosi(T) ListNode<T>::insertAsPred(T const& e) {
    ListNodePosi(T) node = new ListNode<T>(e);
    node->succ = pred->succ;
    node->pred = pred;
    pred->succ = node;
    pred = node;
}

template <typename T>
ListNodePosi(T) ListNode<T>::insertAsSuss(T const& e) {
    ListNodePosi(T) node = new ListNode<T>(e);
    node->pred = succ->pred;
    node->succ = succ;
    succ->pred = node;
    succ = node;
}

#endif
列表的图形化表示
加上这个索引就是单循环链表
头结点
a0
a1
a2
a3
a4
a5
a0
a1
a2
a3
a4
a5
  • 这里需要说明的是,链表分为带表头和不带表头两种,如上图所示。在具体操作时两种形式有所区别,一般来说,带表头的操作方便一些,列表中就是带表头的。
列表类
#ifndef LIST_H
#define LIST_H

// #include "ListNode.cpp"
#include "ListNode.h"

template <typename T>
class List {
   private:
    int _size;
    ListNodePosi(T) header;
    ListNodePosi(T) tailer;

   protected:
    void init();                           // initial after creating
    int clear();                           // clear all nodes
    void copyNodes(ListNodePosi(T), int);  // copy n nodes from p
    void merge(ListNodePosi(T) &,
               int,
               List<T>&,
               ListNodePosi(T),
               int);  // merge two sorted lists
    //有序列表的归并:当前列表中自p起的n个元素,与列表L中自q起的m个元素归并
    void mergeSort(ListNodePosi(T) &, int);  //对从p开始的n个节点归并排序
    void selectionSort(ListNodePosi(T), int);  //对从p开始的n个节点选择排序
    void insertionSort(ListNodePosi(T), int);  //对从p开始的n个节点插入排序

   public:
    List() { init(); }
    List(List<T> const& L);                  // copy whole List
    List(List<T> const& L, Rank r, Rank n);  // copy n elements in L from r
    List(ListNodePosi(T) p, int n);
    ~List();

    Rank size() const { return _size; }
    bool empty() const { return _size <= 0; }
    T& operator[](int r) const;
    ListNodePosi(T) fisrt() const { return header->succ; }
    ListNodePosi(T) last() const { return tailer->pred; }
    bool valid(ListNodePosi(T) p) {
        return p && (header != p) && (tailer != p);
    }
    int disordered() const;
    ListNodePosi(T) find(T const& e) const { find(e, _size, tailer); }
    ListNodePosi(T)
        find(T const& e,
             int n,
             ListNodePosi(T) p);  // find e in range n before p from p
    ListNodePosi(T) search(T const& e) const { search(e, _size, tailer); }
    ListNodePosi(T) search(T const& e, int n, ListNodePosi(T) p);
    ListNodePosi(T)
        selextMax(ListNodePosi(T) p, int n);  // select max node from p to p-n
    ListNodePosi(T) selextMax() { return selextMax(header->succ, _size); }

    ListNodePosi(T) insertAsFirst(T const& e);
    ListNodePosi(T) insertAsFirst(T const& e);
    ListNodePosi(T) insertBefore(ListNodePosi(T) p, T const& e);
    ListNodePosi(T) insertAfter(ListNodePosi(T) p, T const& e);
    T remove(ListNodePosi(T) p);
    void merge(List<T>& L) {
        merge(fisrt(), _size, L, L.fisrt(), L.size());
    }                                     // merge the whole list L
    void sort(ListNodePosi(T) p, int n);  // sort the part of list
    void sort() { sort(first(), _size); }
    int deduplicate();  // deduplicate a unordered list
    int uniquify();     // uniquify an ordered list
    void reverse();     // reverse the list

    void traverse(void (*)(T&));
    template <typename VST>  // operator
    void traverse(VST&);
};

#endif

与Vector的比较

  • 循秩访问与循位置访问
    • vector最大的特点就是循秩访问,也就是可以根据元素的物理地址直接访问该元素。
    • 由于不能直接给出元素所在的物理地址,因此当需要访问某个元素时,只能从头结点依次向后查询,直到找到该元素或者访问到最后一个结点为止,后一种情况时该元素不存在与列表中。
  • 带来的利弊
    • vector可以随机访问,因此当有很多的读取等静态操作时,使用vector效率较高
    • 但对于insert和remove等动态操作,vector的实现时间复杂度为O(n),而list只需新开辟一块地址空间存入新的元素并修改相邻元素的指针或者修改完指针再回收地址空间即可实现,其时间复杂度不过O(1)
  • 综上所述,当需求中静态访问操作较多时,使用vector效率高;当需求中增删操作较多时,使用list效率更高。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值