学习笔记:Java中的数据结构链表

一、链表

数组作为一种数据存储结构具有一定的缺陷,在无序数组中,查找是低效的,在有序数组中,插入效率很低,不管哪种数组删除效率都很低,并且数组创建后大小不可以改变。
链表机制灵活,可以取代数组,成为其它存储结构的基础。

链节点

链节点是某个类的对象,
每个链表中有许多类似的链节点,
每个连接点包含数据项和一个对下一个链节点的引用。

public class ListNode {
    int data;//数据项
    ListNode next;//引用

    public ListNode() {
    }

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

    public ListNode(int data, ListNode next) {
        this.data = data;
        this.next = next;
    }

    @Override
    public String toString() {
        return   "  "+data ;
    }
}

在数组中,可以通过下标号直接访问数组中的元素。
在链表中,寻找一个元素的唯一方法是沿着这个元素的链一直向下访问

二、单链表

单链表的由单链节点实现,只有一个记录首节点的属性;创建一个单链表,没有数据项的情况下,首节点初始化值是null.

public class ListNode {
    int data;//数据项
    ListNode next;//引用

    public ListNode() {
    }

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

    public ListNode(int data, ListNode next) {
        this.data = data;
        this.next = next;
    }

    @Override
    public String toString() {
        return   "  "+data ;
    }
}

class LinkList {
    ListNode first;//维护链表头


    public LinkList() {
        //链表创建时没有数据项
        this.first = null;
    }

    public boolean isEmpty() {
        return this.first == null;
    }

    //头插法
    public void insertFirst(int data) {
        ListNode newNode = new ListNode(data);
        newNode.next = first;
        first = newNode;
    }

    //删除第一个节点
    public void deleteFirst() {
        if (!this.isEmpty()) {
            first = first.next;
        }
    }

    //查找给定元素的索引
    public int indexFind(int data) {
        //声明一个指针
        ListNode cur = first;
        int count = 0;
        while (cur != null) {
            if (cur.data == data) {
                return count;
            } else {
               cur = cur.next;
               count++;
            }
        }
        return -1;
    }

    //删除指定的元素,有两个或多个相同的元素删除索引最小的,f返回索引
    public int deletNode(int data) {
        //声明一个指针
        ListNode cur = first;
        //声明一个指针,记录单链表的前一个元素
        ListNode prev = null;
        int count = 0;
        while (cur != null) {
            if (cur.data == data) { //找到位置
                if (prev == null) { //删除第一个元素
                    first = first.next;
                    break;
                } else {           //删除其它位置元素
                    prev.next = cur.next;
                    break;
                }
            } else {
                prev = cur;     //记录前一个元素位置
                cur = cur.next;
                count++;
            }
        }
        return count;
    }

    //遍历
    public void dispaly() {
        //声明一个指针
        ListNode cur = first;
        System.out.println("链表节点:");
        while (cur != null) {
            System.out.println(cur.toString());
            cur = cur.next;
        }
    }

}

测试代码:

@Test
    public void testListNode() {
        LinkList linkList = new LinkList();
        //插入节点
        linkList.insertFirst(5);
        linkList.insertFirst(4);
        linkList.insertFirst(3);
        linkList.insertFirst(2);
        linkList.dispaly();

        //删除头节点
        linkList.deleteFirst();
        linkList.deleteFirst();
        linkList.dispaly();

        //查找指定的元素
        System.out.println("元素索引:  "+linkList.indexFind(5));
        System.out.println("元素索引:  "+linkList.indexFind(4));

        //删除指定的元素,返回索引
        System.out.println("删除的元素位置:"+linkList.deletNode(5));
    }

运行结果:

链表节点:
2   3   4   5 
链表节点:
4   5 
元素索引:  1
元素索引:  0
删除的元素位置: 1

单链表只维护一个首节点的位置,查找和删除都需要沿着首节点一个一个元素找下去。因为节点类只有一个对后一个元素的引用,所以在中间位置插入元素需要记录前一个元素的位置,删除同样也需要记录前一个节点的位置。

