链表
单向链表:
双向链表:
单向链表的一些理解:
通过定义一个节点类Node来表示链表结点,每个结点是这个类的一个对象
这个类包括一个Node类型的属性next用来保存下一个节点的地址,包括一个属性data用来保存这个节点的数据
如果这个链表是无环的,则最后一个节点的next为null
下面是单向链表的实现和一些操作:
public class MyLink { Node head; // 设置一个头部节点 Node tail; // 设置一个尾部节点 private static class Node{ Node next; // 创建一个Node类型的属性,用来保存后面的结点地址 // int data; // 这个表只能存储int型的数据类型 Object data; public Node(Object data){ this.data = data; } } // 向链表尾部或者头部添加节点 public void addNode(Object d){ Node newNode = new Node(d); // 方法一:添加节点到尾部 if(head==null){ head = newNode; return; } Node tmp = head; while(tmp.next != null){ tmp = tmp.next; } tmp.next = newNode; /* 方法二:添加节点到头部 Node newNode = new Node(d); newNode.next = head; head = newNode; */ } // 添加节点到指定位置 public void addNodeByIndex(int index, Object data){ if(index<0 || index>linkLength()+1) System.out.println(">>>超出添加范围,添加失败<<<"); else { Node newNode = new Node(data); if (index == 1) { // 添加到头部 newNode.next = head; head = newNode; } Node tmp = head; int i = 1; while (true) { // 添加到任意位置 if (i == index-1) { Node target = tmp.next; tmp.next = newNode; newNode.next = target; break; } i++; tmp = tmp.next; } } } // 删除指定位置的节点 public void deleteNode(int index){ if(index<1 || index>linkLength()){ System.out.println(">>>超出链表范围<<<"); } else { if (index == 1) { head = head.next; // 删除头结点 } int i = 1; // 先循环到找到指定节点的前一节点;这种方法可以吗 Node tmp = head; while (tmp.next != null) { if (i == index - 1) break; tmp = tmp.next; i++; } Node target = tmp.next; tmp.next = target.next; // 方法二:这种挺好 // int i = 1; // Node preNode = head; // Node curNode = preNode.next; // while (curNode != null) { // if (i == index-1) { // preNode.next = curNode.next; // return true; // } // preNode = curNode; // curNode = curNode.next; // i++; // } } } // 统计链表长度 public int linkLength(){ Node tmp = head; int len=0; // 这里不能用tmp.next!=null做条件,因为当第一个节点为null的时候会报错 while(tmp!=null){ tmp = tmp.next; len++; } return len; } // 打印链表数据 public void printLinkData(){ System.out.println("---------打印开始------------"); for(Node x=head; x!=null; x=x.next){ System.out.println(x.data); } System.out.println("---------打印结束------------"); // 打印方法二 // Node tmp = head; // while(tmp!=null){ // System.out.println(tmp.data); // tmp = tmp.next; // } } // 获取指定位置的链表数据 public Object getDataByindex(int index){ if(index<1 || index>linkLength()) return null; Node tmp = head; int i=0; while(tmp!=null){ if(i==index-1){ return tmp.data; } i++; tmp = tmp.next; } return null; } public static void main(String[] args){ // Node first = new Node(1); // Node second = new Node(2); // Node third = new Node(3); // first.next = second; // second.next = third; // for(Node x=first; x!=null; x=x.next){ // System.out.println(x.data); // } MyLink myLink = new MyLink(); myLink.addNode(1); myLink.addNode('a'); myLink.addNode("string"); myLink.addNode(3.4); myLink.addNode(5); myLink.printLinkData(); myLink.deleteNode(3); myLink.printLinkData(); System.out.println("length: " + myLink.linkLength()); System.out.println("getData: " + myLink.getDataByindex(4)); myLink.addNodeByIndex(5, "three"); myLink.printLinkData(); } }
链表反转:
递归比较难理解,参考:如何递归反转链表
public Node reverseLink(Node head){ Node pCurr = head; Node pPrev = null; while(pCurr!=null){ // 用一个简单的例子来理解过程 first second third Node pNext = pCurr.next; pCurr.next = pPrev; // 把null给第一个节点存储,第一个节点给第二个节点储存... 不断反转 pPrev = pCurr; pCurr = pNext; } this.head = pPrev; return this.head; } // 递归实现 public Node reverseLinkByRecursion(Node head){ if(head==null || head.next==null) return head; // 5 Node newLink = reverseLinkByRecursion(head.next); head.next.next = head; // 1->2->3->4->5 这里实现5->4 head.next = null; // 这里实现4—>null return newLink; // 返回尾结点 5 } // 递归实现反转前n个节点 Node successor = null; public Node reverseNLinkByRecursion(Node head, int n){ if(n==1){ // 1—>2->3->4->5 反转前3个,需要到3, 3->2->4,3->2->1->4 successor = head.next; // 记录n结点的下一个结点,4 return head; // 返回新的头结点 } Node newlink = reverseNLinkByRecursion(head.next, n-1); head.next.next = head; head.next = successor; return newlink; } // 实现反转链表m-n之间的结点 // m和n同速走,先到达m的位置,再反转后面的结点,例如反转2-4之间的结点 m==1的时候head=2, n=3(2,3,4) public Node reverseMN(Node head, int m, int n){ if(m==1){ return reverseNLinkByRecursion(head, n); } head.next = reverseMN(head.next, m-1, n-1); return head; }
链表排序:
// 链表排序(O(n^2)的时间复杂度) public void orderList(Node head){ Node tmp = head; while(tmp!=null){ Node pNext = tmp.next; while(pNext!=null){ if(tmp.data>pNext.data){ int temp = pNext.data; pNext.data = tmp.data; tmp.data = temp; } pNext = pNext.next; } tmp = tmp.next; } }
O(nlogn)时间复杂度的链表排序:归并排序+找链表的中间结点+合并两条有序链表
O(1)空间的链表插入排序:插入排序
链表的快速排序
// 方法三:快速排序 public ListNode quickSort(ListNode head, ListNode end){ if(head==end || head.next==end) return head; ListNode lhead = head, utail = head, p = head.next; while (p != end){ ListNode next = p.next; if(p.val < head.val){ // 如果p的值小于head的值,头插 p.next = lhead; lhead = p; // 维护小于head部分的头指针 } else { // 如果p的值大于等于head的值,尾插 utail.next = p; utail = p; // 维护大于等于head部分的尾指针 } p = next; } utail.next = end; ListNode node = quickSort(lhead, head); // 小于等于head的部分 head.next = quickSort(head.next, end); // 大于等于head的部分 return node; }
快慢指针查找中间节点:
快指针的速度比慢指针的快一倍,当快指针走到尾部的时候,慢指针正好走到中间
public void findMiddleData(Node head){ // 快慢指针,慢指针每次走一格,快指针每次走两格 Node p = this.head, q = this.head; while(p!=null && p.next!=null){ p = p.next.next; q = q.next; } System.out.println("Middle data: " + q.data); }
快慢指针查找链表是否有环,并且找到环的入口:
// 链表指定位置成环 // 要增加一个Node类型的成员变量记录尾结点,通过尾结点指向其他节点来成环 // 快慢指针查找链表是否有环 // 快指针的速度可以不是 2 而是其他的 3,4 之类的吗? 比较麻烦 public boolean findLoop(Node head){ Node fast = head, slow = head; while(fast!=null && fast.next!=null){ fast = fast.next.next; slow = slow.next; if(fast == slow) {return true; } }return false; } // 快慢指针找环的入口,相遇后慢指针再走a,起始点指针再走a,就是环的入口 public Node findLoopPort(Node head){ Node fast = head, slow = head; while(fast!=null && fast.next!=null) { fast = fast.next.next; slow = slow.next; if(fast==slow) break; } slow = head; while(slow != fast){ slow = slow.next; fast = fast.next; } return slow; }
查找倒数第K个元素(双指针):
用两个指针a、b,a先走K步,然后a,b同时同速走,a和b之间的差距为K,a到了尾部之后,b就到了倒数第K个元素;
删除倒数第K个元素:leetcode
从尾到头打印单链表(递归):
public void printLinkReversed(Node node){ if(node!=null){ // node = node.next; // 不能用这种,区别就是最后一个null会被下面的node.data所使用 // printLinkReversed(node); printLinkReversed(node.next); System.out.println(node.data); } }
学习来源: