链表
1.单链表
1)链表是以节点的方式来存储的
2)每个节点包含data域,next域(指向下一个节点)
3)链表中每个节点不一定是连续存储
4)分为带头节点的链表和没有头节点的链表,根据实际需求来确定
-
1.添加
a.不考虑编号添加
public class SingleLinkedList {
public static void main(String[] args) {
SingleLinkedListDemo singleLink=new SingleLinkedListDemo();
HeroNode heroNode1=new HeroNode(1,"batman","bat");
HeroNode heroNode2=new HeroNode(2,"spider","spider");
HeroNode heroNode3=new HeroNode(3,"ironman","iron");
singleLink.addNode(heroNode1);
singleLink.addNode(heroNode2);
singleLink.addNode(heroNode3);
singleLink.printList();
}
}
//定义单链表
class SingleLinkedListDemo{
//初始化一个头节点,头节点不动,不存放具体的数据
private HeroNode head=new HeroNode(0,"","");
//不考虑编号,添加新节点到链表末尾
//找到当前链表的最后节点,将最后节点的next指向新节点
public void addNode(HeroNode heroNode) {
//头节点不动,需要一个辅助节点temp遍历查找链表末尾
//temp从head开始依次往后遍历
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
} else {
temp = temp.next;
}
}
temp.next = heroNode;
//遍历链表
}
public void printList (){
//判断链表是否为空
if(head.next==null){
System.out.println("链表为空");
return;
}else {
HeroNode temp=head.next;
while(temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
}
//定义Node类,每个HeroNode对象就是一个节点
class HeroNode{
public int num;
public String name;
public String nickName;
public HeroNode next;
//next必定义,类型为类名
//构造器
public HeroNode(){
}
public HeroNode(int num,String name,String nickName){
this.name=name;
this.num=num;
this.nickName=nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"num=" + num +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
b.考虑编号顺序添加
只能按照123,不能132【链表会断】
public void addNode(HeroNode2 heroNode) {
HeroNode2 temp=head;//通过temp辅助节点查找,新节点添加在temp之后
boolean flag=false;//是否已经存在该添加编号
while(true){
//情况1:已经遍历到末尾,直接在temp后,即链表末尾添加
if(temp.next==null){
break;
}
//情况2:已经存在该编号节点
if(temp.next.num==heroNode.num){
flag=true;
System.out.println("编号为"+heroNode.num+"节点已经存在");
break;
}
//情况3:temp.next>新添加节点,新节点应该插入在temp和temp.next之间
if(temp.next.num>heroNode.num){
break;
}
temp=temp.next;
}
if(!flag) {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
c.修改节点
public void update(HeroNode2 newNode){
//如果链表为空
if(head.next==null){
System.out.println("链表为空");
return;
}
HeroNode2 temp=head;
boolean flag=false;//是否找到该编号节点
while (true){
//找不到该节点
if (temp.next==null){
break;
}
//找到该节点并且更改
if(temp.next.num== newNode.num){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.next.name=newNode.name;
temp.next.nickName=newNode.nickName;
}else{
System.out.println("没有该编号节点");
}
}
d.删除节点
public void delete(int num){
//如果链表为空
if(head.next==null){
System.out.println("链表为空");
return;
}
//通过temp辅助节点找出待删除节点的前一个节点,将temp.next的编号和待删除节点的编号比较
HeroNode2 temp=head;
boolean flag=false;
while(true){
//如果没找到该节点
if(temp.next==null){
break;
}
//如果找到了
if(temp.next.num==num){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.next=temp.next.next;
}else {
System.out.println("没有该节点");
}
}
}
单链表的缺点:1.查找方向只能是一个方向2.不能自我删除,需要辅助节点temp找到待删除节点的前一个节点
2.双链表
a.遍历:可以向前也可以向后
b.添加【末尾】
找到双向链表最后一个节点
temp.next=newNode;
newNode.pre=temp;
c.修改
和单链表一样,因为只改数据没有对节点位置更改
d.删除【可以自我删除】
直接找到要删除的节点
temp.pre.next=temp.next
这句需要判断删除节点是否为最后一个节点,否则会产生空指针异常
temp.next.pre=temp.pre
3.链表逆序
1)单链表逆序
链表反转注意返回原尾节点现头节点,并让head指向它,否则JVM会直接释放无法查找的节点
两个初始设置为null的辅助指针,prev和next,使next=head.next,head指向当前头节点
先从A节点开始逆序,将A节点的next指针指向prev,因为prev的当前值是NULL,所以A节点就从链表中脱离出来了。
让pre指向此刻head的位置,head指向next位置
next = head->next;
head->next = prev;
prev = head;
head = next;
当head为null时循环停止,返回此时的pre【之前的尾节点】
static Node reverseList (Node head){
Node pre=null;
Node next=null;
while(head!=null){
next=head.next;
head.next=pre;
pre=head;
head=next;
}
return pre;
}
}
-
头插法:
-
先定义一个节点reverseHead=newNode()
-
遍历原链表,每遍历一个节点就将其取出,放在新链表的最前端
-
原链表的Head.next=reverseHead.next
-
public void reverse() {
//若链表为空或只有一个节点,无需反转,直接返回
if (head.next == null | head.next.next == null) {
return;
}
Node reverseHead = new Node(0);
Node temp = head.next;
Node next = null;//用于存temp的下一个节点
while (temp != null) {
next=temp.next;
temp.next = reverseHead.next;
reverseHead.next = temp;
temp = next;
}
head.next=reverseHead.next;
}
注意:需要临时变量next存放temp.next,不能直接使用temp=temp.next【会断】因为前面已经更改temp.next指向
2)双链表逆序
static Node reverseDoubleList (Node head){
Node pre=null;
Node next=null;
while(head!=null){
next=head.next;
head.next=pre;
head.last=next;
pre=head;
head=next;
}
return pre;
}
}
4.单链表的栈、队列问题
【时间复杂度都是0(1)】
1)队列:先进先出
-
初始化队列:
public class queue{
private Node head;
private Node tail;
private int num;
public queue(){
head=null;
tail=null;
num=0;
}
public queue(){
head=null;
tail=null;
size=0;
}
public boolean isEmpty(){
return size==0;
}
public int getSize(){
return size;
}
}
-
添加入队列时
-
只有一个节点时
-
-
多个节点
public void offer(int num){
//创建新节点
Node cur=new Node(num);
if(tail==null){
head.next=cur;
tail.next=cur;
}else {
tail.next=cur;
tail=cur;
}
size++;
}
-
弹出:从头弹出
public int poll(){
//从头弹出
int ans=-1;
if(head!=null){
ans=head.num;
head=head.next;
size--;
}
//head移动后前面的节点会被释放
if(head==null) {
tail=null;//保持头尾一致,避免head已经到null到尾部还有数据
}
return ans;
}
2)栈:先进后出
只需要一个head变量即可
插入时:新节点加入到oldhead.next,然后head指向新节点
弹出时:head指向上一个节点,该节点被释放
public class stack {
private Node head;
private int size;
public stack() {
head = null;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public int getSize() {
return size;
}
public void offer(int num) {
//创建新节点
Node cur = new Node(num);
if(head==null){
head=cur;//如果栈中为空。第一个节点为head
}else{
cur.next=head;//新进入的节点在old节点上方,指向oldhead
head=cur;
}
size++;
}
public int poll() {
//从头弹出
int ans = -1;
if (head != null) {
ans = head.num;
head = head.next;
size--;
}
return ans;
}
class Node {
public int num;
public Node next;
public Node() {
}
public Node(int num) {
this.num = num;
}
@Override
public String toString() {
return "Node{" +
"num=" + num +
'}';
}
}
}
5.双链表结构实现双端队列
可以从头尾进行添加、取出操作,且复杂度都是O(1)
public class doubleQueue {
private Node head;
private Node tail;
private int size;
public doubleQueue(){
head=null;
tail=null;
size=0;
}
public boolean isEmpty() {
return size == 0;
}
public int getSize() {
return size;
}
public void offerHead(int value){
Node cur=new Node(value);
if(head==null){
head=cur;
tail=cur;
//第一个节点头尾都指向它,它的pre和next都指向空
}else{
cur.next=head;
head.pre=cur;
head=cur;
}
size++;
}
public void offerTail(int value){
Node cur=new Node(value);
if(head==null){
head=cur;
tail=cur;
//第一个节点头尾都指向它,它的pre和next都指向空
}else{
tail.next=cur;
cur.pre=tail;
tail=cur;
}
size++;
}
public int pollHead(){
int ans=-1;
if(head!=null){
ans=head.value;
if(head==tail) {
//只有一个node
head=null;
tail=null;
}else{
head = head.next;
head.pre = null;
}
size--;
}
return ans;
}
public int pollTail(){
int ans=-1;
if(tail!=null){
ans=tail.value;
if(head==tail) {
//只有一个node
head=null;
tail=null;
}else{
tail = tail.pre;
tail.next = null;
}
size--;
}
return ans;
}
}
class Node{
public Node pre;
public Node next;
int value;
public Node(){}
public Node(int value){
this.value=value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
6.k group链表反转
k group内部链表反转,反转完后每个k group链表的尾结点指向下一个k group链表的头节点
1.找到每个k group的尾节
//找到每个k group的尾节点
public static ListNode getGroupEnd(ListNode start,int k){
//边界值考虑:最后一段链表可能没有k个节点,还没遍历k个就已经到null
while(--k!=0&&start.next==null) {
start = start.next;
}
return start;
}
2.反转每个k group内部并且让反转后尾结点(原本start节点)指向后一组k group开头节点
public static void reverse(ListNode start,ListNode end)//k group的开头和结尾
{
end=end.next;//记录end下一个节点作为结束标志
ListNode pre=null;
ListNode next=null;
ListNode cur=start;
while(cur!=end){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
start.next=end;
}
3.处理链表
public static ListNode reverseList(ListNode head, int k)//处理链表
{
//先关注第一组k group,可以确定整个反转后链表的头部
ListNode start = head;
ListNode end = getGroupEnd(head, k);
if (end == null)//该链表不足k个节点
{
return head;//头不变
}
//第一组可以凑齐
head = end;
//只要第一组可以凑齐,头将不再变化
reverse(start, end);
start = end;
//由于单项链表不能直接找到上一个节点,使用记录每k group的上一个节点
ListNode lastend = start;
while (lastend.next != null) {
start = lastend.next;
end = getGroupEnd(start, k);
if (end == null) {
return head;
} else {
reverse(start, end);
lastend.next = end;
lastend = start;
}
}
return head;
}
6.链表相加
1.找到长短链表
public static ListNode addTwoList(ListNode head1,ListNode head2) {
//重定向两个链表,判断两个链表长短
int length1 = getLength(head1);
int length2 = getLength(head2);
ListNode l = length1 >= length2 ? head1 : head2;
ListNode s = l == head1 ? head2 : head1;
}
public static int getLength(ListNode head){
int length=0;
while(head!=null){
head=head.next;
length++;
}
return length;
}
2.处理长短链表
-
长链表有,短链表也有
两个链表对应节点相加并且加上进位信息
-
长链表有,短链表已无
长链表值加上进位信息
-
长链表无,短链表无
如果有进位信息,需要补一个节点存进位信息
提前准备记录进位的信息
不用新链表记录,直接将结果写入长链表,最后看长链表是否需要补位,最后返回长链表头l
public class addList {
public static ListNode addTwoList(ListNode head1,ListNode head2) {
//重定向两个链表,判断两个链表长短
int length1 = getLength(head1);
int length2 = getLength(head2);
ListNode l = length1 >= length2 ? head1 : head2;
ListNode s = l == head1 ? head2 : head1;
ListNode curs=s;
ListNode curl=l;//由于要返回l,另拿值记录l
ListNode last=curl;//当长短链表都已经走到最后(为空),如果遇到需要进位的情况需要记录最后长链表尾部
int curryNum=0;//记录此时的值
int curry=0;//记录进位信息
//第一阶段:长短链表都不为空
while(curs!=null){
curryNum=curl.num+curs.num+curry;
curry=curryNum/10;
curl.num=curryNum%10;
last=curl;
curl=curl.next;
curs=curs.next;
}
//第二阶段:短链表为空,长链表不为空
while(curl!=null){
curryNum=curl.num+curry;
curry=curryNum/10;
curl.num=curryNum%10;
last=curl;
curl=curl.next;
}
//第三阶段:长短链表都空
if(curry!=0){
ListNode curNode=new ListNode(curry);
last.next=curNode;
}
return l;
}
public static int getLength(ListNode head){
int length=0;
while(head!=null){
head=head.next;
length++;
}
return length;
}
}
class ListNode{
public ListNode next;
public int num;
public ListNode(int num) {
this.num = num;
}
}
7.有序链表的合并
1.考虑边界
//若有链表为空则直接返回
if(head1==null|head2==null){
return head1==null?head2:head1;
}
2.重定向,判断哪个节点作为头部
//若1链表头节点小,则head1作为新头;若2链表头节点小,则head2作为新头
ListNode newHead = head1.num<=head2.num?head1:head2;
//cur1指向小头下一个,cur2指向大头
ListNode cur1=head.next;
ListNode cur2= head==head1?head2:head1;
3.pre从头节点开始,指向较小链表的节点,cur1或2后移,pre后移
public ListNode mergeList(ListNode head1, ListNode head2) {
//若有链表为空则直接返回
if (head1 == null | head2 == null) {
return head1 == null ? head2 : head1;
}
//若1链表头节点小,则head1作为新头;若2链表头节点小,则head2作为新头
ListNode head = head1.num <= head2.num ? head1 : head2;
//cur1指向小头下一个,cur2指向大头
ListNode cur1 = head.next;
ListNode cur2 = head == head1 ? head2 : head1;
ListNode pre = head;
while (cur1 != null && cur2 != null) {
if (cur1.num <= cur2.num) {
pre.next = cur1;
cur1 = cur1.next;
} else {
pre.next = cur2;
cur2 = cur2.next;
}
pre = pre.next;
}
pre.next = cur1 == null ? cur2 : cur1;
return head;
}