深入浅出线性表

顾名思义像线一样的性质的表,有一个打头的,有一个结尾的,中间元素一个跟个一个;
线性表List:零个或多个元素的有限序列
序列:元素之间是有顺序的,当表的长度为0称之为空表


1. 线性表的顺序存储结构

顺序存储定义:用一段地址连续的存储单元依次存储线性表的数据元素
存储方式:说白了就是在内存找一块连续的存储单元,就是一维数组
数组长度和线性表长度区别:线性表的长度(随着元素的插入删除是变化的)就是数据元素的个数 数组长度大于或等于这个
存储地址:存储器每个存储单元都有自己的编号就是内存地址,select的时候直接根据数组下标get对应的数据元素 O ( 1 ) O(1) O(1)

1.1 顺序存储结构的插入和删除

获取元素:GetElement(int index) 直接通过下标获取 时间复杂度 O ( 1 ) O(1) O(1)
插入操作插入操作图解
思路

  1. 如果插入位置不合适,抛出异常
  2. 如果线性表大于等于数组长度,则抛出异常或扩容
  3. 从最后一个元素开始到第i个位置的元素进行拷贝。分别将它们都向后移动一个位置
  4. 插入元素到i位置
  5. 表长加1
// 在进行第三步的时候 有个地方需要注意 记得从末尾开始往后移
public static void main(String[] args) {
    
    
        int[] intArr = new int[8];
        for (int i = 0; i < 6; i++) {
    
    
            intArr[i] = i;
        }
        int i = 3;// 插队的数组下标index
        System.out.println(intArr.length);
        for (int k = 6; k >= i; k--) {
    
    
            intArr[k + 1] = intArr[k];
        }
        intArr[i] = 69;
        Arrays.stream(intArr).forEach(System.out::println);
    }
// print result
0
1
2
69
3
4
5
0

删除元素

  1. 如果删除位置不合理 抛出异常
  2. 删除元素
  3. 从删除的位置开始遍历到最后一个元素位置,分别向前移动一个位置
  4. 表长减一

分析插入和删除的时间复杂度
对于最好的情况:如果元素要插入或删除最后一个位置,此时为 O ( 1 ) O(1) O(1),不需要移动其他元素
最坏的情况:如果元素查到首位 或者删除第一个元素。意味着所有的元素要移动时间复杂度为 O ( n ) O(n) O(n)
对于一般情况下,插入或删除第i个元素,取n次平均的情况: n + ( n − 1 ) + . . . + 2 + 1 n = n + 1 2 \frac{n+(n-1)+...+2+1}n=\frac{n+1}2 nn+(n1)+...+2+1=2n+1 可以得出时间复杂度还是 O ( n ) O(n) O(n)
思考:数组的这样的线性表顺序存储结构的优缺点?


2. 线性表的链式存储结构

链式存储结构的特点:任意一组存储单元存储数据元素,不必须是一段连续的存储空间。
在顺序结构当中只需存储数据元素,现在的链式存储结构,除了需要存储数据元素以外,还需要存储它的后继元素的存储地址
这样包含数据元素和后继元素的存储地址的数据元素,称为Node 节点n个节点链接成一个链表
因为每个节点中只包含一个指针域,所以叫单链表
对于单链表,我们把链表的第一个节点叫做 头节点Head Node

// 这里自定义一个Node对象
public class ListNode {
    
    
    private int val;// 存储的值
    private ListNode nextNode;// 后继节点

    public ListNode(int value){
    
    
        this.val = value;
    }
    public void setNextNode(ListNode nextNode1){
    
    
        this.nextNode = nextNode1;
    }

    public ListNode getNextNode(){
    
    
        return this.nextNode;
    }
    public int getVal(){
    
    
        return this.val;
    }
}

public class ReverseNode {
    
    
    // what is data structure? array is king  Algorithm and data structures is program
    // what is Time complexity: 时间复杂度 是一个定性描述程序运行时间
    // what is Space complexity: 空间复杂度
    // why the index of array is 0 start? 1: 可以节省编译时间 2: Python的作者也觉得现代语言用0开始比较优雅 3: 在支持指针的语言 比如C语言 数组的下标一般用偏移量来表示
    // 实现一个链表反转?
    private static final int LENGTH = 6;

    public static void main(String[] args) {
    
    
        ListNode head = new ListNode(1);
        ListNode node1 = new ListNode(2);
        ListNode node2 = new ListNode(3);
        ListNode node3 = new ListNode(4);
        ListNode node4 = new ListNode(5);
        ListNode end = new ListNode(6);
        head.setNextNode(node1);
        node1.setNextNode(node2);
        node2.setNextNode(node3);
        node3.setNextNode(node4);
        node4.setNextNode(end);
        printListNode(head);
        printListNode(reverseListNode(head));
        printListNode(reverseListNode(end, 1, 4));
    }

    public static ListNode reverseListNode(ListNode head) {
    
    
        if (head == null) {
    
    
            return null;
        }
        ListNode previous = head;
        ListNode current = head.getNextNode();
        previous.setNextNode(null);
        while (current != null) {
    
    
            ListNode next = current.getNextNode();
            current.setNextNode(previous);
            previous = current;
            current = next;
        }
        return previous;
    }