三、双端链表

双端链表和单链表实现类似,不同的是除了有对第一个链接点的引用,还有对最后一个链接的引用。可以在链表末尾方便的插入元素,至于遍历和查找、删除指定的元素和普通的单链表实现方法一致。
在链表末尾方便的删除的元素,双端链表也不可以,因为没有一个引用指向倒数第二个节点。假若需要方便的删除最后一个节点,需要实现双向链表。

public class FirstLastList {
    ListNode first;
    ListNode last;

    public FirstLastList() {
        this.first = null;
        this.last = null;
    }

    //头插法
    public void insertFirst(int data) {
        ListNode newNode = new ListNode(data);
        if (first == null) {  //插入首个元素
            last = newNode;
        }
        newNode.next = first;
        first = newNode;
    }

    //尾插法
    public void insertLast(int data) {
        ListNode newNode = new ListNode(data);
        if (this.isEmpty()) { //如果是插入首个元素
            first = newNode;
            last = newNode;
        } else {
            last.next = newNode;
            last = newNode;
        }
    }

    //删除首个元素
    public void deleteFirst() {
        if (!this.isEmpty()) { //判断是否非空
            first = first.next;
            if (first == null) { //判断是否非空
                last = null;
            }
        }
    }

    //判断是否空
    public boolean isEmpty() {
        return first == null;
    }
    
    //遍历
    public void display() {
        //声明一个指针
        ListNode cur = first;
        System.out.println("链表节点:");
        while (cur != null) {
            System.out.print(cur.toString()+" ");
            cur = cur.next;
        }
        System.out.println(" ");
    }

    //和单链表一样
    //查找给定元素的索引
    public int indexFind(int data) {
        //声明一个指针
        ListNode cur = first;
        int count = 0;
        while (cur != null) {
            if (cur.data == data) {
                return count;
            } else {
                cur = cur.next;
                count++;
            }
        }
        return -1;
    }
     
}

测试代码:

 @Test
    public void testFirstLastList() {
        FirstLastList firstLastList = new FirstLastList();

        //测试头插法
        firstLastList.insertFirst(100);
        firstLastList.insertFirst(80);
        firstLastList.insertFirst(50);
        firstLastList.insertFirst(10);
        firstLastList.display();

        //测试尾插法
        firstLastList.insertLast(1000);
        firstLastList.insertLast(2000);
        firstLastList.insertLast(6000);
        firstLastList.display();

        //测试查找
        System.out.println("元素所在位置 "+firstLastList.indexFind(2000));;
    }

运行结果:

链表节点:
  10   50   80   100  
链表节点:
  10   50   80   100   1000   2000   6000  
元素所在位置 5

双端链表适合实现队列。

三、有序链表

有序链表字面意思就是数据在链表中按关键字有序排列的。
有序链表相比有序数组,数据插入速度要快一些,而且链表的大小不固定,方便拓展。
有序链表中插入一个数据项,需要先找到插入位置,考虑插入位置在表头、表尾、中位置的情况。

public class SortedList {
    ListNode first;

    public SortedList() {
        this.first = null;
    }

    //插入
    public void insert(int data) {
        ListNode newNode = new ListNode(data);
        //声明一个指针,记录前一个元素
        ListNode prev = null;
        //声明一个指针
        ListNode cur = first;
        while (cur != null && cur.data <= data) {
            prev = cur;
            cur = cur.next;
        }
        if (prev == null) {     //插入位置在表头或者链表为空
             first = newNode;
        } else {                //其它位置,表中间位置或者表尾
           prev.next = newNode;
        }
        newNode.next = cur;         //都要进行的一步
    }

    //删除头元素
    public ListNode deleteFirst() {
        if (!isEmpty()) {
            ListNode temp = first;
            first = first.next;
            return temp;
        }
        return null;
    }

    //判断非空
    public boolean isEmpty() {
        return first == null;
    }

