数据结构笔记_链表

一.链表

线性数据结构:

  • 动态数组
  • 队列

以上三种数据结构的底层均是依托于静态数组,解决自动变容问题靠的是reSize操作。

 

链表的认识:

  • 是线性数据结构
  • 是一种真正的动态数据结构,同时也是最简单的动态数据结构
  • 更深入的理解引用(指针)
  • 更深入的理解递归
  • 辅助组成其他数据结构

 

链表(Linked List):

  • 数据存储在 “节点”(Node)中

class Node{
    E e;
    Node next;
}
  • 优点:是真正的动态存储,不需要处理固定容量的问题 ,需要存储多少个数据就生成多少个节点
  • 缺点:丧失了数组的随机访问的能力

 

数组和链表的对比

  • 数组最好用于索引有语义的情况,数组最大的优点就是支持快速查询
  • 链表不适合用于索引有语义的情况,链表最大的优点就是动态的存储

 

链表的添加操作:

  • 链表不同于数组,在链表中,最容易添加的位置是头部,直接将新节点的指针指向头结点,然后更新头结点为新添加的节点

  • 在链表中间添加元素的过程:先找到添加位置的前一个节点prev,然后经过两步操作(不可调换位置)完成,(头部节点是没有前一个节点的,需特殊处理)

 

为链表设立虚拟头结点:

  • 由于链表中的头结点相比于其他节点有些特殊,即头结点没有前缀节点,故在实际操作中头结点经常需要特殊考虑。为了解决头结点的特殊性,链表实现中一个很常用的技巧就是为链表添加一个虚拟头结点(dummyHead)。
  • 虚拟头结点不存储数据,但需要指向一个真正的头结点(真正的头结点在初始化时可以为null,即 dummyHead = new Node(null, null) ),换句话说就是即使对于一个空链表来说,它也是存在一个节点的,这个节点就是 dummyHead = new Node(null, null) 。
  • 虚拟头结点仅仅是为了程序编写的方便。

 

链表中的删除操作:

 

链表的查找和遍历操作: 

          链表的遍历需要通过新建引用(指针)的方式实现,指针的后移操作为  cur=cur.next

//for循环遍历整个链表
        for(Node cur = dummyHead.next; cur != null; cur=cur.next){
           //do something
        }

//while循环遍历整个链表
        Node cur = dummyHead.next;
        while(cur != null){

            //do something

            //循环退出条件
            cur = cur.next;
        }

//查找链表中索引为index(索引从0开始)的元素
       Node cur = dummyHead.next;
       for(int i=0; i<index; i++){
            cur = cur.next;
       }

 

链表的时间复杂度分析:

     由于链表的增删改查操作都需要遍历查找过程,所以它们的时间复杂度均为O(n), 但是,对表头的增删查操作,其时间复杂度为O(1)。

     基于以上特点,可以将链表头作为栈顶来实现一个栈。

 

链表的简单改进:带有尾指针的链表,可以用于实现队列

  • 在头结点增加和删除元素都容易,时间复杂度均为O(1)
  • 在尾节点添加节点容易,时间复杂度为O(1),但删除节点不容易(因为指针是单向的,删除尾节点需要从头节点遍历找到尾节点的前一个元素)
  • 基于以上特点,带有尾指针的链表,可以用于实现队列

 

链表的进一步改进:双链表

在头结点和尾节点的增加和删除元素时,时间复杂度均为O(1)

链表的进一步改进:循环双向链表

将双链表添加虚拟头结点,并将指向null的尾节点改为指向虚拟头结点

数组链表:

       在数组中不仅仅只存储一个值,还要存储一个指向下一个空间的索引(数组下标),在明确知道要处理的元素有多少个时,可以考虑使用这种链表。

 

二.链表与递归

递归:

  • 本质上,是将原来的问题转化为更小的同一问题 

//递归求和  计算arr[l, n)这个区间内所有数字的和
    private static int sum(int[] arr, int rangeL){
        if(rangeL == arr.length){
            return 0;
        }

       return  arr[rangeL]+sum(arr, rangeL+1);
    }

 

编写递归程序的基本原则:

      所有的递归程序都可以分为两部分:

  1. 求解最基本问题,这个最基本问题是不能自动解决的,需要通过编写程序解决,但这个最基本问题往往十分简单;
  2. 把原问题转化为更小的问题,这部分是递归的核心,即需要根据最小问题的解构造出原问题的解 

 

编写递归程序的经验 (宏观解读) :

  • 注意递归函数的“宏观”语义;(如:计算一个数组内一段区间的和)
  • 递归函数就是一个函数,用来完成完成一个功能,函数A里调用函数B与函数A里调用函数A本质上是一样的,可以把递归函数想成一个普通的子函数,这个子函数会完成一定的功能(宏观语义)。

 

链表天然的递归性质:

        我们可以把链表看作是一个一个连接的节点,换个思路,也可以看作是一个节点后面挂载了一个更短的链表,直到最后null也是一个链表。对于链表中的许多操作都可以利用递归的思路来完成。

 利用递归解决链表中删除元素的问题(leetcode 203) https://blog.csdn.net/jt102605/article/details/83926604

 

递归函数的“微观解读”:

  • 递归函数的调用本质就是函数的调用,只不过调用的函数是自己而已。
  • 递归调用是有代价的:函数调用+系统栈空间

 

1>. 数组求和的微观过程:

 

2>. 链表删除元素的微观过程:

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值