一.链表基础

动态数组、栈、队列底层都是依托静态数组实现的,靠resize来解决固定容量问题。

链表是真正的动态数据结构,是一种最简单的一种动态数据结构。

更深入的理解引用(或者指针)。

更深入的理解递归。

辅助成其他数据结构。

二.链表 LinkedList

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

class Node{

E e;

 Node next;

}

最后一个节点nxet = null

优点:真正的动态,不需要处理固定容量的问题。

缺点:丧失了随机访问的能力(即给出索引直接得到索引位置的元素)

数组与链表的比较:

1.数组最好用于索引有语意的情况

最大的优点:支持快速查询

2.链表不适合用于索引有语意的情况

最大的优点:动态

三.链表的方法实现

新建类LinkedList<E>

1.为了对用户屏蔽底层实现,在LinkedList中建立内部类Node。

在内部类中设置关于Node的构造方法

public class LinkedList<E> {
//节点设置为内部类,链表结构内可以访问Node,用户外部不可访问
//对用户屏蔽底层实现
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();
}
}

2.成员变量与基本方法

链表头要存储为head

数组尾部添加元素是非常方便的(size指向最后一个元素的下一位)

在链表头添加元素是非常方便的(head跟踪链表头)

基本的成员变量为head和size

  //基本成员变量
private Node head;
private int size;

基本方法

    //  构造函数
public LinkedList(){
head = null;
size = 0;
} //获取链表中元素个数
public int getSize(){
return size;
} //返回链表是否为空
public boolean isEmpty(){
return size == 0;

3.插入元素

1)向表头添加元素

a.设置插入元素为node

b.node的next指向head,即node.next = head

c.让head = node,即head = node

其实可以化为一句:head = new Node(e, head);

d.最后维护size,size ++

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

2)链表中间添加元素

a.对index进行判断,若index < 0 || index>size,则抛出异常index不合法。若index = 0,则直接调用addFirst()方法在表头添加元素

b.创建插入节点node

c.从head开始循环遍历寻找插入节点索引的前一个节点prev

node.next = prev.next;

prev.next = node;   顺序不能颠倒

化为一句话:prev.next=new Node(e, prev.next);

d.最后维护size,size ++

    //在链表index(0-based)位置添加新的元素
//在链表中不是一个常用操作
public void add(int index, E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Add failed. Illagal 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 ++;
} }

3)链表末尾添加元素,直接调用add()方法。

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

4)常用技巧:为链表设立虚拟头节点

首节点元素 为dummyHead.next

需要对基本成员变量和构造函数进行修改

  //基本成员变量
private Node dummyhead;
private int size; // 构造函数
public LinkedList(){
dummyhead = new Node(null,null);
size = 0;
}

对添加方法进行修改,循环遍历次数由index-1变为index,因为加了虚拟头节点,多了一位。

   //在链表index(0-based)为值添加新的元素
//在链表中不是一个常用操作
public void add(int index, E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Add failed. Illagal index.");
}
Node prev = dummyhead;
for(int i = 0 ; i < index ; i ++){
prev = prev.next;
}
Node node = new Node(e);
node.next = prev.next;
prev.next = node;
//prev.next = new Node(e,next);
size ++; }

但此时不用对index是否为0进行判断,addFirst()方法也可以优化为

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

4.链表的查询操作

当找index位置前一个位置的节点,从dummyhead开始遍历

当找index位置的节点,从dummyhead.next开始遍历

获得链表index位置的元素

a.先判断index是否合法

b.令Node cur = dummyhead.next,开始遍历index次,循环体中令cur=cur.next

c.返回cur.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; }

由此得到getFirst()方法和getLast()方法,getLast()方法中为get(size - 1)

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

5.修改链表中index位置的方法set(int index, E e)

a.先判断index是否合法

b.令Node cur = dummyhead.next,开始遍历index次,循环体中令cur=cur.next

c.令cur.e = e

 //修改链表的第index(0-based)位置的元素为e
//在链表中不是一个常用的操作
public void set(int index, E e){
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;
}
cur.e = e;
}

6.查询链表是否存在元素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;
}

7.重写toString()方法

    @Override
public String toString() { StringBuilder res = new StringBuilder();
// Node cur = dummyhead.next;
// while(cur != null){
// res.append(cur+"->");
// cur = cur.next;
// }
for(Node cur = dummyhead.next ; cur != null ; cur = cur.next){
res.append(cur + "—>");
}
res.append("NULL");
return res.toString();
}

现在对于遍历整个链表有两种形式

1)

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

2)

for(Node cur = dummyhead.next ; cur != null ; cur = cur.next){
res.append(cur + "—>");
}

8.链表中元素的删除

有虚拟头节点的链表

1)删除“索引”为index位置的元素delNode

a.先判断index是否合法

b.Node prev = dummyhead;用for循环遍历查找索引为index位置的元素delNode

c. prev.next = delNode.next;

delNode.next = null;

d. 对size进行维护,size --

    //删除index(0-based)位置的元素
public E remove(int index){
if(index < 0 || index >= size){
throw new IllegalArgumentException("Get failed. Illegal index");
}
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; }

2)删除链表第一位和最后一位的元素

    //删除第一个元素
public E removeFirst(){
return remove(0);
} //删除最后一个元素
public E removeLast(){
return remove(size-1);
}

三.时间复杂度分析

添加操作

addLast(e)  O(n)

addFirst(e)  O(1)  与数组相反

add(index, e)   O(n/2) = O(n)

删除操作

removeLast(e)  O(n)

removeFirst(e)  O(n)

remove(index, e)  O(n/2) = O(n)

修改操作

set(index, e)  O(n)

查找操作 O(n)

get(index)   O(n)

contains(e)   O(n)

