1.列表的介绍
列表是采用动态储存策略的典型结构,它的基本单位是节点。各节点通过引用或者指针彼此连接,在逻辑上构成一个线性序列。相邻节点彼此互称为前驱(predecessor)和后继(successor),如果前驱或后继存在则必然唯一,没有前驱(后继)的节点被称为首(末)节点(在有些列表的实际实现中,首(末)节点是拥有前驱(后继)的,它们被称为头(尾)节点,这两个节点并没有实际作用,也不会对外暴露,只是为了方便某些操作而产生的。)。
与向量不同的是,列表在物理上并不是一段连续的内存空间,所以无法通过秩直接访问其中的元素。因此列表更常用的是寻位置访问,也就是通过节点中的互相引用找到每个元素的位置。
2.列表的接口
作为组成列表的基本单位,节点至少需要这些接口:
操作 | 功能 |
pred() | 当前节点前驱节点的位置 |
succ() | 当前节点后继节点的位置 |
data() | 当前节点所存放的数据对象 |
insertAsPred(e) | 插入前驱节点,存入被引用对象e |
insertAsSucc(e) | 插入后继节点,存入被引用对象e |
由节点所组成的列表,则需要如下接口:
操作 | 功能 | 适用对象 |
size() | 报告列表当前的规模(节点总数) | 列表 |
first()、last() | 返回首、末节点的位置 | 列表 |
insertAsFirst(e)、insertAsLast(e) | 将e作为列表的首、末节点插入 | 列表 |
insertBefore(p, e)、insertAfter(p, e) | 将e作为节点p的前驱、后继插入 | 列表 |
remove(p) | 删除节点p | 列表 |
disordered() | 判断所有节点是否已按照非降序排列 | 列表 |
sort() | 调整各节点的位置,使之按非降序排列 | 列表 |
find(e) | 查找目标元素e | 列表 |
search(e) | 查找不大于e且秩最大的节点 | 有序列表 |
deduplicate() | 删除重复节点 | 列表 |
uniquify() | 删除重复节点 | 有序列表 |
traverse() | 遍历列表并统一处理所有节点,处理方法由函数对象指定 | 列表 |
/** * 列表的寻秩访问,时间复杂度O(n) * @param r 待访问元素的秩 * @return 对应秩的节点 */ public ListNode get(int r) { //判断秩是否合法 if (r >= this.size || r < 0) { throw new ArrayIndexOutOfBoundsException("下标越界"); } //得到列表的首节点 ListNode node = this.first(); //从首节点出发,依次指向后继节点,递推r次则得到秩为r的元素 while (r-- > 0) { node = node.succ(); } return node; } /** * 列表的前驱插入操作(后继插入操作正好相反),时间复杂度O(1) * @param node * @param e * @return */ public ListNode insertBefore(ListNode node, T e) { //增加列表的规模 this.size++; //构造一个新节点,新节点的前驱是该节点的前驱,新节点的后继是该节点。 ListNode<T> newNode = new ListNode(e, node.getPred(), node); //将该节点前驱的后继设为新节点 node.getPred().setSucc(node); //将该节点的前驱设为新节点 node.setPred(node); //返回新节点 return newNode; } /** * 删除一个合法位置的节点,时间复杂度O(1) * @param node 待删除的节点 * @return 该节点的元素 */ public T remove(ListNode<T> node) { //减少列表的规模 this.size--; //备份节点中的元素 T e = node.getDate(); //将该节点前驱的后继节点设为该节点的后继 node.getPred().setSucc(node.getSucc()); //将该节点后继的前驱节点设为该节点的前驱 node.getSucc().setPred(node.getPred()); //删除该节点的引用 node = null; //返回该节点的元素 return e; }
4.有序列表
有序列表同有序向量一样,再去重上会有更高的性能。列表和有序列表的去重操作与向量和有序向量类似,时间复杂度也相当:
/** * 列表的去重操作 时间复杂度O(n^2) * @return 删除的元素总数 */ public int deduplicate() { //记录去重之前的规模 int oldSize = this.size; //从首节点开始遍历 ListNode node = this.first(); //记录当前的位置 int r = 1; //依次遍历直到尾节点 while ((node = node.getSucc()) != this.trailer) { //在node的r个前驱中寻找是否有与它内容相同的节点 ListNode temp = this.find(node.getDate(), r, node); //如果存在相同的节点 删除该节点,否则增加前驱范围 if (temp != null) { this.remove(temp); } else { r++; } } //返回规模变化量 return oldSize - this.size; } /** * 有序列表的去重操作 时间复杂度O(n) * @return 删除的元素总数 */ public int uniquify() { //记录每个区间的第一个元素和下个区间的第一个元素的位置 int oldSize = this.size; //从首节点开始遍历 ListNode node = this.first(); //记录下一个节点 ListNode next; //依次遍历直到尾节点 while ((next = node.getSucc()) != this.trailer) { //删除每一个相同节点 if (node.getDate().equals(next.getDate())) { this.remove(next); } else { node = next; } } //返回规模变化量 return oldSize - this.size; }
但是由于列表是寻位置访问而不是寻秩访问,导致有序列表并不能在查找中产生更高的效率。所以有序列表的查找时间复杂度和普通列表一样都是O(n)。
注:本课为清华大学邓俊辉老师在学堂在线的Mooc课程《数据结构》,下次开课时间是2015年春季。如果感兴趣的同学可以去看看,个人觉得这门课无论广度还是深度都高于其他数据结构课。
原文地址:http://blog.csdn.net/sinat_19425927/article/details/42360725