Java:数据结构-链表

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43336822/article/details/102532096

一、单链表

1.不带头节点的单链表:

主体:

public class LianBiaowithoutHead <E> {

    protected Node<E> head;			//声明第一个结点(并不是头结点)

    class Node<E>{					//创建内部类Node
        protected E element;	
        protected Node<E> next;

        public Node(E data){
            this.element = data;
        }
      }
    }

方法:
不带头结点的单链表在进行操作时,移动指针语句:tmp = tmp.next需要写在操作语句之后,因为head结点保存着第一个结点的元素,不可以跳过它。

增删改查等代码较简单不放上来了,这里放一个show方法做代表。

	public void show(){
	 	Node<E> tmp = head;
        while(tmp != null){
            System.out.print(tmp.element+" ");
            tmp = tmp.next;    //注意在操作语句之后移动tmp,否则会跳过第一个结点,而这里的head是放有数据的
        }
        System.out.println();
	}

2.带头结点的单链表:

主体:

public class LianBiaowithHead <E>{
    protected Node <E> head;

    class Node<E>{
        protected E element;
        protected Node<E> next;

        public Node(E data){
            this.element = data;
        }
    }

    public LianBiaowithHead(){ //需要创立头结点
        head = new Node<>((E)new Object());
    }
 }

方法:
带头结点的单链表在进行删除等操作时,移动指针语句:tmp = tmp.next需要写在操作语句之前,因为head结点的数据域没有东西,需要跳过它再进行打印等操作。

依然放一个show方法做代表。

    public void show(){
        Node<E> tmp = head;

        while(tmp.next != null){
            tmp = tmp.next;      //跳过头结点打印
            System.out.print(tmp.element + " ");
        }
        System.out.println();
    }

3.循环链表

主体:

public class CircleList<E> {//循环单链表(带头结点)

    protected Node<E> head;

    class Node <E>{
        protected E element;
        protected Node<E> next;

        public Node(E data){
            this.element = data;
        }


    }

    public CircleList(){
        this.head = new Node<>((E)new Object());
        head.next = head;
    }

方法:
和上面的2,3一样,记得将最后一个结点的指针域设成head,遍历的循环条件变成 tmp.next != head

以add方法作代表:

    public void addTail(E data){
        Node<E> n = new Node<E>(data);
        Node<E> tmp= head;
        while(tmp.next != head){
            tmp = tmp.next;
        }
        n.next = tmp.next;
        tmp.next = n;
    }

4.单链表的一些其他基本方法

(1)【合并两个有序链表,合并后依然有序】:

思想:
先比较list1,list2的第一个结点元素的大小,小的作合并后newList的头结点,以两个链表的表头同时不为空作循环条件,陆续比较元素大小,小的跟在新表指针后,同时该旧表头往后移动,另一个不动。也就是一个if else语句。

代码实现:

 public <E extends Comparable<E>> Node<E> mergeList(Node<E> head1,Node<E> head2){ 
        Node<E> tmp = null;
        Node<E> curHead;

        if(head1.element.compareTo(head2.element) <0){
            curHead = head1;
            head1 = head1.next;  //别忘了这个的表头要往后移一个
        }
        else {
            curHead = head2;
            head2 = head2.next;
        }
        tmp = curHead;
        while(head1!= null  && head2!= null){
            if(head1.element.compareTo(head2.element)<0){
                tmp.next =head1;
                head1 = head1.next;
            }
            else{
                tmp.next = head2;
                head2 = head2.next;

            }
            tmp = tmp.next;
    }
        if(head1 == null){
            tmp.next = head2;
        }
        if(head2 == null){
            tmp.next = head1;
        }

        return curHead;
    }

tips1:
因为要使用compareTo()方法比较element,所以要在方法声明时在T后extends Comparable,也就是在说:擦除的时候不用擦的那么彻底,擦到还有Comparable,能让我使用它的方法~

tips2:
自己在写代码的时候一遇到泛型就有点乱,不知道怎么写方法声明的顺序了,正确顺序应该是:pubic <T> 返回值 方法名(){}

(2)【逆序输出链表元素】:

思想:
用递归可以简单实现。运行时一层层的进reversePrint方法,每一层都没有走到底,直到最后一层(head指针为空)进入if语句后return,这才返回上一层继续往下走进行打印数据,这一层的方法才算结束,正常return后再往下走打印数据……,直到return到最初进入的一层。最后输出就是逆序的。

代码实现:

    public static <E> void reversePrint(LianBiaowithoutHead<E>.Node<E> head){
        if(head == null)
            return;
        reversePrint(head.next);
        System.out.print(head.element + " ");

    }

(3)【逆置链表】

思想:
设置三个指针,也就是除了普通临时指针cur之外,另外设置一个在当前指针之前的指针p,一个在当前指针之后的指针next,以当前指针不空为条件进行循环,每一次循环都会将p,cur之间的指向调转方向。

代码实现:

