算法学习-链表

链表

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;

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值