欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

LinkList(双向链表实现)

程序员文章站 2022-06-02 22:43:28
LinkedList是用链表结构存储数据的,比较适合数据的动态插入和删除,随机访问和遍历速度比较慢,还提供了List接口i中没有定义的方法,专门用于操作表头和表尾的元素,所以可以当作堆栈、队列和双向队列来使用。LInkedList持有头节点和尾节点的引用,有两个构造器,一个是无参构造器,另一个是传入 ......

linkedlist是用链表结构存储数据的,比较适合数据的动态插入和删除,随机访问和遍历速度比较慢,还提供了list接口i中没有定义的方法,专门用于操作表头和表尾的元素,所以可以当作堆栈、队列和双向队列来使用。linkedlist持有头节点和尾节点的引用,有两个构造器,一个是无参构造器,另一个是传入外部集合构造器,它没有像arraylist一样的初始大小的构造器。

  1 //集合元素个数
  2 
  3 transient int size = 0;
  4 
  5  
  6 
  7 //头结点引用
  8 
  9 transient node<e> first;
 10 
 11  
 12 
 13 //尾节点引用
 14 transient node<e> last;
 15 
 16 //无参构造器
 17 public linkedlist() {} 
 18 
 19 //传入外部集合的构造器
 20 public linkedlist(collection<? extends e> c) {
 21     this();
 22     addall(c);
 23 }
 24 
 25 //增(添加)
 26 public boolean add(e e) {
 27   
 28      //在链表尾部添加
 29     linklast(e);
 30     return true;
 31 }
 32 
 33  
 34 //增(插入)
 35 public void add(int index, e element) {
 36 
 37     checkpositionindex(index);
 38 
 39     if (index == size) {
 40         //在链表尾部添加
 41         linklast(element);
 42     } else {
 43         //在链表中部插入
 44         linkbefore(element, node(index));
 45     }
 46 }
 47 
 48  
 49 
 50 //删(给定下标)
 51 public e remove(int index) {
 52 
 53     //检查下标是否合法
 54     checkelementindex(index);
 55     return unlink(node(index));
 56 }
 57 
 58 //删(给定元素)
 59 public boolean remove(object o) {
 60     if (o == null) {
 61         for (node<e> x = first; x != null; x = x.next) {
 62             if (x.item == null) {
 63                 unlink(x);
 64                 return true;
 65             }
 66         }
 67     } else {
 68         //遍历链表
 69         for (node<e> x = first; x != null; x = x.next) {
 70             if (o.equals(x.item)) {
 71                 //找到了就删除
 72                 unlink(x);
 73                 return true;
 74             }
 75         }
 76     }
 77     return false;
 78 }
 79 
 80  
 81 
 82 //改
 83 public e set(int index, e element) {
 84 
 85     //检查下标是否合法
 86     checkelementindex(index);
 87 
 88     //获取指定下标的结点引用
 89     node<e> x = node(index);
 90 
 91     //获取指定下标结点的值
 92 
 93     e oldval = x.item;
 94 
 95     //将结点元素设置为新的值
 96 
 97     x.item = element;
 98 
 99     //返回之前的值
100 
101     return oldval;
102 
103 }
104 
105 
106 //查
107 public e get(int index) {
108 
109     //检查下标是否合法
110     checkelementindex(index);
111 
112     //返回指定下标的结点的值
113     return node(index).item;
114 }

 

