* 注: 本文/本系列谢绝转载,如有转载,本人有权利追究相应责任。 2019年4月8日

Stan Zhang 2019年4月8日  格物致知,经世致用。

[面试题]1.为什么要用链表?

数组具有的缺陷: 数组是长度固定类型固定的,并且它取值快,插入和删除慢

链表正弥补了这样的不足,它是长度都可以灵活扩展的,另外它是插入和删除快,但是取值非常慢

查找也比较慢,这是这种线性结构的通病,需要通过散列的思路进行解决,当然一个例外是

有下标的有序线性结构可以使用二分查找提高查找效率

因此没有最好的,只有最合适的数据结构。

2.链表的分类.

A.单链表:

B:双端链表

C:有序链表 : 没有什么突出的优点,只是为了迎合扩展性(长度扩展)以及业务

故名思意,有序链表的序列是有序的。

先比较一下有序数组,有序数组牺牲插入效率来提升查找效率,配合二分查找(利用数组索引)可以LogN级别定位元素。

但是有序链表中有序并不能提升其查找效率(它没有数组的索引)。

List本生就是按照添加的顺序有序的,再引入一个排序标准没有必要,因此Java当中没有SortedList.

而Set本身没有顺序规则,可以设置一个排序,因此当需要使用有序的集合时,我们可以使用SortedSet.

D:双向链表

3.实现

A.单链表

单链表的特点是头部插入快,其他无论是查找、尾部插入还是删除,效率都是N级别的。

Java代码的实现:

package ds1.linked.table.singleDirection;

/**
* 单向链表
* 单向链表的表中会存留一个链表的起始端 Node first,这导致从尾部插入数据非常麻烦需要花费O(N)的时间
*/
public class SingleDirectionList {
static class Node {
Node next;
String data;
Node(String data){
this.data = data;
}
} static class LinkedList{
Node first; // 保存第一个节点 public LinkedList(){
} public LinkedList(Node first) {
this.first = first;
} public void foreachPrint(){
if(first == null){
System.out.println("[]");
}else{
System.out.print("[" + this.first.data);
Node current = first.next;
while(current != null){
System.out.print("," + current.data);
current = current.next;
}
System.out.println("]");
}
} /**
* 插入节点的时间复杂度: O(1)
* @param node
*/
public void addNode(Node node){
if(first == null){
this.first = node;
}else{
node.next = this.first;
this.first = node;
}
} /**
* 删除节点的时间复杂度: O(N)
* @param node
*/
public void removeNode(Node node){
if(this.first == null){
return;
} if(this.first.equals(node)){
this.first = this.first.next;
node.next = null;
return;
}
Node current = this.first;
Node before = current;
while(current.next != null){
if(current.equals(node)){
before.next = current.next;
node.next = null;
break;
}
before = current;
current = current.next;
}
} /**
* 查找节点的时间复杂度: O(N)
* @param data
* @return
*/
public Node findNodeByData(String data){
Node result = null;
Node current = this.first;
while(current != null){
if(current.data != null && current.data.equals(data)){
result = current;
break;
}
current = current.next;
}
return current;
} } public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
Node node1 = new Node("node1");
Node node2 = new Node("node2");
Node node3 = new Node("node3");
Node node4 = new Node("node4");
Node node5 = new Node("node5");
Node node6 = new Node("node6");
Node node7 = new Node("node7");
linkedList.addNode(node1);
linkedList.addNode(node2);
linkedList.addNode(node3);
linkedList.addNode(node4);
linkedList.addNode(node5);
linkedList.addNode(node6);
linkedList.addNode(node7);
linkedList.foreachPrint();
Node tmpNode4 = linkedList.findNodeByData("node4");
linkedList.foreachPrint();
linkedList.removeNode(tmpNode4);
linkedList.foreachPrint();
}
}

结果:

[node7,node6,node5,node4,node3,node2,node1]
[node7,node6,node5,node4,node3,node2,node1]
[node7,node6,node5,node3,node2,node1]

B.双端链表

双端链表具有两个端点指针,分别指向收尾指针。它具有首位快速插入,首部快速删除的能力。但是因为还是单向的,因此尾部删除性能不佳。

Java代码:

package ds1.linked.table.twoHead;

import ds1.linked.table.singleDirection.SingleDirectionList;

