Java数据结构与算法-链表
(备注)摘录加总结------
常用的链表结构:单向链表、双端链表、双向链表、有序链表、有迭代器的链表。
链表是一种和数组类似的基础的数据结构,可以实现栈和队列这样的数据结构。
(1)单向链表(最简单的链表结构实现)
遍历时只能单向遍历。
以下基本实现的分析:①声明链表中的节点的个数和头结点,声明了这个头结点head就会在内存中开辟一个Node类型的空间,即一个节点。②初始化链表:长度置为0,头结点为空。③每一个节点需要有几部分内容就在节点类中去说明,此处声明为了一个内部类的形式,包含的有节点数据属性和下一节点指针,即将next下一节点的引用即可当作为指针连接包含在Node节点类型中。所以理解下这个包含关系,这种在类声明中包含该类型属性的方法就能在声明使用时获得下一个此对象的引用。④增加节点addHead时,因为Node节点类型声明了一个有参构造器,于是在初始化的时候就将其data属性赋值,然后的newHead.next=head;,即为一个指向操作,将新节点指向原来的头结点,理解这里的“指向”,是xxx.next=head,所以是指向。并且Head=newHead,并且让此时的头结点指向新节点。即一个指向头结点,并声明新头结点的过程。⑤一般在查找指定元素或者删除指定元素时会使用 current = current.next; 这样子的操作,是一种移位操作。也可以这么说,指向next在前,移位next在后。
public class SingleLinkedList { private int size;//链表节点的个数 private Node head;//头节点 public SingleLinkedList(){ size = 0; head = null; } //链表的每个节点类 private class Node{ private Object data;//每个节点的数据 private Node next;//每个节点指向下一个节点的连接 public Node(Object data){ this.data = data; } } //在链表头添加元素 public Object addHead(Object obj){ Node newHead = new Node(obj); if(size == 0){ head = newHead; }else{ newHead.next = head; head = newHead; } size++; return obj; } //在链表头删除元素 public Object deleteHead(){ Object obj = head.data; head = head.next; size--; return obj; } //查找指定元素,找到了返回节点Node,找不到返回null public Node find(Object obj){ Node current = head; int tempSize = size; while(tempSize > 0){ if(obj.equals(current.data)){ return current; }else{ current = current.next; } tempSize--; } return null; } //删除指定的元素,删除成功返回true public boolean delete(Object value){ if(size == 0){ return false; } Node current = head; Node previous = head; while(current.data != value){ if(current.next == null){ return false; }else{ previous = current; current = current.next; } } //如果删除的节点是第一个节点 if(current == head){ head = current.next; size--; }else{//删除的节点不是第一个节点 previous.next = current.next; size--; } return true; } //判断链表是否为空 public boolean isEmpty(){ return (size == 0); } //显示节点信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//当前链表只有一个节点 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果链表一个节点都没有,直接打印[] System.out.println("[]"); } } }
栈的使用若想用单向链表来实现的话,栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()。
public class StackSingleLink { //将之前声名的单向链表结构直接作为属性包含进来 private SingleLinkedList link; //初始化栈的过程即为初始化一个单向链表的过程 public StackSingleLink(){ link = new SingleLinkedList(); } //添加元素 public void push(Object obj){ link.addHead(obj); } //移除栈顶元素 public Object pop(){ Object obj = link.deleteHead(); return obj; } //判断是否为空 public boolean isEmpty(){ return link.isEmpty(); } //打印栈内元素信息 public void display(){ link.display(); } }
(2)双端链表(单向链表功能上的优化)
由于单向链表只能从一端开始进行遍历,双端链表多了一个链表尾的节点,可以方便多一个方向遍历,和在表尾添加元素。
public class DoublePointLinkedList { private Node head;//头节点 private Node tail;//尾节点 private int size;//节点的个数 private class Node{ private Object data; private Node next; public Node(Object data){ this.data = data; } } public DoublePointLinkedList(){ size = 0; head = null; tail = null; } //链表头新增节点 public void addHead(Object data){ Node node = new Node(data); if(size == 0){//如果链表为空,那么头节点和尾节点都是该新增节点 head = node; tail = node; size++; }else{ node.next = head; head = node; size++; } } //链表尾新增节点 public void addTail(Object data){ Node node = new Node(data); if(size == 0){//如果链表为空,那么头节点和尾节点都是该新增节点 head = node; tail = node; size++; }else{
//此处也是那种先指向后移位的思想,node就是tail的下一个元素 tail.next = node; tail = node; size++; } } //删除头部节点,成功返回true,失败返回false public boolean deleteHead(){ if(size == 0){//当前链表节点数为0 return false; } if(head.next == null){//当前链表节点数为1 head = null; tail = null; }else{ head = head.next; } size--; return true; } //判断是否为空 public boolean isEmpty(){ return (size ==0); } //获得链表的节点个数 public int getSize(){ return size; } //显示节点信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//当前链表只有一个节点 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果链表一个节点都没有,直接打印[] System.out.println("[]"); } } }
用双端链表的好处可以实现基本的队列数据结构:队列的insert是在双端链表尾部插入,删除是在表头。
public class QueueLinkedList { private DoublePointLinkedList dp; public QueueLinkedList(){ dp = new DoublePointLinkedList(); } public void insert(Object data){ dp.addTail(data); } public void delete(){ dp.deleteHead(); } public boolean isEmpty(){ return dp.isEmpty(); } public int getSize(){ return dp.getSize(); } public void display(){ dp.display(); } }
(3)有序链表(每次的插入都是一次从小到大的排序)
public class OrderLinkedList { private Node head; private class Node{ private int data; private Node next; public Node(int data){ this.data = data; } } public OrderLinkedList(){ head = null; } //插入节点,并按照从小打到的顺序排列 public void insert(int value){ Node node = new Node(value); Node pre = null; Node current = head; while(current != null && value > current.data){ pre = current; current = current.next; } if(pre == null){ head = node; head.next = current; }else{ pre.next = node; node.next = current; } } //删除头节点 public void deleteHead(){ head = head.next; } public void display(){ Node current = head; while(current != null){ System.out.print(current.data+" "); current = current.next; } System.out.println(""); } }
在有序链表中插入和删除某一项最多需要O(N)次比较,平均需要O(N/2)次,因为必须沿着链表上一步一步走才能找到正确的插入位置,然而可以最快速度删除最值,因为只需要删除表头即可,如果一个应用需要频繁的存取最小值,且不需要快速的插入,那么有序链表是一个比较好的选择方案。比如优先级队列可以使用有序链表来实现。
(4)双向链表(可实现双端队列)
public class TwoWayLinkedList { private Node head;//表示链表头 private Node tail;//表示链表尾 private int size;//表示链表的节点个数
//定义Node节点内部类,每个节点包含的属性内容 private class Node{ private Object data; private Node next; private Node prev; public Node(Object data){ this.data = data; } } public TwoWayLinkedList(){ size = 0; head = null; tail = null; } //在链表头增加节点 public void addHead(Object value){ Node newNode = new Node(value); if(size == 0){ head = newNode; tail = newNode; size++; }else{
//下面这两步在插入这个新的节点newNode, head.prev = newNode; newNode.next = head; head = newNode; size++; } } //在链表尾增加节点 public void addTail(Object value){ Node newNode = new Node(value); if(size == 0){ head = newNode; tail = newNode; size++; }else{ newNode.prev = tail; tail.next = newNode; tail = newNode; size++; } } //删除链表头 public Node deleteHead(){ Node temp = head; if(size != 0){ head = head.next; head.prev = null; size--; } return temp; } //删除链表尾 public Node deleteTail(){ Node temp = tail; if(size != 0){ tail = tail.prev; tail.next = null; size--; } return temp; } //获得链表的节点个数 public int getSize(){ return size; } //判断链表是否为空 public boolean isEmpty(){ return (size == 0); } //显示节点信息 public void display(){ if(size >0){ Node node = head; int tempSize = size; if(tempSize == 1){//当前链表只有一个节点 System.out.println("["+node.data+"]"); return; } while(tempSize>0){ if(node.equals(head)){ System.out.print("["+node.data+"->"); }else if(node.next == null){ System.out.print(node.data+"]"); }else{ System.out.print(node.data+"->"); } node = node.next; tempSize--; } System.out.println(); }else{//如果链表一个节点都没有,直接打印[] System.out.println("[]"); } } }