单向链表数据结构及相关算法题

1 单链表的解释

(1)单链表的组成

单链表:单向链表是一种线性表,由各个节点(Node)组成,其数据在内存中存储是不连续的,它存储的数据分散在内存中,每个结点只能也只有它能知道下一个结点的存储位置。由N各节点(Node)组成单向链表,每一个Node记录本Node的数据(data)及下一个Node的地址(指针,引用)。向外暴露的只有一个头节点(Head),我们对链表的所有操作,都是直接或者间接地通过其头节点来进行的,其最后一个节点指针域指向为空NULL。

由上面的信息我们知道一个链表由一下几个要素:

  • 组成:数据域+指针域
  • 数据域存储具体数据,指针域存储指向下一个节点的地址,简单来说就是存储下一个节点的地址,注意这里是存储的下一个节点的位置信息,不是数据域的地址信息。
  • 在java中可简单理解为一个节点即为一个对象,一个对象中包括数据属性和引用属性,该引用指向下一个对象(节点)。在java中定义一个引用类型的变量时,实际上就是定义该对象所在的地址。
  • 单链表的尾节点其指向为NULL

java引用和指针的区别可参考如下:

引用才是真正含义上的地址,指针不是地址是内存地址,两者关注对象不一样,引用关注的是对象本身,地址随对

象的改变而改变,因为在创建对象的时候,既存数据本身,也将该对象的地址保存其来,和普通变量的地址不一

样,普通变量的地址就指的是内存地址,C中的指针关注的对象就是内存地址,他不关心该地址存储的是什么,只

关系该地址本身,如果该地址内存空间中东西搬走了,指针不会自动改变指向,java中对象重建了地址也变了)

具体定义参考如下图所示:

java以对象来理解具体如下图所示:

2 单链表的实现

3 单链表反转

(1)迭代法实现

先来看示意图,以链表1->2->3->4->5为例:

链表的解题思路:

(1)已知信息头节点

(2)定义前驱节点、后继节点

(3)构造辅助节点

(4)构建虚拟头节点(可以避免处理头节点的一些问题)

package jttl.jxresearch.com.hive.udf.test;

import java.util.HashSet;
import java.util.TreeSet;

public class MyLink {

//   // Node head = null;
//    class Node {
//
//       Node next = null;
//       int data;
//
//       public Node(int data){
//
//           this.data = data;
//
//       }
//   }

    public static void main(String[] args) {


        MyLink list = new MyLink();
        Node node = new Node(1);
        list.insert(1,node);
        list.insert(2,node);
        list.insert(6,node);
        list.insert(3,node);
        list.insert(4,node);
        list.insert(5,node);
        list.insert(6,node);
        list.display(node);
        Node head = list.removeElements(node, 6);
        //LinkNode head = list.removeElementsByStack(list.head, 6);
        //LinkNode head = list.removeRepeat(list.head);
        list.display(head);
    }


    /**
     * 向链表中插入数据
     *
     * @param data
     */

    public void addNode(int data,Node head){

        Node node = new Node(data);
        if (head == null) {
            head = node;
            return;
        }
       //构建辅助节点
        Node cur = head;
        while(cur.next != null){
            cur = cur.next;

        }
        //遍历结束时,此时tmp节点地址指向为null说明是最后一个节点,只需要将tmp.next赋值为添加节点地址即可
        cur.next = node;

    }
    public void insert(int val,Node head){
        if(head==null){
            head = new Node(val);
        }else{
            Node cur = head;
            while(cur.next!=null){
                cur = cur.next;
            }
            cur.next = new Node(val);
        }
    }

    public void display(Node head){
        System.out.print("list:");
        Node cur = head;
        while(cur!=null){
            System.out.print(cur.data+"->");
            cur = cur.next;
        }
        System.out.print("null");
        System.out.println();
    }

    /**
     *
     * @return 返回节点长度
     */
    public int length(Node head){

        int length = 0 ;

        Node tmp = head;

        while(tmp != null){

            length ++;
            tmp = tmp.next;

        }

        return length;

    }