/**
* 双端列表
* 有两个端点
*/
public class TwoHeadLinkedList {
static class Node{
Node next;
String data; public Node(String data) {
this.data = data;
}
} static class LinkedList{
Node first;
Node tail; public LinkedList(){
} public LinkedList(Node node){
this();
addNodeToHead(node);
} /**
* 从首部添加一个节点
* 常数时间
* @param node
*/
public void addNodeToHead(Node node){
if(this.first == null){
this.first = node;
this.tail = node;
}else{
node.next = this.first;
this.first = node;
}
} /**
* 删除头节点
* 常数时间
*/
public void removeHead(){
if(this.first != null){
if(this.first == this.tail){
this.tail = null;
}
this.first = this.first.next;
}
} /**
* 添加尾部节点
* 常数时间
* @param node
*/
public void addNodeTail(Node node){
if(this.tail != null){
this.tail.next = node;
this.tail = node;
}else{
this.first = this.tail = node;
}
} /**
* 删除尾部节点
* 线性时间
*/
public void removeTailNode(){
if(this.first == null){
return;
}
if(this.first == this.tail){
this.first = this.tail = null;
return;
} Node current = this.first;
Node before = current;
while(current.next != null){
before = current;
current = current.next;
}
before.next = null;
this.tail = before;
} /**
* 查找一个节点
* 时间复杂度: 线性时间
* @param data
* @return
*/
public Node findByData(String data){
Node result = null;
if(this.first == null){
return result;
}
Node current = this.first;
while(current != null){
if(current.data != null && current.data.equals(data)){
result = current;
break;
}
current = current.next;
} return result;
} /**
* 删除一个节点
* 时间复杂度:线性时间
* @param node
*/
public void removeNode(Node node){
if(this.first == null){
return;
} if(this.first.equals(node)){
this.first = node.next;
} Node current = this.first;
Node before = current;
while(current != null){
if(current == node){
before.next = current.next;
break;
}
before = current;
current = current.next;
} if(this.tail == node){
this.tail = before;
}
} public void foreachPrint(){
if(first == null){
System.out.println("[]");
}else{
System.out.print("[" + this.first.data);
Node current = first.next;
while(current != null){
System.out.print("," + current.data);
current = current.next;
}
System.out.println("]");
}
} } public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
Node node1 = new Node("node1");
Node node2 = new Node("node2");
Node node3 = new Node("node3");
Node node4 = new Node("node4");
Node node5 = new Node("node5");
Node node6 = new Node("node6");
Node node7 = new Node("node7");
linkedList.addNodeToHead(node1);
linkedList.foreachPrint();
linkedList.addNodeToHead(node2);
linkedList.foreachPrint();
linkedList.addNodeTail(node3);
linkedList.foreachPrint();
linkedList.addNodeToHead(node4);
linkedList.foreachPrint();
linkedList.addNodeTail(node5);
linkedList.foreachPrint();
linkedList.addNodeToHead(node6);
linkedList.foreachPrint();
linkedList.addNodeTail(node7);
linkedList.foreachPrint();
Node tmpNode4 = linkedList.findByData("node4");
linkedList.foreachPrint();
linkedList.removeNode(tmpNode4);
linkedList.foreachPrint();
} }

结果:

[node1]
[node2,node1]
[node2,node1,node3]
[node4,node2,node1,node3]
[node4,node2,node1,node3,node5]
[node6,node4,node2,node1,node3,node5]
[node6,node4,node2,node1,node3,node5,node7]
[node6,node4,node2,node1,node3,node5,node7]
[node6,node2,node1,node3,node5,node7]

C:有序链表 : 没有什么突出的优点,只是为了迎合扩展性(长度扩展/类型扩展)以及业务

package ds1.linked.table.orderedList;

public class OrderedList {
static class Node{
Node next;
long data; public Node(long data) {
this.data = data;
}
} static class LinkedList{
Node first; LinkedList(){
} public void foreachPrint(){
if(first == null){
System.out.println("[]");
}else{
System.out.print("[" + this.first.data);
Node current = first.next;
while(current != null){
System.out.print("," + current.data);
current = current.next;
}
System.out.println("]");
}
} /**
* 插入过程,线性时间
* @param node
*/
public void insert(Node node){
if(this.first == null){
this.first = node;
return;
} if(this.first.data > node.data){
node.next = this.first;
this.first = node;
return;
} Node current = this.first;
Node before = current;
while(current != null){
if(current.data > node.data){
node.next = current;
before.next = node;
break;
}
before = current;
current = current.next;
} if(current == null){
before.next = node;
} } /**
* 查找过程: 不能利用二分查找,还是线性时间
* @param data
*/
public Node findByValue(long data){
Node result = null;
Node current = this.first;
while (current != null){
if(current.data == data){
result = current;
break;
}
current = current.next;
}
return result;
}
} public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.insert(new Node(100));
linkedList.foreachPrint();
linkedList.insert(new Node(200));
linkedList.foreachPrint();
linkedList.insert(new Node(122));
linkedList.foreachPrint();
linkedList.insert(new Node(39));
linkedList.foreachPrint();
linkedList.insert(new Node(103));
linkedList.foreachPrint();
linkedList.insert(new Node(145));
linkedList.foreachPrint();
} }