linkedlist的添加元素的方法主要是调用linklast和linkbefore两个方法,linklast方法是在链表后面链接一个元素,linkbefore方法是在链表中间插入一个元素。linkedlist的删除方法通过调用unlink方法将某个元素从链表中移除。下面是链表的插入和删除操作的核心代码。

 1 //链接到指定结点之前
 2 void linkbefore(e e, node<e> succ) {
 3 
 4     //获取给定结点的上一个结点引用
 5     final node<e> pred = succ.prev;
 6 
 7     //创建新结点, 新结点的上一个结点引用指向给定结点的上一个结点
 8     //新结点的下一个结点的引用指向给定的结点
 9     final node<e> newnode = new node<>(pred, e, succ);
10 
11     //将给定结点的上一个结点引用指向新结点
12     succ.prev = newnode;
13 
14     //如果给定结点的上一个结点为空, 表明给定结点为头结点
15     if (pred == null) {
16 
17         //将头结点引用指向新结点
18         first = newnode;
19     } else {
20         //否则, 将给定结点的上一个结点的下一个结点引用指向新结点
21         pred.next = newnode;
22     }
23 
24     //集合元素个数加一
25     size++;
26 
27     //修改次数加一
28     modcount++;
29 
30 }
31 
32  
33 
34 //卸载指定结点
35 e unlink(node<e> x) {
36 
37     //获取给定结点的元素
38     final e element = x.item;
39 
40     //获取给定结点的下一个结点的引用
41     final node<e> next = x.next;
42 
43     //获取给定结点的上一个结点的引用
44     final node<e> prev = x.prev;
45 
46     //如果给定结点的上一个结点为空, 说明给定结点为头结点
47     if (prev == null) {
48 
49         //将头结点引用指向给定结点的下一个结点
50         first = next;
51     } else {
52         //将上一个结点的后继结点引用指向给定结点的后继结点
53         prev.next = next;
54         //将给定结点的上一个结点置空
55         x.prev = null;
56 
57     }
58 
59     //如果给定结点的下一个结点为空, 说明给定结点为尾结点
60     if (next == null) {
61 
62         //将尾结点引用指向给定结点的上一个结点
63         last = prev;
64     } else {
65 
66         //将下一个结点的前继结点引用指向给定结点的前继结点
67         next.prev = prev;
68         x.next = null;
69 
70     }
71 
72     //将给定结点的元素置空
73     x.item = null;
74 
75     //集合元素个数减一
76     size--;
77     //修改次数加一
78     modcount++;
79     return element;
80 
81 }

通过源码的分析,对链表的插入和删除的时间复杂度都是o(1),而对链表的查找和修改操作都需要遍历链表进行元素的定位,这两个操作都是调用的node(int index) 方法定位元素,如下代码演示根据下标来定位元素。

 1 //根据指定位置获取结点
 2 node<e> node(int index) {
 3     //如果下标在链表前半部分, 就从头开始查起
 4     if (index < (size >> 1)) {
 5         node<e> x = first;
 6         for (int i = 0; i < index; i++) {
 7             x = x.next;
 8         }
 9         return x;
10     } else {
11         //如果下标在链表后半部分, 就从尾开始查起
12         node<e> x = last;
13         for (int i = size - 1; i > index; i--) {
14             x = x.prev;
15         }
16         return x;
17     }
18 }

通过下标定位时先判断是在链表的上半部还是下半部

上半部:从头开始找;

下半部:从尾开始找;

因此通过下标的查找和修改操作的时间复杂度是o(n/2),通过对双向链表的操作还可以实现单项队列,双向队列和栈的功能。

单向队列的操作的代码:

 1 //获取头结点
 2 public e peek() {
 3     final node<e> f = first;
 4     return (f == null) ? null : f.item;
 5 }
 6  
 7 //获取头结点
 8 public e element() {
 9     return getfirst();
10 }
11  
12 //弹出头结点
13 public e poll() {
14     final node<e> f = first;
15     return (f == null) ? null : unlinkfirst(f);
16 }
17  
18 //移除头结点
19 public e remove() {
20     return removefirst();
21 }
22  
23 //在队列尾部添加结点
24 public boolean offer(e e) {
25     return add(e);
26 }

双向队列的操作:

 1 //在头部添加
 2 public boolean offerfirst(e e) {
 3     addfirst(e);
 4     return true;
 5 }
 6  
 7 //在尾部添加
 8 public boolean offerlast(e e) {
 9     addlast(e);
10     return true;
11 }
12  
13 //获取头结点
14 public e peekfirst() {
15     final node<e> f = first;
16     return (f == null) ? null : f.item;
17  }
18  
19 //获取尾结点
20 public e peeklast() {
21     final node<e> l = last;
22     return (l == null) ? null : l.item;
23 }

栈操作:

1 //入栈
2 public void push(e e) {
3     addfirst(e);
4 }
5  
6 //出栈
7 public e pop() {
8     return removefirst();
9 }

对lindedlist,有:

1. linkedlist是基于双向链表实现的,不论是增删改查方法还是队列和栈的实现,都可通过操作结点实现

2. linkedlist无需提前指定容量,因为基于链表操作,集合的容量随着元素的加入自动增加

3. linkedlist删除元素后集合占用的内存自动缩小,无需像arraylist一样调用trimtosize()方法

4. linkedlist的所有方法没有进行同步,因此它也不是线程安全的,应该避免在多线程环境下使用

5. 以上分析基于jdk1.7,其他版本会有些出入,因此不能一概而论

以上内容部分借鉴于:https://blog.csdn.net/iteen/article/details/83109717