    //遍历
    public void display() {
        //声明一个指针
        ListNode cur = first;
        System.out.println("链表节点:");
        while (cur != null) {
            System.out.print(cur.toString()+" ");
            cur = cur.next;
        }
        System.out.println(" ");
    }

  
    //和单链表一样
    //查找给定元素的索引
    public int indexFind(int data) {
        //声明一个指针
        ListNode cur = first;
        int count = 0;
        while (cur != null) {
            if (cur.data == data) {
                return count;
            } else {
                cur = cur.next;
                count++;
            }
        }
        return -1;
    }
}

测试代码:

 @Test
    public void testSortedList() {
        SortedList sortedList = new SortedList();

        //插入元素
        sortedList.insert(100);
        sortedList.insert(11);
        sortedList.insert(23);
        sortedList.insert(18);
        sortedList.insert(1100);
        sortedList.insert(1200);
        sortedList.display();

        //删除头元素
        System.out.println("删除的最小元素是: "+sortedList.deleteFirst().toString());;
        System.out.println("删除的最小元素是: "+sortedList.deleteFirst().toString());;

        //查找
        System.out.println("元素的索引是: "+sortedList.indexFind(100));
    }

运行结果:

链表节点:
  11   18   23   100   1100   1200  
删除的最小元素是:   11
删除的最小元素是:   18
元素的索引是: 1

有序链表寻找、和删除指定元素同样需要沿着第一个链节点找,与单链表一样。不过删除最小的元素,只需要O(1)的时间,比较快速,即删除首元素方法。

五、双向链表

双向链表中每个链节点有两个指向其它链节点的引用,而不是一个,所以既允许向后遍历,也允许向前遍历。
双向链表每次插入或删除,要处理三个链节点的引用
双向链表不必是双端链表,实现了有助于删除最后一个元素

1.双向链表的节点类
class Node {
    int data;
    Node prev;
    Node next;

    public Node(int data) {
        this.data = data;
        this.prev = null;
        this.next = null;
    }

    @Override
    public String toString() {
        return ""+data;
    }
}
2.双向链表创建初始化,维护两个指针,方便访问首元素和尾元素
public class DoubleLinkList {
    //采用双端链表的形式
    Node first;
    Node last;

    public DoubleLinkList() {
        this.first = null;
        this.last = null;
    }

    //判断是否空
    public boolean isEmpty() {
        return first == null;
    }
}
3.双向链表的插入方法相比单向链表,需要多维护队前一个链节点的引用.
   //头插法
   //与单链表相比需要维护链节点对前一个的引用
   public void insertFirst(int data) {
       Node newNode = new Node(data);
       if (!isEmpty()) {  //非空,不需要维护last指针
           newNode.next = first;
           first.prev = newNode;
           first = newNode;
       } else {           //空,需要维护last指针
           last = newNode;
           first = newNode;
       }
   }

 //尾插法,有双端链表的情况才有这种方法
   //相比单链表,需要多维护链节点对前一个的引用
   public void insertLast(int data) {
       Node newNode= new Node(data);
       if (!isEmpty()) {   //非空,不需要维护first指针
           last.next = newNode;
           newNode.prev = last;
           last = newNode;
       }else {                //空,需要维护first指针
           first = newNode;
           last = newNode;
       }
   }

测试代码:

@Test
    public void testDoubleLinkList() {
        DoubleLinkList doubleLinkList = new DoubleLinkList();

        //测试头插法
        doubleLinkList.insertFirst(19);
        doubleLinkList.insertFirst(13);
        doubleLinkList.insertFirst(100);
        doubleLinkList.display();

        //测试尾插法
        doubleLinkList.insertLast(30);
        doubleLinkList.insertLast(40);
        doubleLinkList.insertLast(20);
        doubleLinkList.display();
}

运行结果:

双向链表的元素: 
100  13  19    
双向链表的元素: 
100  13  19  30  40  20    
4.插入和删除首尾元素