    public static ListNode reverseListNode(ListNode headNode, int m, int n) {
    
    
        if (headNode == null || m >= n || n > LENGTH) {
    
    
            return null;
        }
//        // 增加一个哨兵节点
//        ListNode sentryNode = new ListNode(-1);
//        sentryNode.setNextNode(headNode);
        //head = sentryNode;
        ListNode head = headNode;
        // 保存获取第m-1个节点的node
        for (int i = 0; i < m - 1; i++) {
    
    
            head = head.getNextNode();
        }
        ListNode nodeMBefore = head;
        // 获取第m个节点
        head = head.getNextNode();
        ListNode nodeM = head;
        // 断掉m-1节点的下一个指针
        //nodeMBefore.setNextNode(null);
        // 获取第n个节点
        for (int j = 0; j < n - m; j++) {
    
    
            head = head.getNextNode();
        }
        ListNode nodeN = head;
        // 保存第n+1个节点node
        ListNode nodeNToNext = nodeN.getNextNode();
        // 断掉第n个节点的下一个指针 这个地方一定要断开
        nodeN.setNextNode(null);
        // 调用之前反转链表的方法 返回其实就是n节点
        ListNode reverseNode = reverseListNode(nodeM);
        // 把m-1节点指向nodeN
        nodeMBefore.setNextNode(reverseNode);
        // 把nodeM指向n+1节点
        nodeM.setNextNode(nodeNToNext);
        return headNode;
    }

    public static void printListNode(ListNode headNode) {
    
    
        ListNode head = headNode;
        while (head != null) {
    
    
            System.out.print(head.getVal());
            head = head.getNextNode();
            if (head != null) {
    
    
                System.out.print("->");
            }
        }
        System.out.println();
    }

}

以上是一个单链表的反转demo,加深自己的理解
Leetcode的单链表深拷贝题目

2.1 单链表的读取

回顾:
我们知道在线性表的顺序存储结构中,要获取任意一个位置的元素是 O ( 1 ) O(1) O(1)
但是在单链表中,获取第i个元素必须从头开始找,相对比较麻烦,思路:

  1. 声明一个结点P指向链表第一个结点,初始化j从1开始
  2. 当j<i&&P!=null,遍历链表,让P指针向后移动指向下一个结点,j累加1
  3. 在遍历时候考虑是否到链表末尾 加上判断该结点是否为空
  4. 查找成功 返回结点P的数据元素

这个算法取决于i的位置,每次从头开始找,因此最坏情况下的时间复杂度是 O ( n ) O(n) O(n)

2.2 单链表的插入和删除

单链表的插入图
注意插入的顺序一定不能换,思考一下什么是掉链?

// 这两句代码的顺序不可改变 一定要先获取结点的nextNode 赋值给插入结点的nextNode
s.setNextNode(p.getNextNode());
p.setNextNode(s);

3. 线性表的两种存储结构的优缺点

存储分配方式 时间复杂度 空间性能
顺序存储结构-数组 一段连续的存储空间 查找: O ( 1 ) O(1) O(1) 插入和删除: O ( n ) O(n) O(n) 需要预分配存储空间
链式存储结构-单链表 用一组任意的存储单元 查找: O ( n ) O(n) O(n) 插入和删除: O ( 1 ) O(1) O(1) 不受限制 灵活分配

思考:ArrayList or LinkList how to choose?

4. 循环链表

将单链表的尾结点的指针指向头结点,使得整个链表形成一个环,这种头尾相连的单链表简称为 循环链表
解决的问题:从任意一个结点开始 能够访问到链表当中的任意结点
循环条件的差异:单链表判断下一个是否是null;循环链表判断下一个是否是头结点即可
思考:怎么合并两个循环链表?

5.双向链表

为了克服单向链表单向性这个缺点,无法快速获取上一个结点的数据元素 O ( n ) O(n) O(n);双向链表double linked list就闪亮登场。
在单链表的基础上,增加一个指向前驱结点的指针;
双向链表图

// 如果设计双向链表的对象
public class DoubleLinkedList {
    
    
    private Integer value;// 结点值
    private DoubleLinkedList previousNode;// 前驱结点
    private DoubleLinkedList nextNode;// 后继结点

    public DoubleLinkedList(Integer value, DoubleLinkedList previousNode, DoubleLinkedList nextNode) {
    
    
        this.value = value;
        this.previousNode = previousNode;
        this.nextNode = nextNode;
    }

    public Integer getValue() {
    
    
        return value;
    }

    public void setValue(Integer value) {
    
    
        this.value = value;
    }

    public DoubleLinkedList getPreviousNode() {
    
    
        return previousNode;
    }

    public void setPreviousNode(DoubleLinkedList previousNode) {
    
    
        this.previousNode = previousNode;
    }

    public DoubleLinkedList getNextNode() {
    
    
        return nextNode;
    }

    public void setNextNode(DoubleLinkedList nextNode) {
    
    
        this.nextNode = nextNode;
    }
}

猜你喜欢

转载自blog.csdn.net/blackxc/article/details/107362207
今日推荐