   public Node<E> reverseList(Node<E> head){ 
        Node <E> cur = head;
        Node <E> p = null;
        Node <E> newHead =null;

        while(cur!= null){
            Node<E> next = cur.next;
            if(next == null)
                newHead = cur;
            cur.next = p;
            p = cur;
            cur = next;
        }

        return newHead;
    }

(4)【查找单链表中倒数第k个结点】:

思想:
倒数第k个结点和最后一个结点下标差k-1-,所以可以设立两个指针,一个指针p1先走k-1步,再和另一个起点在head的指针p2同时前进,当p1.next为空时,p2也就走到了倒数第k个结点。
看图:
在这里插入图片描述

代码实现:

    public E findTheK(int index){

        if(head == null || index<0)
            return null;
        Node<E> p1 = head;
        Node<E> p2 = head;
        int num = 0;

        while (num != index-1){
            p2 = p2.next;
            num++;
        }

        while(p2.next != null){
            p1 = p1.next;
            p2 = p2.next;
        }

        return p1.element;

    }

(5)【查找两个链表中的相交结点】:
注意是地址一致的结点,而不只是数据一样

思想:
最容易想到的是两个嵌套的循环,可是时间复杂度会达到O(n^2),。
再想到可以利用栈的先进后出进行pop后比较,但是同样会付出额外空间复杂度的代价:O(n),而且较复杂,代码较长。
放弃算了
其实,可以将较长的链表截断,使两个链表的长度一致再进行遍历,就可以只用一个循环实现。
看图:
在这里插入图片描述
代码实现:

public static <E> LianBiaowithHead<E>.Node<E> banana(LianBiaowithHead<E> list1 , LianBiaowithHead<E> list2){

        if(list1.head == null || list2.head == null) return null;

        int length1 = list1.getLength();
        int length2 = list2.getLength();
        int lengthDif = Math.abs(length1-length2);

        LianBiaowithHead<E>.Node<E> longHead = list1.head;
        LianBiaowithHead<E>.Node<E> shortHead = list2.head;

        if(length1 < length2){
            longHead = list2.head;;
            shortHead = list1.head;
        }

        for(int i=0; i<lengthDif; i++){
            longHead = longHead.next;
        }

        while(longHead != shortHead){
            longHead = longHead.next;
            shortHead = shortHead.next;
        }
        return longHead;
    }

tips1:
在测试类中设立公有结点时,list1中用p1遍历到公有结点,list2中用p2遍历到尾,p2.next = p1就可以了。

(6)【在指定结点前插入新结点】:
思想:
先在指定结点之后插入,再交换两结点。

代码实现:

 public void LinkedListInsertBefore(Node<E> head, Node<E> pos, E data){//不允许遍历链表,在pos之前插入
        Node<E> n = new Node<>(data);
        Node<E> taxi = head;

        while(taxi.next != head){
            if(taxi.next == pos)
                break;
            taxi = taxi.next;
        }
        taxi.next = n;
        n.next = pos;

    }

(7)【如果一个链表有环,得到入环结点】:
思想:
试想有2人在操场跑步,一个是肥宅,一个是现充,要是两人跑的是直跑道,现充只会离肥宅越来越远,但要是环形跑道,现充必定会在将来的某一时刻再次超过肥宅。
所以我们可以设置两个速度不同的指针,肥宅指针p1步长为一,现充指针p2步长为2,如果真的有某一个时刻p1,p2指向了同一个结点,那么这个链表中肯定就有环。
也容易知道,两指针相遇的结点肯定是环结点之一,这样我们根据一个计数器,设置tmp指针遍历,计数器++,当tmp绕完了环一圈,再次指向了相遇结点,length 就等于计数器值。
要环长值有什么用?当然是为了解决下一个问题。
将带环链表伸展开,入环结点也就可以看成是倒数第length个结点,现在我们可以依据之前【查找单链表中倒数第k个结点】的思想,设置两个起点相差length的指针,让他们同时移动,保证他们之间始终相差5,这样,当前面的指针走完了一圈时,后面的指针必定才刚开始进环。这时的p1,p2同时指向入环结点,设置这个为循环的终止条件,当前的p1,p2就保存着入环结点地址。
看图:
在这里插入图片描述
代码实现:

    public static <E> CircleList<E>.Node<E> isRing(CircleList<E>.Node<E> head){//单链表是否有环,环的入口节点是哪个
        if(head == null)
            return null;
        if(head.next==null)
            return null;

        CircleList<E>.Node<E> taxi1 = head.next;
        CircleList<E>.Node<E> taxi2 = head.next.next;

        while(taxi2 != null && taxi1!=null){
            if(taxi2 == taxi1){
                return taxi1;
            }

            taxi1 = taxi1.next;
            taxi2 = taxi2.next;
            if(taxi2.next != null)//避免空指针异常
                taxi2 = taxi2.next;
        }

        return null;
    }