双向链表同样可以删除首个元素和末尾元素,同样需要注意对前一个链节点引用的维护。对于只有一个元素,需要考虑维护首为指针。

    //删除第一个链节点
    //相比单链表,需要多链节点对前一个的引用
    public void deleteFirst() {
        if (!isEmpty()) {  //判断非空
            if (first.next != null) {  //如果不止一个元素,不需要维护last指针
                first.next.prev = null;
                first = first.next;
            } else {                    //如果只有一个元素,需要维护last指针
                last = null;
                first = null;
            }
        }
    }

    //删除最后一个节点,双向链表相比其它链表最方便的特性
    public void deleteLast() {
        if (!isEmpty()) {       //判断非空
            if (first.next != null) {  //如果不止一个元素,不需要维护first指针
                last.prev.next = null;
                last = last.prev;
            } else {                    //如果只有一个元素,需要维护last指针
                first = null;
                last = null;
            }
        }
    }

测试代码:

        //删除首个元素
        doubleLinkList.display();
        doubleLinkList.deleteFirst();
        doubleLinkList.display();

        //删除末尾元素
        doubleLinkList.display();
        doubleLinkList.deleteLast();
        doubleLinkList.display();

运行结果 :

双向链表的元素: 
100  13  19  30  40  20    
双向链表的元素: 
13  19  30  40  20    
双向链表的元素: 
13  19  30  40  20    
双向链表的元素: 
13  19  30  40
5.元素的插入和删除

双向链表的插入删除方法,需要维护三个链节点之间的应用。同样也需要插入删除位置在表头、表尾、表中间位置的情况。

    //在某个特定的链节点插入元素
    public boolean insertAfter(int key,int data) {
        //声明一个指针
        Node cur = first;
        while (cur != null && cur.data != key) {       //寻找要插入的元素位置
            cur = cur.next;
        }
        if (cur == null) {              //找不到退出
            return false;
        }
        Node newNode = new Node(data);      //找到
        if(cur != last) {                   //要插入的位置不是末尾
            //先于后一个链节点相连
            newNode.next = cur.next;
            cur.next.prev = newNode;
            //再与前一个链节点相连
            cur.next = newNode;
            newNode.prev = cur;
        } else {                            //插入的位置是末尾,要维护last指针
            cur.next = newNode;
            newNode.prev = cur;
            last = newNode;
        }
        return true;
    }

    //删除元素
    public boolean deleteKey(int key) {
        if (isEmpty()) {
            return false;
        }
        //声明一个指针
        Node cur = first;
        while (cur != null && cur.data != key) {       //寻找位置
            cur = cur.next;
        }
        if (cur == null) {              //找不到退出
            return false;
        }
        if (cur == first) {             //删除的是第一个元素,需要维护first指针
            first.next.prev = null;
            first = first.next;
        } else{                         //删除其它位置元素
            cur.prev.next = cur.next;
        }
        if (cur == last) {              //删除的是末尾最后一个元素,需要维护last指针
            last.prev.next = null;
            last = last.prev;
        } else {
            cur.next.prev = cur.prev;
        }
        return true;
    }

测试代码:

        //测试在指定元素后面插入新元素
        System.out.println("测试");
        doubleLinkList.insertAfter(40,41);
        doubleLinkList.display();

        //测试删除指定的元素
        doubleLinkList.deleteKey(13);
        doubleLinkList.display();

        //测试向后遍历
        doubleLinkList.displayBackward();

运行结果:

测试
双向链表的元素: 
13  19  30  40  41    
双向链表的元素: 
19  30  40  41    
双向链表的元素: 
41  40  30  19 
6.遍历

双向链表可以向前遍历。

//向前遍历
    public void displayBackward() {
        Node cur = last;
        System.out.println("双向链表的元素: ");
        while (cur != null) {
            System.out.print(cur.toString()+"  ");
            cur = cur.prev;
        }
        System.out.println(" ");
    }
posted @ 2021-07-30 17:08  夏天的风key  阅读(114)  评论(0编辑  收藏  举报