一.链表基础

动态数组、栈、队列底层都是依托静态数组实现的,靠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. docker 学习资料收集

    Docker中文网 http://www.docker.org.cn/book/ docker镜像怎么迁移到其他的服务器 http://www.talkwithtrend.com/Question/1 ...

  2. 支持scrollTo的RecycleView

    RecycleView内部没有帮我们实现ScrollTo的方法,不过帮我们实现了ScrollBy,我们可以通过ScrollBy自定义一个支持scrollTo的RecycleView. public c ...

  3. 从零学习Fluter(七):Flutter打包apk详解

    写一个win上 flutter 打包apk的教程 这篇文档介绍一下flutter打包发布正式版apk 整体来看,和命令行打包rn的方法相差不大 打包前先做检查工作&查看构建配置 Android ...

  4. MySQL常用命令汇总(偏向运维管理)

    基础部分 1. select @@version; ##查询当前mysql的版本. 2. show variables like 'port';##查看mysql实例的端口. 3. show vari ...

  5. Python第十一天 异常处理 glob模块和shlex模块 打开外部程序和subprocess模块 subprocess类 Pipe管道 operator模块 sorted函数 os模块 hashlib模块 platform模块 csv模块

    Python第十一天    异常处理  glob模块和shlex模块    打开外部程序和subprocess模块  subprocess类  Pipe管道  operator模块   sorted函 ...

  6. Asp.net mvc 项目返回Json

    因mvc控制器返回类型JsonResult 在处理对象转JSON的时候,对日期的格式化处理并不太符合要求,所以重新继承抽象类ActionResult使用Newtonsoft.Json来系列化 usin ...

  7. AngularJS学习之旅—AngularJS HTML DOM(十三)

    1.AngularJS HTML DOM AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令. ng-disabled 指令:ng-disabled 指令直接绑定应用程序数据 ...

  8. 【学习】Linux Shell脚本编程

    1.脚本的组成和执行 Linux shell脚本的结构并不复杂,其主要由变量.内部命令以及shell的语法结构和一些函数.其他命令行的程序等组成,以下是一个简单的shell脚本. #!/bin/bas ...

  9. php去掉字符串的最后一个字符

    php去掉字符串的最后一个字符 //例如 $str = "12,34,56,"; $newstr = substr($str,0,strlen($str)-1); //从第一位开始 ...

  10. 目录命令(dir)

    DIR 命令: // 描述: (Directory) 显示目录的文件和子目录的列表. // 语法: dir [<Drive>:][<Path>][<FileName> ...