    /**
     * 删除链表中指定值的节点
     *
     * @param head,dataue
     * @return
     */
   public Node removeElements(Node head,int data){


      //如果是头节点需要删除

       while(head != null){

           if(head.data != data){

               break;

           }

           head = head.next;


       }

       //如果不是头节点需要删除

       Node pre = head;
       Node cur = head.next;
       while (cur != null){

           if (cur.data != data){
               pre = cur;
               
           }
           pre.next = cur.next;

           cur = cur.next;
       }

       return head;
   }
   //这道题还有一个变种,就是删除重复的节点,不是将重复的所有节点删除,而是保留第一个节点,后面如果重复,就删除。这里也给出代码:
   //先给出一道其他算法去重的题目理解一下
    /**
     * 把字符串去重,并升序排序
     * @param str
     * @return
     */
    public static String sort2(String str) {

        //把String变成单一字符数组(获取字符数组)
        String[] chars = str.split("");

        //把字符串数组放入TreeSet中,根据set元素不重复的特性去掉重复元素。根据treeSet的有序性排序
        TreeSet<String> treeSet = new TreeSet();
        for (String s : chars) {
            treeSet.add(s);
        }

        //把treeSet拼接成字符串
        str = "";
        for (String s : treeSet) {
            str += s;
        }
        return str;
    }

    public Node removeRepeat(Node head){
        if(head==null){
            return null;
        }
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(head.data);
        //定义前驱节点
        Node pre = head;
        //定义当前节点
        Node cur = head.next;
        while(cur!=null){
            //如果当前节点不为null则判断当前节点的数据是否在集合中,存在就删除,不存在就继续遍历
            if(set.contains(cur.data)){
                //删除当前节点
                pre.next = cur.next;
            }else{
                set.add(cur.data);
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }


    /**
     * 单链表的反转
     *
     * @param head,dataue
     * @return
     */

    public Node reverseLink(Node head){

        //定义前驱节点,初始化为NULL。链表反转后头节点会指向NULL所以将前驱节点定义为NULL
        Node pre = null;
        //定义当前节点
        Node cur = head;
       //当当前节点不为NULL的时候,进行遍历
        while(cur != null){
           //定义下一节点。将当前节点的的指向信息保存起来,因为在下一步,当前节点的指向信息被覆盖了
            Node tmp = cur.next;
            //将当前节点的中指向下一个节点的地址修改为前驱节点
            cur.next = pre;
            //在遍历的过程中,游标(指针)移动。每遍历一次,前驱节点向前移动一次,当前节点向前移动一次。
            //也就是将当前节点的地址给前驱节点,当前节点移动到下一个节点的位置,如此循环
            pre = cur;
            cur = tmp;

        }
        //此时pre是头节点
      return pre;
    }

    /**
     * 单链表的反转:反转从位置m到n的链表(1=<m<=n<=链表长度)。虚拟头节点
     *
     * @param head,dataue
     * @return
     *
     * 解析:本题,还有下一个要介绍的困难级别的题目,算是一种类型题,它们都是要对链表中特定区间中的节点进行操作。面对这种题目,有固定的套路可以帮你简化解题思路,套路如下:
     *
     *     给链表添加虚拟头节点 dummy,这样就不需要再单独考虑头节点了,可以省去很多麻烦;
     *     找到需要操作的链表区间,区间起始节点用 start 表示,结束节点用 end 表示;
     *     对区间上的链表进行操作;
     *     将操作后的链表重新接回原链表,这里我们需要另外两个变量,前驱节点 prev 和后继节点 successor。
     */



}

链表通过虚拟头结点处理问题

为链表构建虚拟头节点,如下图所示

 构建虚拟头节点为NULL称为dummyHead

构建dummyHead节点之后,际上就是让前驱节点指向NULL而不是head

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

}

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node head;
    private int size;

