链表
单链表
单链表的结构与创建
结构
单链表具有表头,数据和指针(指向下一元素)
代码
java
public class SingleLinkList {
//创建头指针
private Node head = null;
//链表格式
private class Node {
//存储内容
int element;
Node next;
public Node(int element, Node next) {
this.element = element;
this.next = next;
}
}
}
头插
原理
先创建一个数据块,将链表块的next指针指向head连接的内容,再将head赋值为链表块
代码
java
public void addFirst(int element) {
//创建数据块,先将头指针连接的内容接到插入元素上,再将插入元素赋值给头指针
head = new Node(element, head);
// Node first = head;
// head = first
}
尾插
原理
尾插的原理比较简单,直接让最后一个元素的next指针指向刚创建的元素,但是链表不是数组,无法从下标直接调用到最后元素,所以只能采取遍历的方法,移动到最后一个元素,再进行赋值。
代码
尾部赋值
java
public void addLast(int element) {
//判断链表是否为空
// if (head == null) {
// addFirst(element);
// } else {//不为空,调用遍历,查找最后一个元素
// Node last = traversal();
// last.next = new Node(element, null);
// }
Node last = traversalLast();
// Node last = recursion(head);
if (last == null) {
addFirst(element);
return;
}
last.next = new Node(element, null);
}
for循环寻找Last节点
java
private Node traversalLast() {
if (head == null) {
return null;
}
Node point;
/*
这里的写的是point.next != null而不是point != null的原因是:
point的话即使他是最后一个他也会再往下运行一次,所以此时point指向的是空指针
*/
for (point = head; point.next != null; point = point.next) {
}
return point;
}
递归寻找Last节点
java
private Node recursion(Node node) {
//第一个条件判断链表是否为空,第二个判断是否有下一个节点
if(node == null || node.next == null) {
return node;
}
//无论递归多少次,最后return的一定是if语句中return的node
return recursion(node.next);
}
头删
原理
头删即直接将头节点指向第一节点的next节点,而头节点本就等于第一节点,所以头节点直接指向头节点的next节点
代码
java
public void removeFirst() {
if (head == null) {
System.out.println("头指针为空!");
return;
}
head = head.next;
}
查找删除
原理
通过遍历查找待删除的索引的前一个节点,将前一个节点的next值赋值为待删除节点的next值
代码
java
public void remove(int index) {
Node prev = searchIndex(index - 1);
// Node prev = recursionIndex(index - 1, 0, head);
Node removed = prev.next;
prev.next = removed.next;
}
java
//递归查找索引
private Node recursionIndex(int index, int i, Node point) {
if(index == i){
return point;
}else if(point == null){
return null;
}
i++;
return recursionIndex(index,i,point.next);
}
//for循环查找索引
private Node searchIndex(int index) {
int i = 0;
Node point;
for (point = head; point != null; point = point.next, i++) {
if (i == index) {
//找到了返回目标节点
return point;
}
}
return null;//没有找到
}
一遍删除完所有的相同值
思路
创建前后节点prev与rem,判断rem的element与val是否相同,如果相同就让prev.next = rem.next,然后rem = rem.next(节点向后移一位。如果没找到,prev和rem都向后移一位,也就可以写为prev = rem; rem = rem.next;
代码
java
public void lastingRemove(int val) {
Node prev = head;
Node rem = head.next;
while (rem != null) {
if (rem.element == val) {
prev.next = rem.next;
rem = rem.next;
} else {
prev = rem;
rem = rem.next;
}
}
if (head.element == val) {
head = head.next;
}
}
倒序排列链表
思路
使用三指针,先将new一个新的节点cur,获取head后面的一个节点,然后再创建一个节点p,记录cur之后的节点。这时候将head节点的next置空,然后将cur节点插如到head节点的前面,再将head赋值为cur,成为新的头节点(头插)。
代码
java
public void displaceElement() {
Node cur = head.next;
head.next = null;
Node p = cur.next;
//注意不是p.next否则会漏掉最后一个元素
while (p != null) {
p = cur.next;
cur.next = head;
head = cur;
cur = p;
}
}
快慢指针---输出节点中间数值
思路
创建两个指针,快指针一次位移两个节点,慢指针一次位移一个节点。当快指针到终点的时候,慢指针刚好在链表的中间节点(奇数为中间,偶数为中间的右节点)。
代码
java
//快慢指针,输出一个链表的中间值,如果是偶数的话就输出右边的值
public void outPutMiddleNode() {
Node fast = head;
Node slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
System.out.println(slow.element);
}
快慢指针---输出指定倒数第K个节点的element
思路
通过快慢指针,先将fast指针向后移动k-1个节点,然后再将fast、slow指针同时移动,这样等fast指针结束的时候,slow指针就指在倒数第K个节点上。
代码
java
//快慢指针,输出倒数第K个节点
public void outPutBottomKTh(int k) {
if (k == 0) {
System.out.println("非法输入");
return;
}
Node fast = head;
Node slow = head;
while (k - 1 != 0) {
fast = fast.next;
if (fast == null) {
System.out.println("非法输入");
return;
}
k--;
}
while(fast.next != null) {
slow = slow.next;
fast = fast.next;
}
System.out.println(slow.element);
}
删除倒数第n个节点,并返回链表
思路
删除倒数第n个节点和上题使用的思路是相同的------快慢指针,但是写法有点区别,因为这里如果删除的是头节点的话,还需要特殊处理。所以我们需要创建一个标枪节点。
还是创建双节点fast和slow,让fast = head;slow = mark;这样位移的时候fast和slow中间可以始终差n个节点,当fast == null时,slow.next就是待删除的节点,令slow.next = slow.next.next即可。
此时如果只有一个节点的话,n = 1,这时fast往前走一步就为null,所以slow.next = slow.next.next (null)。
代码
java
public Node removeNthFromEnd(int n) {
Node mark = new Node(0, head);
Node fast = head;
Node slow = mark;
while(n != 0) {
fast = fast.next;
n--;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return mark.next;
}
链表的合并
思路
创建一个标枪指针newhead,再创建一个移动指针tmp,创建循环将head1和head2的元素拿来对比,将小的赋值给tmp,然后向下移动一个节点,当head1或者head2为空的时候,就说明有一个链表已经插入完毕,只需要将剩下的链表的head直接赋值给tmp.next即可。
图一
如上图,创建一个标枪指针newhead和一个移动指针tmp,下面就是比较head1的第一个Node和head2第一个Node,head1的element比head2的小,所以将head1赋值给tmp
图二
图二将head1赋值给tmp之后,head1 = head1.next; tmp = tmp.next;下面就在进行比较。
图三
图三是相同的,将head2的节点赋值给tmp.next;直到图四执行完毕。
图四
代码
java
public void CombineLinkList(SingleLinkList singleLinkList1, SingleLinkList singleLinkList2) {
Node head1 = singleLinkList1.head;
Node head2 = singleLinkList2.head;
if (head1 == null && head2 == null) {
System.out.println("双链表为空!");
return;
}
Node newhead =new Node(0,null);
Node tmp = newhead;
while(head1 != null && head2 != null ) {
if (head1.element <= head2.element) {
tmp.next = head1;
head1 = head1.next;
tmp = tmp.next;
} else {
tmp.next = head2;
head2 = head2.next;
tmp = tmp.next;
}
}
if (head1 == null) {
tmp.next = head2;
} else {
tmp.next = head1;
}
for (Node p = newhead.next;p != null;p = p.next) {
System.out.println(p.element);
}
}
回文链表
题目及思路
回文链表的结构:
首先找到中间节点,然后从中间节点开始往后的节点全部进行反转
除了中间节点slow,再定义两个节点,一个mid用来记录slow.next节点,一个cur用来记录slow.next.next节点,那么当这两个节点到位了之后,就开始反转,mid.next = slow,如下图
反转完后,需要节点往后走,可以写为slow = mid;mid = cur;然后cur = cur.next;当cur = null时,说明已经遍历完毕。
代码
java
public void palindromeLinkedList() {
Node fast = head;
Node slow = head;
//寻找中间节点slow
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//从slow开始进行反转链表
Node mid = slow;
Node cur = slow.next;
while(cur != null) {
mid = cur;
cur = cur.next;
mid.next = slow;
slow = mid;
}
/*开始检测是否回文,Node pre = head节点从前往后,mid节点从后往前
这里的条件有两种情况,一种是奇数个数链表,条件是两个指针走到中间,那么地址相同
一种是偶数个数链表,当pre.next = mid的时候,代表已经相遇
*/
while(pre != mid) {
if(head.element != mid.element) {
System.out.println("该链表不是回文链表");
return;
}
//判断第二个条件,当偶数链表相遇时,直接结束循环
if(pre.next == mid) {
break;
}
pre = pre.next;
mid = mid.next;
}
System.out.println("该链表是回文链表");
}
给定x排序
题目及思路
首先题目需求是给定一个x,将这个链表小于x的值排序在大于x的值的左边,并且保证链表基本顺序不变
所以我们需要创建两个单链表,将小于x的值放在1链表中,大于x的值放在2链表中。
最后再将两个链表连在一起,这里还有两种情况,如果链表中的所有数都小于x值,或者都大于x值,那么直接返回1链表或者2链表。
代码
java
public Node sortNodeList(int x) {
//创建两个链表,一个链表存储的是小于x的,一个链表是存储大于x的
Node head1 = null;
Node cur1 = null;
Node head2 = null;
Node cur2 = null;
//遍历链表
while(head != null) {
//小于x的存储于头节点为head1的链表
if (head.element < x) {
//如果头节点为空的话,就要先赋值给头节点
if (head1 == null) {
head1 = head;
cur1 = head1;
} else {//使用尾插的方法存储
cur1.next = head;
cur1 = cur1.next;
}
} else {//大于x存储于头节点为head2的链表
if (head2 == null) {
head2 = head;
cur2 = head2;
} else {
cur2.next = head;
cur2 = cur2.next;
}
}
head = head.next;
}
/*
如果head1为空,则链表中全是大于x的数,反之head2为空则是都是小于x的数,那么就产生两种情况
head1为空的话直接返回的是head2,反之则返回head1。
*/
if (head2 == null ) {
return head1;
}
if (head1 == null) {
return head2;
}
cur1.next = head2;
cur2.next = null;
return head1;
}
奇偶链表
原理
图一
图二
图三
代码
java
public Node oddEvenList() {
if(head == null) {
return null;
}
Node evenNubmer = head.next;
Node even = evenNubmer;
Node odd = head;
while(even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenNubmer;
return head;
}
两个链表的第一个共同节点
解法1:
思路
先计算出两个链表的长度,然后相减就是之间的长度差,让长的链表先走长度差之后,再和短的链表同时遍历,这样就能找到共同节点。
代码
java
public void sameNode(Node headA, Node headB) {
int a = 0;
int b = 0;
Node l = headA;
Node s = headB;
while(l != null) {
a += 1;
l = l.next;
}
while(s != null) {
b += 1;
s = s.next;
}
l = headA;
s = headB;
int len = a - b;
if(len < 0) {
len = b - a;
l = headB;
s = headA;
}
while(len != 0) {
l = l.next;
len--;
}
while(s != l) {
l = l.next;
s = s.next;
}
System.out.println("第一个共同节点为" + s.element);;
}
解法2:
思路
双指针:headA链表长度为a,headB链表长度为b,共同节点长度为c。
当A指针从headA链表走,B指针从headB走,当A指针走完headA,再走到headB的共同节点Node时,B指针刚好也走完B走到A的Node节点,
a + (b - c) = b + (a - c)时代表有共同节点,如果没有,那么返回空节点null。
代码
java
public void sameNode(Node headA, Node headB) {
Node A = headA;
NOde B = headB;
while(A != B) {
/*
判断 A 是否为 null,如果不为 null,则将 A 移动到下一个节点,即 A = A.next;
如果 A 为 null,则将 A 移动到链表 B 的头节点,即 A = headB。*/
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
return A;
}
环形链表
题目及思路
判断一个链表中是否有环,类似于数学中的追击问题,如果两个指针相遇(相等)那么就是有环,如果有一个为null那么就没有环。
所以这题用快慢指针很好解决,fast走两步,slow走一步,如果fast先等于null,那么就相当于无环,如果有环的话,fast最多走一圈环能追上slow。
代码
java
public void hasCycle() {
Node fast = head;
Node slow = head;
while(fast != null && fast.next != n) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
返回环形链表的入口点
思路
代码
java
public Node findCycleEntrance() {
Node fast = head;
Node slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(slow == fast) {
break;
}
}
if(fast == null || fast.next != null) {
return null;
}
slow = head;
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
双向链表
双向链表的结构与创建头、尾节点
结构
双链表具有元素前驱和后继,与单链表相比,双链表可以向前移动。
代码
java
public static class Node {
int element;
Node pre;
Node next;
public Node(int element, Node pre, Node next) {
this.element = element;
this.pre = pre;
this.next = next;
}
}
//创建头节点
Node head = null;
Node last = null;
头插
原理
创建一个新的节点等于头节点的pre,然后让头节点往前一位
代码
java
public void addFirst(int element) {
if (head == null) {
head = new Node(element, null , null);
} else {
head.pre = new Node(element, null, head);
head = head.pre;
}
}
尾插
原理
先判断head是否为空,为空直接让head等于新创建的节点,并让最后一个节点last等于head。
如果不为空,就要让last.next等于新的节点,然后last = last.next;
代码
java
public void addEnd(int element) {
if (head == null) {
head = new Node(element, null, null);
last = head;
} else {
last.next = new Node(element, last, null);
last = last.next;
}
}
头删
原理
首先判断链表是否为空,然后让head位移到下一个节点,并将这个节点的前驱置为null。
代码
java
public void firstRemove() {
if (head == null) {
System.out.println("此表为空!");
}
head = head.next;
head.pre = null;
}
尾删
原理
首先判断链表是否为空,然后让last位移到前一个节点,并将这个节点的next置为null。
代码
java
public void endRemove() {
if (last == null) {
System.out.println("此链表为空!");
}
last = last.pre;
last.next = null;
}
查找删除
原理
先判断链表是否为空,不为空判断链表头、尾元素是否与待删除元素相等,相等的话执行头删或者尾删。如果不相等就使用循环遍历,找到之后让这个元素的前一个元素的next值指向后一个元素,后一个元素的pre值指向前一个元素。这样待删除元素就没有节点指向,会被垃圾回收。
图一
如图一,假设待删除节点元素是1,那么这是找到的情况,执行完成如图二
图二
代码
java
public void searchRemove(int element) {
if (head == null) {
System.out.println("此链表为空!");
return;
}
if (head.element == element) {
head = head.next;
head.pre = null;
} else if (last.element == element) {
last = last.pre;
last.next = null;
} else {
Node cur = head;
while (cur != null) {
if (cur.element == element) {
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
}
cur = cur.next;
}
}
System.out.println("找无此元素");
}
清空链表
原理
双链表清空和单链表有所不同,双链表因为有前驱后继,所以必须都置空才能全部不引用对方,所以必须遍历置空。
遍历置空就需要创造一个节点专门记录下一个节点。置空完所有节点之后头节点和尾节点也需要再次置空。
代码
java
public void clearList() {
Node cur = head;
while(cur != null) {
//创建节点记录下一个节点
Node nextNode = cur.next;
cur.next = null;
cur.pre = null;
cur = nextNode;
}
head = last = null;
}