第五章 链表

     链表是一种新的数据存储结构,有别于低效的、大小不可变的数组,它被广泛地应用在许多通用的数据库中。除非需要频繁的通过下标随机访问各个数据,否则在很多使用数组的地方都可以用链表代替。

一、链结点Link
     每个数据项都被包含在“链结点”中。一个链结点是某个类的对象,这个类可以叫做Link。每个Link对象中都包含一个对下一个链结点引用的字段(next)。链表本身的对象中有一个字段指向对第一个链结点的引用。

     class Link {
          public int iData;
          public Link next;
     }
     这是一种自引用式的类,因为它包含了一个和自己类型相同的字段。
     Q:这里我们提一个问题。在Link的类定义中定义一个Link类型的域,编译器怎样才能不混淆呢?编译器在不知道一个Link对象占多大空间的情况下,如何知道一个包含相同对象的Link对象占多大空间?
     A:在Java中,Link对象并没有真正包含另外一个Link对象。类型为Link的next字段仅仅是对另外一个Link对象的“引用”,而不是一个对象。一个引用是一个对某个对象的参照数值,它是一个计算机内存中的对象地址,然而不需要知道它的具体值。在给定的操作系统中,所有的引用,不管它指向谁,大小都是一样的。因此,对编译器来说,知道这个字段的大小并由此构造出整个Link对象,是没有任何问题的。
     所以,这里可以看出链表和数组的差别。在一个数组中,每一项占用一个特定的位置。这个位置可以用一个下标号直接访问。就像一排房子,你可以凭地址找到其中特定一间。但是在链表中,寻找一个特定元素的唯一方法就是沿着这个元素的链一直向下寻找,每个元素之间都有关系。

二、单链表的Java代码

class Link {                    //链结点
     public int iData;
     public Link next;

     public Link(int value) {
          iData = value;
     }

     public void displayLink() {
          System.out.print(iData + “   “);
     }
}

class LinkList {               //链表
     private Link first;//链表的第一个链结点,它是惟一的需要维护的永久信息,用以定位其它链结点

     public LinkList() {
     }
     
     public boolean isEmpty() {
          return first == null;
     }
          
     public void insertFirst(int id) {
          Link newLink = new Link(id);
          newLink.next = first;               //newLink - -> old first
          first = newLink;                      //first - -> newLink
     }

     public Link deleteFirst() {
          Link temp = first;
          first = first.next;                    //删除:first - -> old next
          return temp;
     }

     //查找到包含指定关键字的链结点
     public Link find(int key) {
          Link current = first;
          while(current.iData != key) {
               if(current.next == null) 
                    return null;
               else 
                    current = current.next;
          }
          return current;
     }

     //删除包含指定关键字的链结点
     public Link delete(int key) {
          Link current = first;
          Link previous = first;
          while(current.iData != key) {
               if(current.next == null)
                    return null;
               else {
                    previous = current;
                    current = current.next;
               }
               if(current == first)
                    first = first.next;
               else
                    previous.next = current.next;     //被删除的节点的链在此处断开
               return current;
          }
     }

     public void displayList() {
          System.out.print(“List (first - -> last): “);
          Link current = first;
          while(current != null) {
               current.displayLink();
               current = current.next;
          }
     }
}

三、双端链表
     双端链表新增一个特性:即对最后一个链结点的引用,就像对第一个链结点的引用一样。像表头一样访问表尾的特性,使双端链表更适合于一些普通链表不方便操作的场合,如队列。

class Link {
     public long data;
     public Link next;

     public Link(long value) {
          data = value;
     }

     public void displayLink() {
          System.out.print(data + “ “);
     }
}

class FirstLastLinkList {
     public Link first;          //指向链表中第一个链结点
     public Link last;          //指向链表中最后一个链结点
     //如果链表中只有一个链结点,两者都指向它,如果没有链结点,两者都是null
     
     public boolean isEmpty {
          return first == null;
     }

     public void insertFirst(long data) {
          Link newLink = new Link(data);

          if (isEmpty())               //如果链表是空的,必须把last指向新的链结点
               last = newLink;     //last引用到表尾
          newLink.next = first;
          first = newLink;          //从表头插入,指向新的链结点
     }

