链表问题总结
- 链表
链表是一种基本的一维离散存储数据结构。相对于数组,它是离散内存的,不能如数组一样通过下标来查询相应地数据,每一个链表节点只能知道它的上一个(双端链表)和它的下一个(单链表,双端链表)节点。C语言通过malloc/free控制内存,C++里通过new/delete,Java则是只有new对象。
1.Java链表节点定义
class Node{ private Node next; private int data; public Node(){ } public Node(int data){ this.data=data; } public int getData() { return data; } public void setData(int data) { this.data = data; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } }
2.链表操作类
class LinkedList{ private Node first; public Node getfirst() { return first; } public void setfirst(Node first) { this.first = first; } //按值搜寻第一个等于目标的节点 public Node findNode(int target){ if(first==null){ System.out.println("initialise the first"); return null; } Node p=first; while(p.getData()!=target && p!=null) p=p.getNext(); return p; } public Node findPreNode(Node target){ if(first==null){ System.out.println("initialise the first"); return null; } Node p=first; while(p.getNext()!=target) p=p.getNext(); return p; } //返回链表下标查找 public int IndexOfNode(int target){ int index=1; if(first==null){ System.out.println("initialise the first"); return -1; } Node p=first; while(p.getData()!=target) { index++; p=p.getNext(); if(p==null) { index=-1; break; } } return index; } //在末尾添加节点 public void addNode(Node add){ if(first==null){ first=add; add.setNext(null); return; } Node p=first; while(p.getNext()!=null) p=p.getNext(); p.setNext(add); add.setNext(null); } //头插法生成链表 public void headAdd(Node node){ if(first == null){ first=node; node.setNext(null); }else{ node.setNext(first); first=node; } } //删除节点--须指定链表中的节点 public void deleteNode(Node toDel){ if(first==null){ System.out.println("initialise the first"); return ; } Node p=toDel.getNext(); if(p!=null) { toDel.setNext(p.getNext()); toDel.setData(p.getData()); p.setNext(null); }else{ deleteEndNode(toDel); } } //删除末尾 public void deleteEndNode(Node toDel){ Node p=first; while(p.getNext()!=toDel && p!=null) p=p.getNext(); p.setNext(null); } //常规删除 public void deleteNormal(Node toDel){ Node p=findPreNode(toDel); p.setNext(toDel.getNext()); toDel.setNext(null); } //修改一个节点的值 public void update(Node target,int value){ if(first==null){ System.out.println("initialise the first"); return ; } target.setData(value); } public void printList(){ if(first==null){ System.out.println("initialise the first"); return ; } Node p=first; while(p!=null){ System.out.print(p.getData()+" "); p=p.getNext(); } System.out.println(); } }
小记:(1) 生成单链表可以有头插法和尾插法,尾插法的添加节点为O(n)复杂,删除也是O(n);头插法添加则是O(1),删除还是O(n)。
(2) 删除方法有一个是O(1)复杂度的。方法:给定一个节点,删除它下一个节点并把下一个节点的值转移到给定节点。
- 问题1:删除链表内重复的节点。(数据重复)
因为使用的是那个O(1)删除的方法,所以写得有些奇怪。总体方法就是使用一个O(n)空间来存储节点,发现重复就删除,遍历时间复杂就只用O(n)。
//删除未排序链表重复节点1 缓冲区版 public void DelRepeated1(LinkedList list){ HashMap<Integer,Boolean> buffer=new HashMap<Integer,Boolean>(); Node p=list.getfirst(); while(p!=null){ System.out.println("data="+p.getData()); if(buffer.get(p.getData())==null){ buffer.put(p.getData(), true); } else { list.deleteNode(p); while(true){ if(buffer.get(p.getData())!=null) list.deleteNode(p); else break; } } p=p.getNext(); } }
若不使用缓冲区,则只能使用暴力遍历法,从当前节点开始到表尾“排除异己”,“步步为营”。复杂度O(n*n)。
并且删除方法是使用O(n)复杂度的。
//删除未排序俩表重复节点2 无缓冲区 public void DelRepeated2(LinkedList list) { Node p = list.getfirst(); Node q = list.getfirst().getNext(); while(p!=null){ q = p.getNext(); while(q!=null){ if(p.getData()==q.getData()) list.deleteNormal(q); q=q.getNext(); } p = p.getNext(); } }
- 问题2:寻找倒数低k个节点。
这个问题其实挺简单,如果链表是你自建的,那么添加个length或size属性记录那么解决这个问题就方便多了。如果仅给定一个单链表,则遍历一次找出length再以互补或互余的思维来求出倒k的下标。
//寻找倒数第k个节点 public Node findNK(LinkedList list,int k){ if(k<=0) { System.out.println("k should be upper than 0"); return null; } int n=0; Node p=list.getfirst(); while(p!=null) { n++; p=p.getNext(); } int result=n-k; if(n-k<0) { System.out.println("index out of range"); return null; } p=list.getfirst(); while(result!=0){ p=p.getNext(); result--; } return p; }
- 问题3:给定一个值x,将链表分割成两部分,大于x部分和小与x部分。
接到这道题的时候,我马上蹦出一个想法:排序。排完序就解决了。最快的排序算法也是O(n*logn)。然后想到快速排序,快速排序的partition函数可以一次将数组的分割成以基准元素为分界点的序列。这么一想,这道题可以做成O(n)复杂度了。实现它需要的数据结构为双端链表。
//双端链表分割 public void DivideLink(DoubleLink dl,int element){ DoubleNode tail=dl.getEnd().getPre(); DoubleNode first=dl.getFirst(); first.setData(element); int start=1; int end=dl.getLength(); int x=element; while(start<end){ while(start<end && tail.getData()>=x) { end--; tail=tail.getPre(); } if(start<end){ start++; first.setData(tail.getData()); first=first.getNext(); } while(start<end && first.getData()<=x) { start++; first=first.getNext(); } if(start<end){ tail.setData(first.getData()); end--; tail=tail.getPre(); } } first.setData(x); }
循环链表的实现:
class DoubleLink{ DoubleNode first; DoubleNode end; DoubleNode current; int length=0; //初始化空双端链表 public DoubleLink(){ first=new DoubleNode(); end=new DoubleNode(); first.setNext(end); end.setNext(first); first.setPre(end); end.setPre(first); current=first; } public int getLength(){ return length; } public DoubleNode getFirst() { return first; } public void setFirst(DoubleNode first) { this.first = first; } public DoubleNode getEnd() { return end; } public void setEnd(DoubleNode end) { this.end = end; } public void InsertNode(DoubleNode node){ current.setNext(node); node.setPre(current); current=node; end.setPre(node); node.setNext(end); length++; } public void RemoveNode(DoubleNode toDel){ DoubleNode p = toDel.getPre(); DoubleNode q = toDel.getNext(); p.setNext(q); q.setPre(p); toDel.setPre(null); toDel.setNext(null); // if (toDel != first && toDel != end) { // p.setNext(q); // q.setPre(p); // }else if(toDel == first){ // first=q; // p.setNext(q); // q.setPre(p); // }else if(toDel == end){ // end=p; // p.setNext(q); // q.setPre(p); // } length--; } public void UpdateNode(DoubleNode toUpdate,int data){ toUpdate.setData(data); } public DoubleNode SearchNode(int data){ DoubleNode p=first.getNext(); while(p.getData()!=data && p!=end) p=p.getNext(); if(p==end) p=null; return p; } public int IndexOfNode(DoubleNode search){ int index=0; DoubleNode p=first; while(p!=end && p!=search){ p=p.getNext(); index++; } if(p==end) index=-1; return index; } public void printLink(){ DoubleNode p=first.getNext(); while(true){ if(p==end) break; System.out.print(p.getData() + " "); p = p.getNext(); } System.out.println(); } } class DoubleNode{ private int data; private DoubleNode pre; private DoubleNode next; public DoubleNode(){ } public DoubleNode(int data){ this.data=data; } public int getData() { return data; } public void setData(int data) { this.data = data; } public DoubleNode getPre() { return pre; } public void setPre(DoubleNode pre) { this.pre = pre; } public DoubleNode getNext() { return next; } public void setNext(DoubleNode next) { this.next = next; } }
- 问题四:链表加法,每一个节点为一个数位,分别分两种方法来实现,高位到低位、低位到高位。
//链表加法 public LinkedList ListAdd1(LinkedList list1,LinkedList list2){ Node current1=list1.getfirst(); Node current2=list2.getfirst(); LinkedList result=new LinkedList(); int carry=0; int count; result.setfirst(new Node(-1)); while(current1 != null && current2 != null){ count=carry+current1.getData()+current2.getData(); carry=count/10; count=count%10; Node reNode=new Node(count); result.addNode(reNode); current1=current1.getNext(); current2=current2.getNext(); } while(current1 != null){ Node reNode=new Node(current1.getData()+carry); result.addNode(reNode); current1=current1.getNext(); carry=0; } while(current2 != null){ Node reNode=new Node(current2.getData()+carry); result.addNode(reNode); current1=current2.getNext(); carry=0; } if(carry!=0){ Node reNode=new Node(carry); result.addNode(reNode); } return result; } public LinkedList ListAdd2(LinkedList list1, LinkedList list2) { int number1 = 0; int number2 = 0; Node f1 = list1.getfirst(); Node f2 = list2.getfirst(); LinkedList result = new LinkedList(); while (f1 != null) { number1 = number1 * 10 + f1.getData(); f1 = f1.getNext(); } while (f2 != null) { number2 = number2 * 10 + f2.getData(); f2 = f2.getNext(); } int count = number1 + number2; int rank=1; int temp=count; while(temp>10) { temp=temp/10; rank=rank*10; } temp=count; while (rank != 0) { result.addNode(new Node(temp/rank)); temp%=rank; rank/=10; } return result; }
- 问题五:链表查环,从哪个节点开始成环的,输出节点。
这个解法还是hashmap。
//链表查环 public Node findCircle(LinkedList list){ HashMap<Node,Boolean> map=new HashMap<Node,Boolean>(); Node p=list.getfirst(); while(true){ if(map.get(p)==null) map.put(p, true); else break; System.out.println(p.getData()); p=p.getNext(); } return p; }
- 问题六:链表回文检测。每个数据位存储字符,一个字符串是否回文。
关键点是使用栈来保存状态,到中点的时候开始pop,如果发现不是回文,则返回false。
//回文检测 public <T> boolean LinkedPalindrome(ArrayList<T> list){ boolean isPalindrome=true; ArrayList<T> stack=new ArrayList<T>(); int i=-1; int isEven=list.size()%2; int mid=list.size()/2; System.out.println("mid="+mid); for(int j=0;j<list.size();j++){ T data=list.get(j); if(j<mid) { stack.add(data); i++; } else { if (isEven == 0) { if(data==stack.get(i)) { i--; continue; }else { isPalindrome=false; break; } }else if(isEven == 1){ if(j==mid) continue; else{ if(data==stack.get(i)) { i--; continue; }else{ isPalindrome=false; break; } } } } System.out.println("j="+data+" i="+stack.get(i)); } return isPalindrome; }
- 问题七:链表找环
把我以前的代码复制过来了,追赶法。
bool IfCircle(LinkList *root) { /* 追赶式验证是否存在环 */ LinkList *fast=root; LinkList *slow=root; while(fast && slow) { fast=fast->p_next; fast=fast->p_next; if(fast==slow) return true; slow=slow->p_next; } return false; }