数据结构之线性表-链式存储之单链表(一)
本人文笔较差,语文从来不及格,基础不好,写此类文章仅供自己学习,理解队列及其他知识,高手大神请略过。参考书籍 《数据结构与算法分析-Java语言描述》
1.1 单链表简介
线性表的最大的缺点就是插入和删除操作需要移动大量的元素,这是它在内存中的连续存储结构造成的。为了弥补这2个缺点,就出现了链表,即线性表的链式存储。
链表是由一系列的节点组成,这些节点不必在内存中相连。这就意味着元素可以存在内存未被占用的任意位置。如图
这个存储可以想象成元素在内存中成三维空间存储,他们之间可没有像数组那样的下标标记。他们之间的联系是靠图中的箭头。与顺序存储结构不同的是,链表的
每一个节点不仅仅保存着元素本身,还包含另一块区域,而这块区域保存着它的直接后即元素节点的链儿,也叫next链儿(next引用,其实就是节点的引用变量),
顶头的节点存储的不是元素,是一个只有指向第一个元素节点的链儿(头指针),最后一个节点的next链儿是引用null。(C语言中貌似叫指针,原谅我这个没学过C语言的土鳖)。
所以单链表的存储是从头指针开始的,之后的每一个节点都是上一个next链儿指向的位置。这种方式的存储使得要获取某一个节点变得困难。如要获取单链表
a1,a2,...ai...,an的元素ai,必须得先获取ai-1,因为只有ai-1存储了指向ai的next链儿。同样获取ai-1必须先获取ai-2
若从头指针开始,获取第一个元素耗时将是O(1)(与线性表类似,假设获取某一个元素时间是t[t是固定不变的常数]),最费时的情况将是获取最后一个元素。
O(N),则整个链表的获取元素的平均耗时就是O(N)/2,时间复杂度即O(N)。
看起来确实不如顺序存储。但在这个二元对立的世界,任何行为都是具有两面性的。对于单链表的插入,若将节点 P 插入上图的节点a3的位置,只需要将a2的
next链儿指向节点P(若P不是空节点),P的next链儿指向a3就完事儿了。其它元素节点不需要任何移动。如图
对于删除节点a3也是同一个道理,只需要将节点a2的next链儿指向a4,然后移除a3就可以了。单纯就插入和删除的操作来说,时间复杂度是O(1)。可是这真的
就节省时间了么?仔细看,插入和删除都是由两部分组成的:第一部分是遍历获取元素,第二部分才是插入或删除的操作。整体来说时间复杂度还是O(N),这
看起来与顺序存储确实没有太大的优势。但如果插入或删除元素较多,如从ai元素位置插入10个,100个,甚至上10000(当然表的最大存储空间要足够大),或者
更多个元素,对于顺序存储每一次插入(删除)都要移动n-i个节点元素,也就是每一次时间复杂度都是O(N),但对于单链表而言只是简单的赋值和next链的移动,
时间复杂度都是O(1)。
总结:插入或删除节点元素操作频繁的,单链表的效率要高于顺序存储的线性表。
1.2 单链表的Java简单实现
1 import java.util.Arrays; 2 import java.util.Collection; 3 import java.util.List; 4 import java.util.NoSuchElementException; 5 6 /** 7 * Created with IntelliJ IDEA. 8 * CreateUser: blentle 9 * Email: renhuan@milipp.com 10 * CreateTime: 2014/11/6 23:20 11 * ModifyUser: blentle 12 * ModifyTime: 2014/11/6 23:20 13 * Class Description: 14 * To change this template use File | Settings | File Templates. 15 */ 16 public class SingleLinkedList<T> { 17 //链表的存储大小 18 private int size; 19 //链表的头结点 20 private Node<T> firstNode; 21 //链表的最后一个元素,里面的nextChain指向引用null 22 private Node<T> lastNode; 23 24 public SingleLinkedList() { 25 26 } 27 28 /** 29 * 不指定位置,插入到最后一个元素的后面 30 * @param t 31 * @return 32 */ 33 public boolean add(T t) { 34 //存储插入前最后一个元素,便于后面修改next链儿 35 Node<T> node = this.lastNode; 36 Node<T> newNode = new Node(t,null); 37 //修改最后一个元素的值 38 this.lastNode = newNode; 39 if(node == null) { 40 //原来是空表,头指针也是最后一个节点 41 this.firstNode = this.lastNode; 42 } else { 43 //将插入之前最后一个节点的next链儿指向新的节点元素 44 node.nextChain = newNode; 45 } 46 //增加链表的长度 47 size++; 48 return true; 49 } 50 51 /** 52 * 指定位置,插入到指定位置 53 * @param index 54 * @param t 55 * @return 56 */ 57 public boolean add(int index,T t) { 58 if(index == this.size) { 59 //若指定位置刚好在最后一个元素(从0开始)后面 60 add(t); 61 } else { 62 Node<T> node = get(index); 63 //插入的新元素节点next链儿指向原来位置的元素节点 64 Node newNode = new Node(t, node); 65 //新元素节点前驱元素的next链儿指向新元素 66 if(index > 0) { 67 //插入的不是第一个节点 68 Node<T> prevousNode = get(index - 1) ; 69 prevousNode.nextChain = newNode; 70 } else { 71 //插入到第一个节点 72 firstNode = newNode; 73 } 74 75 } 76 size++; 77 return true; 78 } 79 80 /** 81 * 这个方法才能体现出单链表的优势 82 * todo:待优化 83 * @param index 84 * @param collection 85 * @return 86 */ 87 public boolean addAll(int index,Collection<? extends T> collection) { 88 //检查序号越界 89 if(index < 0 || index > size) { 90 throw new IndexOutOfBoundsException("single linked list size is :" + size + ",but index is:" + index); 91 } 92 int insertSize = collection.size(); 93 if(index == size) { 94 //序号和表的长度相同,即插入到最后一个元素后面,保存插入前的最后一个元素节点,用于后面移动next链儿 95 Node<T> previosNode = this.lastNode; 96 for(T oneTarget: collection) { 97 Node<T> newNode = new Node<T>(oneTarget,null); 98 if(previosNode == null) { 99 //插入的是空表 100 firstNode = newNode; 101 } else { 102 previosNode.nextChain = newNode; 103 } 104 //插入下一个元素时的,previousNode就变成了当前插入的元素 105 previosNode = newNode; 106 } 107 } else { 108 //插入到其他位置 109 Node<T> indexNodeBeforeInsert = get(index); 110 Node<T> previosNode = null; 111 if(index > 0) { 112 previosNode = get(index-1); 113 } 114 for(T oneTarget: collection) { 115 Node<T> newNode = new Node<T>(oneTarget,null); 116 if(previosNode == null) { 117 //插入的是空表 118 firstNode = newNode; 119 } else { 120 previosNode.nextChain = newNode; 121 } 122 //插入下一个元素时的,previousNode就变成了当前插入的元素 123 previosNode = newNode; 124 previosNode.nextChain = indexNodeBeforeInsert; 125 } 126 127 } 128 size += insertSize; 129 return true; 130 } 131 132 /** 133 * 删除指定位置的元素 134 * @param index 135 * @return 136 */ 137 public boolean remove(int index) { 138 if(size == 0) { 139 return false; 140 } 141 //检查标号越界 142 if(index < 0 || index >= size ) { 143 throw new IndexOutOfBoundsException("single linked list size is :" + size + ",but index is:" + index); 144 } 145 Node<T> target = get(index); 146 Node<T> nextNode = target.nextChain; 147 Node<T> previousNode = null; 148 if(index > 0) { 149 previousNode = get(index - 1); 150 previousNode.nextChain = nextNode; 151 } else { 152 firstNode = nextNode; 153 } 154 if(nextNode == null) { 155 //删除的是最后一个元素,改变表尾元素节点 156 lastNode = previousNode; 157 } else { 158 //删除后自身的next链儿引用null 159 target.nextChain = null; 160 } 161 target.item = null; 162 size--; 163 return true; 164 } 165 166 /** 167 * 清理链表 168 */ 169 public void clear() { 170 if(size > 0) { 171 //第一个节点开始遍历,依次引用null 172 Node<T> node = firstNode; 173 while(node != null) { 174 Node<T> next = node.nextChain; 175 node.nextChain = null; 176 node.item = null; 177 node = next; 178 } 179 firstNode = lastNode = null; 180 size = 0; 181 } 182 } 183 184 /** 185 * 获取链表的元素长度 186 * @return 187 */ 188 public int size() { 189 return this.size; 190 } 191 192 /** 193 * 根据标号获取元素节点,从第头指针(第一个next链儿指向第一个节点元素)开始遍历, 194 * 每遍历一个元素正在活跃的next链儿向后移动一位 195 * @param index 196 * @return 197 */ 198 private Node<T> get(int index) { 199 //这里next链是可以移动到最后一个元素size-1的 200 if(index < size && index >= 0) { 201 //从第一个节点开始遍历 202 Node<T> current = this.firstNode; 203 for(int i = 0 ; i < index ; i++) { 204 current = current.nextChain; 205 } 206 return current; 207 } 208 throw new NoSuchElementException(); 209 } 210 211 /** 212 * 链表中的每一个节点元素【参考LinkedList源码】 213 * @param <T> 214 */ 215 private static class Node<T> { 216 //节点元素 217 private T item; 218 //next链儿(下一节点的引用变量) 219 private Node<T> nextChain; 220 221 Node(T item, Node<T> nextChain) { 222 this.item = item; 223 this.nextChain = nextChain; 224 } 225 } 226 227 }
转载于:https://www.cnblogs.com/blentle/p/4149422.html
数据结构之线性表-链式存储之单链表(一)相关推荐
- 数据结构与算法(2-2)线性表之链式存储(单链表、静态链表、循环链表、双向循环链表)
目录 一.单链表 1.存储方式 2.插入 3.删除 总代码: 二.静态链表 1.存储方式 2.插入 3.删除 4.遍历 总代码: 三.循环链表 总代码: 四.双向循环链表 1.存储方式: 2.插入和删 ...
- php数据结构链表代码,数据结构之线性表——链式存储结构之单链表(php代码实现)...
/** * * 1. 类LNode用作创建单链表时,生成新的节点. * 2. 类SingleLinkList用于创建单链表以及对单链表的一些操作方法(实例化此类就相当于创建了一个空链表) * 3. C ...
- 数据结构之线性表——链式存储结构之单链表(php代码实现)
<?php /**** 1. 类LNode用作创建单链表时,生成新的节点.* 2. 类SingleLinkList用于创建单链表以及对单链表的一些操作方法(实例化此类就相当于创建了一个空链表)* ...
- 数据结构第三篇——线性表的链式存储之单链表
♥注:未经博主同意,不得转载. 线性表的链式存储结构的特点是用一组任意的存储单元来存储线性表的数据元素,这些单元可以分散在内存中的任意位置上,其在物理上可以是连续的,也可以是不连续的.具有链式存储结构 ...
- 线性表-链式存储结构
3.6 线性表的链式存储结构 3.6.1 顺序存储结构不足的解决办法 前面我们讲的线性表的顺序存储结构.它是有缺点的,最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间.能不能想办法解决 ...
- 线性表----链式表
定义 线性表的链式存储又称单链表,它是指通过任意的存储单元来存储线性表的数据.注意此时的数据在物理地址上不在连续,内存是动态分配的,而且数据是存放在结点中,结点组成链表,每个节点分为数据域和指针域,所 ...
- 数据结构(二):线性表包括顺序存储结构(顺序表、顺序队列和顺序栈)和链式存储结构(链表、链队列和链栈)...
还记得数据结构这个经典的分类图吧: 今天主要关注一下线性表. 什么是线性表 线性表的划分是从数据的逻辑结构上进行的.线性指的是在数据的逻辑结构上是线性的.即在数据元素的非空有限集中 (1) 存在唯一的 ...
- 数据结构之顺序存储与链式存储
数据结构之顺序存储与链式存储 定义 特点 前驱和后继 存储结构 顺序存储结构 定义 特点 优缺点 基本操作 链式存储结构 1.单链表 节点 基本操作 2.静态链表 3.双向链表 4.循环链表 顺序存储 ...
- Python 数据结构 之 串 的链式存储结构
本文所采用的数据结构模板为 <数据结构教程>C语言版,李春葆.尹为民等著. 改篇所涉及到的是 串 的链式存储结构. 用Python仿照C语言来实现. 文章转载请注明: Python 数据 ...
最新文章
- Metasploit设置VERBOSE参数技巧
- Xamarin iOS编写第一个应用程序创建工程
- 【NLP】simhash判断文档相似度
- php学习之------[流程控制]
- joomla \libraries\joomla\session\session.php 反序列化截断畸形字符串导致对象注入漏洞...
- Oracle 11g用户创建、授权和导入dmp文件
- python cmd窗口 title_解决python在windows上运行弹出cmd窗口(dos窗口)
- android教务系统框架,基于android的面向学生的移动教务管理系统设计与实现
- netware 6.5的故障解决
- 实例教学!12种透明背景的万能设计方法
- 2021美赛C题解题思路(Confirming the Buzz about Hornets)
- 到底什么是云计算?学云计算能从事哪些职业
- SNAP7 PLC协议S7 PDU程式读取长度
- 运行cool edit时显示系统配置不正确
- 助力新冠抗原检测产品规模化、智能化生产,慧灵科技推出整体解决方案
- b树和b+树的区别。
- c语言练习:输入一个字符,如果是数字字符就输出,如果不是则输出“不是数字字符”。
- 有效管理时间的10个技巧
- 适合初学者练手的vue小商城项目(附github源码)
- 将英文版Windows改为中文版