数据结构之线性表-链式存储之单链表(一)
2014-12-07 14:55
465 查看
本人文笔较差,语文从来不及格,基础不好,写此类文章仅供自己学习,理解队列及其他知识,高手大神请略过。参考书籍 《数据结构与算法分析-Java语言描述》
链表是由一系列的节点组成,这些节点不必在内存中相连。这就意味着元素可以存在内存未被占用的任意位置。如图
这个存储可以想象成元素在内存中成三维空间存储,他们之间可没有像数组那样的下标标记。他们之间的联系是靠图中的箭头。与顺序存储结构不同的是,链表的
每一个节点不仅仅保存着元素本身,还包含另一块区域,而这块区域保存着它的直接后即元素节点的链儿,也叫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.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简单实现
import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.NoSuchElementException; /** * Created with IntelliJ IDEA. * CreateUser: blentle * Email: renhuan@milipp.com * CreateTime: 2014/11/6 23:20 * ModifyUser: blentle * ModifyTime: 2014/11/6 23:20 * Class Description: * To change this template use File | Settings | File Templates. */ public class SingleLinkedList<T> { //链表的存储大小 private int size; //链表的头结点 private Node<T> firstNode; //链表的最后一个元素,里面的nextChain指向引用null private Node<T> lastNode; public SingleLinkedList() { } /** * 不指定位置,插入到最后一个元素的后面 * @param t * @return */ public boolean add(T t) { //存储插入前最后一个元素,便于后面修改next链儿 Node<T> node = this.lastNode; Node<T> newNode = new Node(t,null); //修改最后一个元素的值 this.lastNode = newNode; if(node == null) { //原来是空表,头指针也是最后一个节点 this.firstNode = this.lastNode; } else { //将插入之前最后一个节点的next链儿指向新的节点元素 node.nextChain = newNode; } //增加链表的长度 size++; return true; } /** * 指定位置,插入到指定位置 * @param index * @param t * @return */ public boolean add(int index,T t) { if(index == this.size) { //若指定位置刚好在最后一个元素(从0开始)后面 add(t); } else { Node<T> node = get(index); //插入的新元素节点next链儿指向原来位置的元素节点 Node newNode = new Node(t, node); //新元素节点前驱元素的next链儿指向新元素 if(index > 0) { //插入的不是第一个节点 Node<T> prevousNode = get(index - 1) ; prevousNode.nextChain = newNode; } else { //插入到第一个节点 firstNode = newNode; } } size++; return true; } /** * 这个方法才能体现出单链表的优势 * todo:待优化 * @param index * @param collection * @return */ public boolean addAll(int index,Collection<? extends T> collection) { //检查序号越界 if(index < 0 || index > size) { throw new IndexOutOfBoundsException("single linked list size is :" + size + ",but index is:" + index); } int insertSize = collection.size(); if(index == size) { //序号和表的长度相同,即插入到最后一个元素后面,保存插入前的最后一个元素节点,用于后面移动next链儿 Node<T> previosNode = this.lastNode; for(T oneTarget: collection) { Node<T> newNode = new Node<T>(oneTarget,null); if(previosNode == null) { //插入的是空表 firstNode = newNode; } else { previosNode.nextChain = newNode; } //插入下一个元素时的,previousNode就变成了当前插入的元素 previosNode = newNode; } } else { //插入到其他位置 Node<T> indexNodeBeforeInsert = get(index); Node<T> previosNode = null; if(index > 0) { previosNode = get(index-1); } for(T oneTarget: collection) { Node<T> newNode = new Node<T>(oneTarget,null); if(previosNode == null) { //插入的是空表 firstNode = newNode; } else { previosNode.nextChain = newNode; } //插入下一个元素时的,previousNode就变成了当前插入的元素 previosNode = newNode; previosNode.nextChain = indexNodeBeforeInsert; } } size += insertSize; return true; } /** * 删除指定位置的元素 * @param index * @return */ public boolean remove(int index) { if(size == 0) { return false; } //检查标号越界 if(index < 0 || index >= size ) { throw new IndexOutOfBoundsException("single linked list size is :" + size + ",but index is:" + index); } Node<T> target = get(index); Node<T> nextNode = target.nextChain; Node<T> previousNode = null; if(index > 0) { previousNode = get(index - 1); previousNode.nextChain = nextNode; } else { firstNode = nextNode; } if(nextNode == null) { //删除的是最后一个元素,改变表尾元素节点 lastNode = previousNode; } else { //删除后自身的next链儿引用null target.nextChain = null; } target.item = null; size--; return true; } /** * 清理链表 */ public void clear() { if(size > 0) { //第一个节点开始遍历,依次引用null Node<T> node = firstNode; while(node != null) { Node<T> next = node.nextChain; node.nextChain = null; node.item = null; node = next; } firstNode = lastNode = null; size = 0; } } /** * 获取链表的元素长度 * @return */ public int size() { return this.size; } /** * 根据标号获取元素节点,从第头指针(第一个next链儿指向第一个节点元素)开始遍历, * 每遍历一个元素正在活跃的next链儿向后移动一位 * @param index * @return */ private Node<T> get(int index) { //这里next链是可以移动到最后一个元素size-1的 if(index < size && index >= 0) { //从第一个节点开始遍历 Node<T> current = this.firstNode; for(int i = 0 ; i < index ; i++) { current = current.nextChain; } return current; } throw new NoSuchElementException(); } /** * 链表中的每一个节点元素【参考LinkedList源码】 * @param <T> */ private static class Node<T> { //节点元素 private T item; //next链儿(下一节点的引用变量) private Node<T> nextChain; Node(T item, Node<T> nextChain) { this.item = item; this.nextChain = nextChain; } } }
相关文章推荐
- 数据结构之线性表――链式存储结构之单链表(php代码实现)
- 数据结构:线性表的链式存储结构_单链表
- 数据结构:线性表的链式存储
- 数据结构之线性表的链式存储
- 数据结构之线性表——栈的链式存储
- 数据结构之线性表——链表的链式存储(链式描述)注释版
- c/c++常用算法(2) -- 数据结构(线性表的链式存储)
- JAVA数据结构之线性表的链式存储结构——单链表
- 线性表的链式存储结构——单链表(头插法)
- [SDUT](2117)数据结构实验之链表二:逆序建立链表 ---链式存储(线性表)
- 数据结构之线性表代码实现顺序存储,链式存储,静态链表(选自大话数据结构)
- 数据结构——线性表的链式存储
- 线性表的链式存储结构:单链表
- 数据结构——线性表的伪链表存储(顺序存储链式遍历)
- 数据结构之线性表——队列的链式存储
- 数据结构之线性表-链式存储之循环链表(三)
- 数据结构之线性表——链表的链式存储(链式描述)
- 数据结构(八)线性表链式存储结构
- [SDUT](2116)数据结构实验之链表一:顺序建立链表 ---链式存储(线性表)
- JAVA数据结构之线性表的链式存储结构——循环链表