    public static <E> CircleList<E>.Node<E> ringNode(CircleList<E>.Node<E> head){
        CircleList<E>.Node<E> meet = isRing(head);

        if(meet == null)
            return null;

        int length = 0;
        CircleList<E>.Node<E> tmp = meet;

        do{
            tmp = tmp.next;
            length++;
        }while(tmp != meet);  //得到环的长度

        System.out.println("length of the ring:"+length);

        CircleList<E>.Node<E> p1 = head;
        CircleList<E>.Node<E> p2 = head;
        int num = 0;
        while(num != length){
            num++;
            p2 = p2.next;
        }//p2先走length步

        while( p1 != p2){
            p1 = p1.next;
            p2 = p2.next;
        }

        return p1;
    }

二、双向链表

双向链表也就只是在单链表的基础上增加一个前驱属性而已,操作与单链表基本相同,就不再详细描述啦,这里仅放上代码:
1.非循环双向链表:

主体:

public class DoubleLinkedList<E> {
    protected Node<E> head;

    class Node<E>{
        protected E element;
        protected Node<E> before;
        protected Node<E> after;

        public Node(E data){
            element = data;
        }
    }

    public DoubleLinkedList(){
        this.head = new Node<>((E)new Object());
        head.after = head;
        head.before = head;
    }
 }

方法:

    public void addTail(E data){
        Node<E> newNode = new Node<>(data);
        Node<E> tmp = head;

        while(tmp.after != head){
            tmp = tmp.after;
        }
        tmp.after = newNode;
        newNode.before = tmp;
        newNode.after = head;
    }

    public void addHead( E data){
        Node<E> newNode = new Node<>(data);
        Node<E> tmp = head;

        newNode.after = head.after;
        newNode.before = head;
        head.after = newNode;
    }

    public void delete( E data){
        Node<E> tmp = head;

        while(tmp.after != head){
            if(tmp.element.equals(data)){
                tmp.before.after = tmp.after;
                tmp.after.before = tmp.before;
            }else {
                tmp = tmp.after;
            }
        }
    }

    public int getLength(){
        Node<E> tmp = head;
        int size = 0;

        while(tmp.after != head){
            tmp = tmp.after;
            size ++;
        }

        return size;
    }

    public void show(){
        Node<E> tmp = head;

        while(tmp.after != head){
            tmp = tmp.after;
            System.out.print(tmp.element + " ");
        }

        System.out.println();
    }

    public E findValue(int index){
        Node<E> tmp = head;
        int num = 0;

        while(tmp.after != head){ //第一个结点(不是头结点)的下标是0
            if( num == index)
                return tmp.element;
            tmp = tmp.after;
            num ++;
        }

        return null;
    }

2.循环双向链表:

主体:

public class DoubleCircularLinkedList <E>{

    protected  Node<E> head;

    class Node<E>{
        protected E element;
        protected Node<E> before;
        protected Node<E> after;

        public Node(E data){
            element = data;
        }
    }

    public DoubleCircularLinkedList(){
        this.head = new Node<>((E)new Object());
        head.after = head;
        head.before = head;
    }
}

方法:

    public void addTail(E data){
        Node<E> newNode = new Node<>(data);
        Node<E> tmp = head;

        while(tmp.after != head){
            tmp = tmp.after;
        }
        tmp.after = newNode;
        newNode.before = tmp;
        newNode.after = head;
    }

    public void addHead( E data){
        Node<E> newNode = new Node<>(data);
        Node<E> tmp = head;

        newNode.after = head.after;
        newNode.before = head;
        head.after = newNode;
    }

    public void delete( E data){
        Node<E> tmp = head;

        while(tmp.after != head){
            if(tmp.element.equals(data)){
                tmp.before.after = tmp.after;
                tmp.after.before = tmp.before;
            }else {
                tmp = tmp.after;
            }
        }
    }

    public int getLength(){
        Node<E> tmp = head;
        int size = 0;

        while(tmp.after != head){
            tmp = tmp.after;
            size ++;
        }

        return size;
    }

    public void show(){
        Node<E> tmp = head;

        while(tmp.after != head){
            tmp = tmp.after;
            System.out.print(tmp.element + " ");
        }

        System.out.println();
    }

    public E findValue(int index){
        Node<E> tmp = head;
        int num = 0;

        while(tmp.after != head){ //第一个结点(不是头结点)的下标是0
            if( num == index)
                return tmp.element;
            tmp = tmp.after;
            num ++;
        }

        return null;
    }

猜你喜欢

转载自blog.csdn.net/qq_43336822/article/details/102532096