     public void insertLast(long data) {
          Link newLink = new Link(data);
         
          if (isEmpty())               //如果链表是空的,必须把first指向新的链结点
               first = newLink;     //first引用到表头
          else
               last.next = newLink;
          last = newLink;          //从表尾插入,指向新的链结点
     }

     public long deleteFirst() {
          long temp = first.data;

          if(first.next == null)     //如果只有一个数据项
               last = null;             //last - ->null
          first = first.next;         //first - ->old next
          return temp;
     }

     public void displayList() {
          System.out.print(“List (first - ->last): “);
          Link current = first;
          while(current != null) {
               current.displayLink();
               current = current.next;
          }
     }
}
     双端链表中,在表头重复插入操作会颠倒链结点进入的顺序,比如插入1,2,3,在表中是3,2,1.但是在表尾重复插入操作则保持链结点进入的顺序。
     然而,双端链表并不能高效地删除最后一个链结点,因为没有一个引用指向倒数第二个链结点。如果最后一个链结点被删除,倒数第二个链结点的next字段应该变成null。

四、链表的效率
     在表头插入和删除速度很快,仅需要改变一两个引用值,所以花费O(1)的时间。平均起来,查找、删除和插入都需要搜索链表中的一半链结点,需要O(N)次比较,在数组中执行这些操作也是O(N)次比较。当然,链表更快,因为链表不需要移动任何东西。此外,链表需要用多少内存就可以用多少内存,并且可以扩展到所有可用内存。而数组的大小在它创建时就固定了。
     向量是一种可扩展的数组,它可以通过可变长度解决这个问题,但是它经常只允许以固定大小的增量扩展,比如快要溢出时增加一倍数组容量。

五、抽象数据类型(ADT)
     它是一种考虑数据结构的方式:着重于它做了什么,而忽略它是怎么做的。栈和队列都是ADT。

class Link {
     public long data;
     public Link next;

     public Link(long value) {
          data = value;
     }
}

1、用链表实现的栈

class LinkList {
     private Link first;

     public boolean isEmpty() {
          return first == null;
     }

     public void insertFirst(long value) {
          Link newLink = new Link(value);
          newLink.next = first;
          first = newLink;
     }

     public long deleteFirst() {
          Link temp = first;
          first = first.next;
          return temp.data;
     }
}

class LinkStack {
     private LinkList list;
     
     public LinkStack() {
          list = new LinkList();
     }
     
     public void push(long value) {
          list.insertFirst(value);
     }
     
     public long pop() {
          return list.deleteFirst();
     }
     
     public boolean isEmpty() {
          return list.isEmpty();
     }
}

2、用链表实现的队列

class FirstLastList {
     private Link first;
     private Link last;

     public boolean isEmpty() {
          return first == null;
     }

     //从表尾插入数据,可以实现进入队列的效果
     public void insertLast(long value) {
          Link newLink = new Link(value);
          if(isEmpty()) 
               first = newLink;
          else 
               last.next = newLink;
          last = newLink;
     }

     //从表头弹出数据,可以实现弹出队列的效果
     public long deleteFirst() {
          long temp = first.data;
          if(first.next == null)
               last = null;
          first = first.next;
          return temp;
     }
}

class LinkQueue {
     private FirstLastList list;
     
     public LinkQueue() {
          list = new FirstLastList();
     }

     public boolean isEmpty() {
          return list.isEmpty();
     }

     public void insert(long value) {
          list.insertLast(value);
     }

     public long remove() {
          return list.deleteFirst();
     }
}

六、有序链表
     数据按照关键值有序排序的。可以用来实现优先级队列。在有序链表插入一个数据,关键在于遍历链表,寻找合适的插入位置,把新链结点的next字段指向下一个链结点,把前一个链结点的next字段指向新的链结点。
class SortedList {
     private Link first;

     public boolean isEmpty() {
          return first == null;
     }

     public void insert(long data) {
          Link newLink = new Link(data);
          Link previous = null;
          Link current = first;

          while(current != null && key > current.data) {
               previous = current;
               current = current.next;
          }
          
          if(previous == null)
               first = newLink;
          else
               previous.next = newLink;
          newLink.next = current;
     }
     
     public Link remove() {
          Link temp = first;
          first = first.next;
          return temp;
     }
}

     在有序链表中插入或删除数据必须沿着链表一步一步找到正确位置,因此需要O(N)次比较,平均N/2。然而只需要O(1)的时间就可以找到或删除最小值。