结果:

[]
[,]
[,,]
[,,,]
[,,,,]
[,,,,,]

D:双向链表

双向链表很好的利用了双端和双向的优势,使得双端的插入删除操作变为常数级别。

package ds1.linked.table.twoHeadTwoDirection;

/**
* 双向链表
* 有双端点双向,也就意味着两头的插入删除效率都是线性的。
*/
public class TwoHeadTwoDirectionList {
static class Node {
Node next;
Node before;
String data;
Node(String data){
this.data = data;
}
} static class LinkedList{
Node first; // 保存第一个节点
Node tail; // 保留最后一个节点 public LinkedList(){
} public LinkedList(Node first) {
this.first = this.tail = first;
} public void foreachPrint(){
if(first == null){
System.out.println("[]");
}else{
System.out.print("[" + this.first.data);
Node current = first.next;
while(current != null){
System.out.print("," + current.data);
current = current.next;
}
System.out.println("]");
}
} /**
* 插入头节点的时间复杂度:线性
* @param node
*/
public void addNodeToHead(Node node){
if(first == null){
this.first = this.tail = node;
}else{
node.next = this.first;
this.first = node;
}
} /**
* 删除头节点的时间复杂度:线性时间
*/
public void removeNodeFromHead(){
if(this.first != null){
Node tmp = this.first.next;
this.first.next = null;
this.first = tmp;
}
} /**
* 尾部新增一个节点
* 线性时间
* @param node
*/
public void addNodeToTail(Node node){
if(this.tail == null){
this.tail = node;
}else{
node.before = this.tail;
this.tail.next = node;
this.tail = node;
}
} /**
* 从尾部删除节点
* 线性时间
*/
public void removeNodeFromTail(){
if(this.tail != null){
Node tmp = this.tail;
this.tail = this.tail.before;
this.tail.next = null;
tmp.before = null;
}
} /**
* 删除节点的时间复杂度: O(N)
* @param node
*/
public void removeNode(Node node){
if(this.first == null){
return;
} if(this.first.equals(node)){
this.first = this.first.next;
node.next = null;
return;
}
Node current = this.first;
Node before = current;
while(current.next != null){
if(current.equals(node)){
before.next = current.next;
before.next.before = before;
node.next = null;
node.before = null;
break;
}
before = current;
current = current.next;
}
} /**
* 查找节点的时间复杂度: O(N)
* @param data
* @return
*/
public Node findNodeByData(String data){
Node result = null;
Node current = this.first;
while(current != null){
if(current.data != null && current.data.equals(data)){
result = current;
break;
}
current = current.next;
}
return current;
} } public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
Node node1 = new Node("node1");
Node node2 = new Node("node2");
Node node3 = new Node("node3");
Node node4 = new Node("node4");
Node node5 = new Node("node5");
Node node6 = new Node("node6");
Node node7 = new Node("node7");
linkedList.addNodeToHead(node1);
linkedList.foreachPrint();
linkedList.addNodeToHead(node2);
linkedList.foreachPrint();
linkedList.addNodeToTail(node3);
linkedList.foreachPrint();
linkedList.addNodeToHead(node4);
linkedList.foreachPrint();
linkedList.addNodeToTail(node5);
linkedList.foreachPrint();
linkedList.addNodeToTail(node6);
linkedList.foreachPrint();
linkedList.addNodeToHead(node7);
linkedList.foreachPrint();
Node tmpNode4 = linkedList.findNodeByData("node4");
linkedList.foreachPrint();
linkedList.removeNode(tmpNode4);
linkedList.foreachPrint();
linkedList.removeNodeFromHead();
linkedList.foreachPrint();
linkedList.removeNodeFromTail();
linkedList.foreachPrint();
linkedList.removeNodeFromTail();
linkedList.foreachPrint();
linkedList.removeNodeFromHead();
linkedList.foreachPrint();
}
}

结果:

