数组和链表

157 阅读2分钟

数组和链表

数组

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

数组的插入、删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移,所以时间复杂度是O(n)。

链表

单链表

linked2.jpg

在链表中插入或者删除一个数,只需要考虑相邻结点的指针改变,所以对应的时间复杂度是O(1)

linked3.jpg

链表随机访问性能没有数组好,需要 O(n) 的时间复杂度。

循环链表

linked4.jpg

双向链表

linked5.jpg 双向链表需要额外的两个空间存储后继节点和前驱节点的地址,但可以支持双向遍历。

从结构上看,双向链表可以支持 O(1) 时间复杂度的情况下找到前驱节点,再某些情况下插入、删除等操作都要比单链表简单、高效。

双向循环链表

linked6.jpg

数组 VS 链表

内存分布

linked1.jpg

操作数组链表
访问O(1):直接下标访问O(n):需要遍历元素
内存大小固定,一组连续的内存空间需要额外的指针,内存空间不用连续
插入1. 头部插入:O(n) ,需要将每个元素向高位移动一个位置
2. 尾部插入:数据未满O(1);数组已满O(n),创建一个新的数组并复制所有的内容。
3. 中间插入:O(n),需要移动数据,平均情况下,时间与n成正比。
1. 头部插入:O(1),创建一个新节点,并调整头指针和该新节点的链接。
2. 尾部插入:O(n),需要遍历到尾部。
3. 中间插入:O(n),需要遍历到该位置然后调整链接。
删除1. 头部删除:O(n) ,需要将每个元素向低位移动一个位置
2. 尾部删除:不缩减数组 O(1);缩减数组 O(n),创建一个新的数组并复制所有的内容。
3. 中间删除:O(n),需要移动数据,平均情况下,时间与n成正比。
1. 头部删除:O(1),调整头指针链接。
2. 尾部删除:O(n),需要遍历到尾部。
3. 中间删除:O(n),需要遍历到该位置然后调整链接。
使用简单复杂,尤其在C/C++中更容易出现错误,比如段错误和内存泄漏。

链表反转

迭代实现

public ListNode reverseList(ListNode head) {
    if(head == null) {
        return head;
    }
    ListNode reverse = null;
    ListNode current = head;
    while(current != null) {
        ListNode next = current.next;
        current.next = reverse;
        reverse = current;
        current = next;
    }
    return reverse;
    
}

递归实现

public ListNode reverseList(ListNode head) {
    if(head == null || head.next == null) {
        return head;
    }
    ListNode last = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return last;
}