    public LinkedList(){
        head = null;
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
//        Node node = new Node(e);
//        node.next = head;
//        head = node;

        head = new Node(e, head);
        size ++;
    }



    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        if(index == 0)
            addFirst(e);
        else{
            Node prev = head;
            for(int i = 0 ; i < index - 1 ; i ++){

                prev = prev.next;

             }
                

//            Node node = new Node(e);
//            node.next = prev.next;
//            prev.next = node;
           //优雅的写法
            prev.next = new Node(e, prev.next);
            size ++;
        }
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
   //优雅的写法,通过虚拟头结点,避免处理头节点的问题
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node dummyHead  = new Node(null,null);

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }


    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }
}

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedList(){
        dummyHead = new Node();
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
        add(0, e);
    }

    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }

    // 获得链表的第index(0-based)个位置的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E get(int index){

        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        return cur.e;
    }

    // 获得链表的第一个元素
    public E getFirst(){
        return get(0);
    }

    // 获得链表的最后一个元素
    public E getLast(){
        return get(size - 1);
    }

    // 修改链表的第index(0-based)个位置的元素为e
    // 在链表中不是一个常用的操作,练习用:)
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        cur.e = e;
    }

    // 查找链表中是否有元素e
    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e))
                return true;
            cur = cur.next;
        }
        return false;
    }

    // 从链表中删除index(0-based)位置的元素, 返回删除的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E remove(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode.next = null;
        size --;

        return retNode.e;
    }

    // 从链表中删除第一个元素, 返回删除的元素
    public E removeFirst(){
        return remove(0);
    }

    // 从链表中删除最后一个元素, 返回删除的元素
    public E removeLast(){
        return remove(size - 1);
    }

    // 从链表中删除元素e
    public void removeElement(E e){

        Node prev = dummyHead;
        while(prev.next != null){
            if(prev.next.e.equals(e))
                break;
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }
}

leetcode习题详解

public class ListNode {

    int val;
    ListNode next;

    public ListNode(int val) {
        this.val = val;
    }

    //链表节点的构造函数
    //使用arr为参数,创建一个链表,当前的ListNode为链表的头节点
    public ListNode(int[] arr){

        if(arr.length == 0 || arr == null){

            throw new IllegalArgumentException("arr can not be empty");

        }

        this.val = arr[0];
        ListNode cur = this;
        for (int i = 1; i <arr.length ; i++) {

            cur.next = new ListNode(arr[i]);
            cur=cur.next;

        }

    }
    @Override
    public String toString(){

        StringBuilder res = new StringBuilder();

        ListNode cur = this;
        while(cur != null){
            res.append(cur.val + "->");
            cur=cur.next;
        }
        res.append("NULL");
        return res.toString();

    }
}
public class testLeetcode {
     //虚拟头节点解法
    public ListNode removeElements(ListNode head,int val){

        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode prev=dummyHead;
        ListNode cur=prev.next;

        while(cur!=null){

            if(cur.val == val){

                prev.next=cur.next;

            }
            //不能写在else语句块中,否则while条件执行不了,while条件更新的状态一定在while语句块中
            prev = cur;
            cur=cur.next;
        }
        //返回头节点
        return dummyHead.next;


    }

    public ListNode removeElements2(ListNode head,int val){


        //如果是头节点需要删除

        while(head != null){

            if(head.val != val){

                break;

            }

            head = head.next;


        }

        //如果不是头节点需要删除

        ListNode pre = head;
        ListNode cur = head.next;
        while (cur != null){

            if (cur.val == val){
                pre.next = cur.next;

            }
            pre = cur;
            cur = cur.next;
        }

        return head;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 6, 5, 6};
        ListNode head = new ListNode(nums);
        System.out.println(head);
        ListNode res = (new testLeetcode()).removeElements(head, 1);
        System.out.println(res);
        
    }
}

猜你喜欢

转载自blog.csdn.net/godlovedaniel/article/details/115121236