[node1]
[node2,node1]
[node2,node1,node3]
[node4,node2,node1,node3]
[node4,node2,node1,node3,node5]
[node4,node2,node1,node3,node5,node6]
[node7,node4,node2,node1,node3,node5,node6]
[node7,node4,node2,node1,node3,node5,node6]
[node7,node2,node1,node3,node5,node6]
[node2,node1,node3,node5,node6]
[node2,node1,node3,node5]
[node2,node1,node3]
[node1,node3]

[数据结构]P1.1 链表结构的更多相关文章

  1. 数据结构:单链表结构字符串(python版)改进

    此篇文章的replace实现了字符串类的多次匹配,但依然有些不足. 因为python字符串对象为不变对象,所以replace方法并不修改原先的字符串,而是返回修改后的字符串. 而此字符串对象时用单链表 ...

  2. 数据结构:单链表结构字符串(python版)添加了三个新功能

    #!/urs/bin/env python # -*- coding:utf-8 -*- #异常类 class stringTypeError(TypeError): pass #节点类 class ...

  3. 数据结构:单链表结构字符串(python版)

    #!/urs/bin/env python # -*- coding:utf-8 -*- #异常类 class stringTypeError(TypeError): pass #节点类 class ...

  4. 【C&数据结构】---关于链表结构的前序插入和后序插入

    刷LeetCode题目,需要用到链表的知识,忽然发现自己对于链表的插入已经忘得差不多了,以前总觉得理解了记住了,但是发现真的好记性不如烂笔头,每一次得学习没有总结输出,基本等于没有学习.连复盘得机会都 ...

  5. 数据结构( Pyhon 语言描述 ) — — 第4章:数据和链表结构

    数据结构是表示一个集合中包含的数据的一个对象 数组数据结构 数组是一个数据结构 支持按照位置对某一项的随机访问,且这种访问的时间是常数 在创建数组时,给定了用于存储数据的位置的一个数目,并且数组的长度 ...

  6. 《Java数据结构》链表结构(单向链表,双向链表)

    单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始:链表是使用指针进行构造的列表:又称为结点列表,因为链表是由一个个结点组装起来的:其中每个结点都有指 ...

  7. python数据结构与算法——链表

    具体的数据结构可以参考下面的这两篇博客: python 数据结构之单链表的实现: http://www.cnblogs.com/yupeng/p/3413763.html python 数据结构之双向 ...

  8. [pjsip]Pjlib中的链表结构

    Pjlib的链表结构跟常见的链表结构有所区别,如下图所示: 图1:一般链表结构 图2:pjlib中的链表结构 可以看到一般的双向链表是链表节点包含数据域,而pjlib中是数据域包含链表节点.一般的链表 ...

  9. 数据结构与算法 —— 链表linked list(01)

    链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...

随机推荐

  1. gcc update in centos to 6.3 by scl

    CentOS 7虽然已经出了很多年了,但依然会有很多人选择安装CentOS 6,CentOS 6有些依赖包和软件都比较老旧,如今天的主角gcc编译器,CentOS 6的gcc版本为4.4,CentOS ...

  2. Django + Axios & Ajax post和get 传参

    话说千遍淡如水,不如代码来一通.   Axios post: let params = new URLSearchParams(); params.append('id',xx) axios({ ur ...

  3. Spring Boot事务管理(中)

    在上一篇 Spring Boot事务管理(上)的基础上介绍Spring Boot事务属性和事务回滚规则 . 4 Spring Boot事务属性 什么是事务属性呢?事务属性可以理解成事务的一些基本配置, ...

  4. PHP调用接口用post方法传送json数据

    1.核心代码: <?php require("helper.php"); header('content-type:text/html;charset=utf-8'); $k ...

  5. 解决端口耗尽问题: tcp_tw_reuse、tcp_timestamps

    一.本地端口有哪些可用 首先,需要了解到TCP协议中确定一条TCP连接有4要素:local IP, local PORT, remote IP, remote PORT.这个四元组应该是唯一的. 在我 ...

  6. Win10系统jdk环境变量配置方法

    http://www.w10zj.com/Win10xy/Win10yh_5620.html

  7. GlusterFS配置及使用

    一.GlusterFS 配置及使用 GlusterFS配置及使用:https://www.cnblogs.com/sxchengchen/p/7805667.html 二.CentOS 7 安装部署 ...

  8. wysiwyg 富文本编辑器(附带图片上传功能)

    Fist: 需要的文件 font 文件夹下面的也是需要的哟 Then: 引入文件 <link href="bootstrap/css/bootstrap.css" rel=& ...

  9. CSS注意点

    案例: 实际开发中,这样写:

  10. NOIP2012借教室

    题目描述 Description 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要 向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海 ...