七、双向链表
     为什么使用双向链表?因此传统的链表难以沿链表的反向遍历。

class Link {
     public long data;
     public Link next;
     public Link previous;

     public  Link(long value) {
          data = value;
     }
}
     双向链表的缺点是每次插入或删除一个链结点的时候,要处理四个链结点的引用,因此链结点占用空间也变大了。
class DoubleLinkedList {
     private Link first;
     private Link last;

     public boolean isEmpty() {
          return first == null;
     }

     public void insertFirst(long value) {
          Link newLink = new Link(value);
          
          if(isEmpty())
               last = newLink;
          else
               first.previous = newLink;
          newLink.next = first;
          first = newLink;
     }

     public void insertLast(long value) {
          Link newLink = new Link(value);

          if(isEmpty())
               first = newLink;
          else {
               last.next = newLink;
               newLink.previous = last;
          }
          last = newLink;
     }

     public Link deleteFirst() {   
          Link temp = first;
          if(first.next == null)
               last = null;
          else
               first.next.previous = null;
          first = first.next;
          return temp;
     }

     public Link deleteLast() {
          Link temp = last;
          if(first.next == null)
               first = null;
          else
               last.previous.next = null;
          last = last.previous;
          return temp;
     }

     public boolean insertAfter(long key, long value) {
          Link current = first;
          while(current.data != key) {
               current = current.next;
               if(current == null)
                    return false;
          }
          Link newLink = new Link(value);
          
          if(current == last) {
               newLink.next = null;
               last = newLink;
          } else {
               newLink.next = current.next;
               current.next.previous = newLink;
          }
          newLink.previous = current;
          current.next = newLink;
          return true;
     }
     
     public Link deleteKey(long key) {
          Link current = first;
          while(current.data != key) {
               current = current.next;
               if(current == null) 
                    return null;
          }
          if (current == first)
               first = current.next;
          else
               current.previous.next = current.next;
          
          if(current == last)
               last = current.previous
          else
               current.next.previous = current.previous;

          return current;
     }

     public void displayForward() {
          Link current = first;
          while(current != null) {
               current.displayLink();
               current = current.next;
          }
     }

     public void displayBackward() {
          Link current = last;
          while(current != null) {
               current.displayLink();
               current = current.previous;
          }
     }
}

八、迭代器
     假定你要遍历一个链表,并在某些特定的链结点上执行一些操作。在数组中,这些操作很容易实现,因为可以利用数组下标。可是链表并没有下标,你要从表头开始考察每个链结点。
     作为类的用户,需要能存取指向任意链结点的引用。这样就可以考察和修改链结点。引用应该能递增,因此可以沿着整个链表遍历,依次查看每个链结点,而且可以访问这个引用所指向的链结点。这里,我们利用迭代器来包含对数据结构中数据项的引用。
class LinkList {
     private Link first;
     
     public Link getFirst() {
          return first;
     }

     public void setFirst(Link link) {
          first = link;
     }

     public ListIterator getIterator() {
          return new ListIterator(this);
     }
}

class ListIterator {
     private Link current;
     private Link previous;
     private LinkList ourList;

     public ListIterator(LinkList list) {
          ourList = list;
          reset();
     }

     public void reset() {
          current = ourList.getFirst;
          previous = null;
     }

     public boolean atEnd() {
          return current.next == null;
     }
          
     public void nextLink() {
          previous = current;
          current = current.next;
     }
     
     public Link getCurrent() {
          return current;
     }

     public void insertAfter(long data) {
          Link newLink = new Link(data);

          if(ourList.isEmpty()) {
               ourList.setFirst(newLink);
               current = newLink;
          } else {
               newLink.next = current.next;
               current.next = newLink;
               nextLink();
          }
     }

     public void insertBefore(long data) {
          Link newLink = new Link(data);

          if(previous == null) {
               newLink.next = ourList.getFirst();
               ourList.setFirst(newLink);
               reset();
          } else {
               newLink.next = previous.next;
               previous.next = newLink;
               current = newLink;
          }
     }

     public long deleteCurrent() {
          long value = current.data;
          
          if(previous == null) {
               ourList.setFirst(current.next);
               reset();
          } else {
               previous.next = current.next;
               if(atEnd())
                    reset();
               else
                    current = current.next;
          }
          return value;
     }
}
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值