增、删、改、查均为O(n)

对链表头进行增加,删除,查询时时间复杂度为O(1)。

四.总结

1.链表是真正的动态数据结构,不需要处理固定容量的问题。

2.数组尾部添加元素是非常方便的(size指向最后一个元素的下一位)

在链表头添加元素是非常方便的(head跟踪链表头)

3.为链表设立虚拟头节点,可以让很多方法实现起来更方便

4.当找index位置前一个位置的节点,从dummyhead开始遍历

当找index位置的节点,从dummyhead.next开始遍历

5.对于链表遍历所有元素的方式有多种

6.增、删、改、查的时间复杂度均为O(n)

对链表头进行增加,删除,查询时时间复杂度为O(1)。

<数据结构基础学习>(四)链表 Part 1的更多相关文章

  1. Python基础学习四

    Python基础学习四 1.内置函数 help()函数:用于查看内置函数的用途. help(abs) isinstance()函数:用于判断变量类型. isinstance(x,(int,float) ...

  2. <数据结构基础学习>(四)链表 Part 2

    一.使用链表实现栈 增,删,查只对链表头进行操作,时间复杂度都为O(1) 链表头作为栈顶 LinkedListStack<E> implements Stack<E> publ ...

  3. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  4. C语言数据结构基础学习笔记——树

    树是一种一对多的逻辑结构,树的子树之间没有关系. 度:结点拥有的子树数量. 树的度:树中所有结点的度的最大值. 结点的深度:从根开始,自顶向下计数. 结点的高度:从叶结点开始,自底向上计数. 树的性质 ...

  5. Mybatis基础学习(四)—关系映射

    一.模型分析 user和orders user---->orders 一个用户可以创建多个订单,一对多. orders--->user 一个订单只由一个用户创建,一对一.   orders ...

  6. <数据结构基础学习>(三)Part 2 队列

    一.队列 Queue 队列也是一种线性结构 相比数组,队列对应的操作是数组的子集 只能从一端(队尾)添加元素,只能从另一端(队首)取出元素. (排队) 队列是一种先进先出的数据结构(先到先得)FIFO ...

  7. <数据结构基础学习>(三)Part 1 栈

    一.栈 Stack 栈也是一种线性的数据结构 相比数组,栈相对应的操作是数组的子集. 只能从一端添加元素,也只能从一端取出元素.这一端成为栈顶. 1,2,3依次入栈得到的顺序为 3,2,1,栈顶为3, ...

  8. Node.js基础学习四之注册功能

    前言:在Node.js学习(二)和(三)中介绍了如何在Node.js 中获取登录的用户名和密码与数据库进行验证并返回数据给客户端 需求:实现注册功能 为了区分登录和注册是两个不同的请求,在端口后面加上 ...

  9. C语言数据结构基础学习笔记——B树

    2-3树:是一种多路查找树,包含2结点和3结点两种结点,其所有叶子结点都在同一层次. 2结点:包含一个关键字和两个孩子(或没有孩子),其左孩子的值小于该结点,右孩子的值大于该结点. 3结点:包含两个关 ...

随机推荐

  1. Broadcast发送广播

    一.知识介绍 1.[广播分类] ①有序广播:接收者A收到广播传递给B,B传给C,有序传递.任何一个环节都可以终止广播,也可以修改广播中携带的数据. 发送的方式:sendOrderedBroadcast ...

  2. 2019Java查漏补缺(三)

    1.为什么这个public的类的类名必须和文件名相同    是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件    2.java8 的一些新特性:     3: 数据库隔离级别 隔离级别 ...

  3. 两种常用的全排列算法(java)

    问题:给出一个字符串,输出所有可能的排列. 全排列有多种算法,此处仅介绍常用的两种:字典序法和递归法. 1.字典序法: 如何计算字符串的下一个排列了?来考虑"926520"这个字符 ...

  4. AngularJS学习之旅—AngularJS Http(九)

    1.AngularJS XMLHttpRequest $http 是 AngularJS 中的一个核心服务,用于读取远程服务器的数据. eg: // 简单的 GET 请求,可以改为 POST $htt ...

  5. 解决Win10系统本地主机,网络受限占用CPU过高的问题

    Win10版本为2015年第一个版本,第一次安装时没有这个问题,后面每次安装后开机正常,但是只要运行一段时间后(机子有运行各种软件的情况),发现CPU使用率为100% 即使结束所有在运行的程序,依然居 ...

  6. Windows 下端口被占用

    0. 参考 参考链接:  Windows下如何查看某个端口被谁占用 1. 遇到的问题 在 Windows 下的 IDEA 中启动 Web 服务显示 8080 端口被占用,程序无法正确启动. 2. 解决 ...

  7. LeetCode算法题-Poor Pigs(Java实现)

    这是悦乐书的第235次更新,第248篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第102题(顺位题号是455).有1000个水桶,其中只有一个水桶含有毒药,其余的都没毒 ...

  8. docker容器日志收集方案(方案四,目前使用的方案)

    先看数据流图,然后一一给大家解释 ​ 这个方案是将日志直接从应用代码中将日志输出到redis中(注意,是应用直接连接redis进行日志输出),redis充当一个缓存中间件有一定的缓存能力,不过有限,因 ...

  9. 聚类——GMM

    聚类——认识GMM算法 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 一.GMM概述 二.GMM算法步骤 三.具体推导参考文献 1. 李航. 统计学习 ...

  10. 我的第一个python web开发框架(32)——定制ORM(八)

    写到这里,基本的ORM功能就完成了,不知大家有没有发现,这个ORM每个方法都是在with中执行的,也就是说每个方法都是一个完整的事务,当它执行完成以后也会将事务提交,那么如果我们想要进行一个复杂的事务 ...