[数据结构]P1.1 链表结构
* 注: 本文/本系列谢绝转载,如有转载,本人有权利追究相应责任。 2019年4月8日
Stan Zhang 2019年4月8日 格物致知,经世致用。
[面试题]1.为什么要用链表?
数组具有的缺陷: 数组是长度固定类型固定的,并且它取值快,插入和删除慢。
链表正弥补了这样的不足,它是长度都可以灵活扩展的,另外它是插入和删除快,但是取值非常慢。
查找也比较慢,这是这种线性结构的通病,需要通过散列的思路进行解决,当然一个例外是
有下标的有序线性结构可以使用二分查找提高查找效率。
因此没有最好的,只有最合适的数据结构。
2.链表的分类.
A.单链表:
B:双端链表
C:有序链表 : 没有什么突出的优点,只是为了迎合扩展性(长度扩展)以及业务
故名思意,有序链表的序列是有序的。
先比较一下有序数组,有序数组牺牲插入效率来提升查找效率,配合二分查找(利用数组索引)可以LogN级别定位元素。
但是有序链表中有序并不能提升其查找效率(它没有数组的索引)。
List本生就是按照添加的顺序有序的,再引入一个排序标准没有必要,因此Java当中没有SortedList.
而Set本身没有顺序规则,可以设置一个排序,因此当需要使用有序的集合时,我们可以使用SortedSet.
D:双向链表
3.实现
A.单链表
单链表的特点是头部插入快,其他无论是查找、尾部插入还是删除,效率都是N级别的。
Java代码的实现:
package ds1.linked.table.singleDirection; /** * 单向链表 * 单向链表的表中会存留一个链表的起始端 Node first,这导致从尾部插入数据非常麻烦需要花费O(N)的时间 */ public class SingleDirectionList { static class Node { Node next; String data; Node(String data){ this.data = data; } } static class LinkedList{ Node first; // 保存第一个节点 public LinkedList(){ } public LinkedList(Node first) { this.first = first; } public void foreachPrint(){ if(first == null){ System.out.println("[]"); }else{ System.out.print("[" + this.first.data); Node current = first.next; while(current != null){ System.out.print("," + current.data); current = current.next; } System.out.println("]"); } } /** * 插入节点的时间复杂度: O(1) * @param node */ public void addNode(Node node){ if(first == null){ this.first = node; }else{ node.next = this.first; this.first = node; } } /** * 删除节点的时间复杂度: O(N) * @param node */ public void removeNode(Node node){ if(this.first == null){ return; } if(this.first.equals(node)){ this.first = this.first.next; node.next = null; return; } Node current = this.first; Node before = current; while(current.next != null){ if(current.equals(node)){ before.next = current.next; node.next = null; break; } before = current; current = current.next; } } /** * 查找节点的时间复杂度: O(N) * @param data * @return */ public Node findNodeByData(String data){ Node result = null; Node current = this.first; while(current != null){ if(current.data != null && current.data.equals(data)){ result = current; break; } current = current.next; } return current; } } public static void main(String[] args) { LinkedList linkedList = new LinkedList(); Node node1 = new Node("node1"); Node node2 = new Node("node2"); Node node3 = new Node("node3"); Node node4 = new Node("node4"); Node node5 = new Node("node5"); Node node6 = new Node("node6"); Node node7 = new Node("node7"); linkedList.addNode(node1); linkedList.addNode(node2); linkedList.addNode(node3); linkedList.addNode(node4); linkedList.addNode(node5); linkedList.addNode(node6); linkedList.addNode(node7); linkedList.foreachPrint(); Node tmpNode4 = linkedList.findNodeByData("node4"); linkedList.foreachPrint(); linkedList.removeNode(tmpNode4); linkedList.foreachPrint(); } }
结果:
[node7,node6,node5,node4,node3,node2,node1]
[node7,node6,node5,node4,node3,node2,node1]
[node7,node6,node5,node3,node2,node1]
B.双端链表
双端链表具有两个端点指针,分别指向收尾指针。它具有首位快速插入,首部快速删除的能力。但是因为还是单向的,因此尾部删除性能不佳。
Java代码:
package ds1.linked.table.twoHead; import ds1.linked.table.singleDirection.SingleDirectionList; /** * 双端列表 * 有两个端点 */ public class TwoHeadLinkedList { static class Node{ Node next; String data; public Node(String data) { this.data = data; } } static class LinkedList{ Node first; Node tail; public LinkedList(){ } public LinkedList(Node node){ this(); addNodeToHead(node); } /** * 从首部添加一个节点 * 常数时间 * @param node */ public void addNodeToHead(Node node){ if(this.first == null){ this.first = node; this.tail = node; }else{ node.next = this.first; this.first = node; } } /** * 删除头节点 * 常数时间 */ public void removeHead(){ if(this.first != null){ if(this.first == this.tail){ this.tail = null; } this.first = this.first.next; } } /** * 添加尾部节点 * 常数时间 * @param node */ public void addNodeTail(Node node){ if(this.tail != null){ this.tail.next = node; this.tail = node; }else{ this.first = this.tail = node; } } /** * 删除尾部节点 * 线性时间 */ public void removeTailNode(){ if(this.first == null){ return; } if(this.first == this.tail){ this.first = this.tail = null; return; } Node current = this.first; Node before = current; while(current.next != null){ before = current; current = current.next; } before.next = null; this.tail = before; } /** * 查找一个节点 * 时间复杂度: 线性时间 * @param data * @return */ public Node findByData(String data){ Node result = null; if(this.first == null){ return result; } Node current = this.first; while(current != null){ if(current.data != null && current.data.equals(data)){ result = current; break; } current = current.next; } return result; } /** * 删除一个节点 * 时间复杂度:线性时间 * @param node */ public void removeNode(Node node){ if(this.first == null){ return; } if(this.first.equals(node)){ this.first = node.next; } Node current = this.first; Node before = current; while(current != null){ if(current == node){ before.next = current.next; break; } before = current; current = current.next; } if(this.tail == node){ this.tail = before; } } public void foreachPrint(){ if(first == null){ System.out.println("[]"); }else{ System.out.print("[" + this.first.data); Node current = first.next; while(current != null){ System.out.print("," + current.data); current = current.next; } System.out.println("]"); } } } public static void main(String[] args) { LinkedList linkedList = new LinkedList(); Node node1 = new Node("node1"); Node node2 = new Node("node2"); Node node3 = new Node("node3"); Node node4 = new Node("node4"); Node node5 = new Node("node5"); Node node6 = new Node("node6"); Node node7 = new Node("node7"); linkedList.addNodeToHead(node1); linkedList.foreachPrint(); linkedList.addNodeToHead(node2); linkedList.foreachPrint(); linkedList.addNodeTail(node3); linkedList.foreachPrint(); linkedList.addNodeToHead(node4); linkedList.foreachPrint(); linkedList.addNodeTail(node5); linkedList.foreachPrint(); linkedList.addNodeToHead(node6); linkedList.foreachPrint(); linkedList.addNodeTail(node7); linkedList.foreachPrint(); Node tmpNode4 = linkedList.findByData("node4"); linkedList.foreachPrint(); linkedList.removeNode(tmpNode4); linkedList.foreachPrint(); } }
结果:
[node1]
[node2,node1]
[node2,node1,node3]
[node4,node2,node1,node3]
[node4,node2,node1,node3,node5]
[node6,node4,node2,node1,node3,node5]
[node6,node4,node2,node1,node3,node5,node7]
[node6,node4,node2,node1,node3,node5,node7]
[node6,node2,node1,node3,node5,node7]
C:有序链表 : 没有什么突出的优点,只是为了迎合扩展性(长度扩展/类型扩展)以及业务
package ds1.linked.table.orderedList; public class OrderedList { static class Node{ Node next; long data; public Node(long data) { this.data = data; } } static class LinkedList{ Node first; LinkedList(){ } public void foreachPrint(){ if(first == null){ System.out.println("[]"); }else{ System.out.print("[" + this.first.data); Node current = first.next; while(current != null){ System.out.print("," + current.data); current = current.next; } System.out.println("]"); } } /** * 插入过程,线性时间 * @param node */ public void insert(Node node){ if(this.first == null){ this.first = node; return; } if(this.first.data > node.data){ node.next = this.first; this.first = node; return; } Node current = this.first; Node before = current; while(current != null){ if(current.data > node.data){ node.next = current; before.next = node; break; } before = current; current = current.next; } if(current == null){ before.next = node; } } /** * 查找过程: 不能利用二分查找,还是线性时间 * @param data */ public Node findByValue(long data){ Node result = null; Node current = this.first; while (current != null){ if(current.data == data){ result = current; break; } current = current.next; } return result; } } public static void main(String[] args) { LinkedList linkedList = new LinkedList(); linkedList.insert(new Node(100)); linkedList.foreachPrint(); linkedList.insert(new Node(200)); linkedList.foreachPrint(); linkedList.insert(new Node(122)); linkedList.foreachPrint(); linkedList.insert(new Node(39)); linkedList.foreachPrint(); linkedList.insert(new Node(103)); linkedList.foreachPrint(); linkedList.insert(new Node(145)); linkedList.foreachPrint(); } }
结果:
[100] [100,200] [100,122,200] [39,100,122,200] [39,100,103,122,200] [39,100,103,122,145,200]
D:双向链表
双向链表很好的利用了双端和双向的优势,使得双端的插入删除操作变为常数级别。
package ds1.linked.table.twoHeadTwoDirection; /** * 双向链表 * 有双端点双向,也就意味着两头的插入删除效率都是线性的。 */ public class TwoHeadTwoDirectionList { static class Node { Node next; Node before; String data; Node(String data){ this.data = data; } } static class LinkedList{ Node first; // 保存第一个节点 Node tail; // 保留最后一个节点 public LinkedList(){ } public LinkedList(Node first) { this.first = this.tail = first; } public void foreachPrint(){ if(first == null){ System.out.println("[]"); }else{ System.out.print("[" + this.first.data); Node current = first.next; while(current != null){ System.out.print("," + current.data); current = current.next; } System.out.println("]"); } } /** * 插入头节点的时间复杂度:线性 * @param node */ public void addNodeToHead(Node node){ if(first == null){ this.first = this.tail = node; }else{ node.next = this.first; this.first = node; } } /** * 删除头节点的时间复杂度:线性时间 */ public void removeNodeFromHead(){ if(this.first != null){ Node tmp = this.first.next; this.first.next = null; this.first = tmp; } } /** * 尾部新增一个节点 * 线性时间 * @param node */ public void addNodeToTail(Node node){ if(this.tail == null){ this.tail = node; }else{ node.before = this.tail; this.tail.next = node; this.tail = node; } } /** * 从尾部删除节点 * 线性时间 */ public void removeNodeFromTail(){ if(this.tail != null){ Node tmp = this.tail; this.tail = this.tail.before; this.tail.next = null; tmp.before = null; } } /** * 删除节点的时间复杂度: O(N) * @param node */ public void removeNode(Node node){ if(this.first == null){ return; } if(this.first.equals(node)){ this.first = this.first.next; node.next = null; return; } Node current = this.first; Node before = current; while(current.next != null){ if(current.equals(node)){ before.next = current.next; before.next.before = before; node.next = null; node.before = null; break; } before = current; current = current.next; } } /** * 查找节点的时间复杂度: O(N) * @param data * @return */ public Node findNodeByData(String data){ Node result = null; Node current = this.first; while(current != null){ if(current.data != null && current.data.equals(data)){ result = current; break; } current = current.next; } return current; } } public static void main(String[] args) { LinkedList linkedList = new LinkedList(); Node node1 = new Node("node1"); Node node2 = new Node("node2"); Node node3 = new Node("node3"); Node node4 = new Node("node4"); Node node5 = new Node("node5"); Node node6 = new Node("node6"); Node node7 = new Node("node7"); linkedList.addNodeToHead(node1); linkedList.foreachPrint(); linkedList.addNodeToHead(node2); linkedList.foreachPrint(); linkedList.addNodeToTail(node3); linkedList.foreachPrint(); linkedList.addNodeToHead(node4); linkedList.foreachPrint(); linkedList.addNodeToTail(node5); linkedList.foreachPrint(); linkedList.addNodeToTail(node6); linkedList.foreachPrint(); linkedList.addNodeToHead(node7); linkedList.foreachPrint(); Node tmpNode4 = linkedList.findNodeByData("node4"); linkedList.foreachPrint(); linkedList.removeNode(tmpNode4); linkedList.foreachPrint(); linkedList.removeNodeFromHead(); linkedList.foreachPrint(); linkedList.removeNodeFromTail(); linkedList.foreachPrint(); linkedList.removeNodeFromTail(); linkedList.foreachPrint(); linkedList.removeNodeFromHead(); linkedList.foreachPrint(); } }
结果:
[node1]
[node2,node1]
[node2,node1,node3]
[node4,node2,node1,node3]
[node4,node2,node1,node3,node5]
[node4,node2,node1,node3,node5,node6]
[node7,node4,node2,node1,node3,node5,node6]
[node7,node4,node2,node1,node3,node5,node6]
[node7,node2,node1,node3,node5,node6]
[node2,node1,node3,node5,node6]
[node2,node1,node3,node5]
[node2,node1,node3]
[node1,node3]