(一)链表的定义
用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的
(二)单向链表
1、定义
在数据存储中,使用单一指针(java中称对象)指向下一个存储单元的位置
2、存储结构
3、代码实现
Node.java
> /** * 节点 */
public class Node {
private Node next;
private Object value;
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
simpleList.java
/**
* 单向链表
* @param <T>
*/
public class simpleList<T> {
private Node headNode;
private int size;
//初始化simpleList
public simpleList(){
size = 0;
}
/**
*节点的添加:
* 1、在链头添加
* 2、在链表中间插入
*/
//链头
public void add(T data){
Node target = headNode; //等到头节点
Node newNode = new Node(); //新建节点
newNode.setValue(data); //设置新节点的值
newNode.setNext(target); //设置新节点的下一个节点指针的指向
headNode = newNode; //将新建的节点设置为头节点
size++;
}
//链表中间(重载)
public void add(int index,T data){
checkPositionIndex(index);
if (index <= 0){ //如果是在头节点之前
add(data); //链头插入
}else{
Node target = node(index-1); //得到插入位置节点的上一个节点target
Node newNode = new Node(); //新建一个节点
newNode.setValue(data); //设置新节点的值
newNode.setNext(target.getNext()); //设置新节点的下一个节点的指针的值
target.setNext(newNode); //设置target的下一个节点指针的指向
}
size++; //节点数+1
}
/***
*删除节点:
* 1、链头删除
* 2、链表中间删除
*/
//链头
public boolean remove(){
if (headNode !=null){ //如果链表不为空
Node target = headNode.getNext(); //等到头节点的下一个节点
headNode.setNext(null); //设置头节点的下一个节点指针为null
headNode.setValue(null); //设置头节点的值为null 目的:gc回收
headNode = target; //设置头节点的指向
size--;
return true;
}
return false;
}
//链表中间(重载)
public boolean remove(int index){
checkPositionIndex(index);
if (headNode != null){ //链表不为空
if (index<= 0){ //在头节点删除
remove();
}else{
Node head = headNode;
Node target = headNode;
for (int i = 0; i <index ; i++) {
head = target; //得到删除节点的上一个节点
target = target.getNext(); //得到删除节点
}
head.setNext(target.getNext()); //设置删除节点的下一个节点的指向
target.setNext(null); //设置删除节点的下一个节点的指针指向为null
target.setValue(null); //设置删除节点的值为null 目的:gc回收
}
size--; //节点数-1
return true;
}
return false;
}
/**
*查找节点的值
*/
public T get(int index){
checkPositionIndex(index);
return (T) node(index).getValue();
}
/**
*修改节点的值
*/
public boolean set(int index,T data){
checkPositionIndex(index);
if (headNode !=null){ //如果链表不为空
Node target = node(index); //得到目的节点
target.setValue(data); //更改目的节点的值
return true;
}
return false;
}
//判断下标index是否符合条件
private void checkPositionIndex(int index) {
if(index < 0 || index >= size) throw new IndexOutOfBoundsException("下标溢出");
}
//得到目的节点
private Node node(int index){
Node target = headNode; //等到头节点
for (int i = 0; i < index; i++) {
target = target.getNext(); //找到目的节点
}
return target; //返回节点
}
//返回节点的个数
public int size(){
return size;
}
public static void main(String[] args) {
simpleList list = new simpleList();
for (int i = 0; i < 5; i++) {
list.add("data"+i);
}
list.remove(2);
list.set(2,1520);
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
}
}
单向链表的实现过程和解析在代码中都由注析了,在这我就不重复了,那么在代码中,add(int index)方法中的 newNode.setNext(target.getNext());
和 target.setNext(newNode);
的位置可以交换吗?为什么?
newNode.setNext(target.getNext());和 target.setNext(newNode);的位置不可以交换,因为如果交换了位置,那么它的存储结构就变成了如下图
newNode.setNext(target.getNext());相当于上图中的s.next=p.next
target.setNext(newNode);相当于上图中p.next = s
(三)双向链表
1、定义
在数据存储中,使用双指针(2个对象)指向与之关联的数据存储单元,一个指向与之关联的上一个数据存储单元地址,另外一个指向与之关联的下一个数据存储单元地址
2、存储结构
3、代码实现
Node.java
public class Node {
private Node prev;
private Node next;
private Object data;
public Node getPrev() {
return prev;
}
public void setPrev(Node prev) {
this.prev = prev;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
bothWayList.java
/**
* 双向链表
* @param <T>
*/
public class bothWayList<T> {
private Node firstNode,lastNode;
private int size;
public bothWayList(){
size = 0;
}
/**
* 在双向链表中添加元素:
* 1、在链头添加
* 2、在链尾添加
* 3、在中间插入
*/
//链头插入
private void linkFirst(T data){
Node f = firstNode; //获取当前的头节点
Node newNode = new Node(); //新建一个节点
newNode.setPrev(null); //设置节点的上一个节点
newNode.setData(data); //设置节点的数值
newNode.setNext(f); //设置节点的下一个节点
firstNode = newNode; //头节点等于新建的节点
if(f == null) //如果开始的头节点为null
lastNode = newNode; //最后节点等于新建的节点(即头节点和尾节点都是这个新建的节点)
else
f.setPrev(newNode); //上一个头节点的上一个节点等于新建的节点
size++; //节点数
}
//链尾插入
private void linkLast(T data){
Node l = lastNode; //获取当前的尾节点
Node newNode = new Node(); //新建节点
newNode.setPrev(l); //设置新节点的上一个节点
newNode.setData(data); //设置新节点的数值
newNode.setNext(null); //设置新节点的下一个节点
lastNode = newNode; //尾结点等于这个新的节点
if (l == null) //如果开始的尾结点为null
firstNode = newNode; //头节点等于这个新建的节点(即头节点和尾节点都是这个新建的节点)
else
l.setNext(newNode); //设置开始的尾节点的下一个节点为新建的节点
size++;
}
//中间插入
private void linkBefore(int index,T data){
Node targetNode = node(index); //获取要插入时位置存在的节点target
Node prev = targetNode.getPrev(); //在这个位置节点的前一个节点prev
Node newNode = new Node(); //新建一个节点
newNode.setPrev(prev); //设置新节点的上一个节点
newNode.setData(data); //设置新节点的数值
newNode.setNext(targetNode); //设置新节点的下一个节点
targetNode.setPrev(newNode); //设置targetNode的前一个节点
if (prev == null){
firstNode = newNode; //头节点等于新的节点
}else{
prev.setNext(newNode); //prev的下一个节点是新的节点
}
size++; //节点的数量
}
//暴露添加节点的方法
public void add(T data){
linkLast(data);
}
public void add(int index,T data){
checkPositionIndex(index);
if (index == size)
linkLast(data);
else
linkBefore(index,data);
}
/**删除节点:
* 1、在链头删除
* 2、在链尾删除
* 3、在链表中间删除
*
*/
//链头
private boolean firstUnLinked(){
if (firstNode!=null){
Node target = firstNode.getNext(); //得到头节点的下一个节点traget
firstNode.setNext(null); //设置头节点的下一个节点的指针为null
firstNode.setData(null); //设置头节点的数值为null 设置这两个为null的目的是为了回收(gc)
firstNode = target; //将target设置为头节点
if (target == null) //链表为空
lastNode = null; //最后的节点为空
else
target.setPrev(null); //设置target的上一个节点为空,即头节点为空(没有这句的话firstNode.getPrev的值等于上一个节点的地址)
size--;
return true;
}
return false;
}
//链尾
private boolean lastUnLinked(){
if (lastNode !=null){
Node target = lastNode.getPrev(); //等到链尾的上一个节点target
lastNode.setPrev(null); //设置链尾节点的上一个节点的指针为null
lastNode.setData(null); //设置链尾节点的数值为null 目的:GC回收
lastNode = target; //将链尾节点设置为target
if (target == null)
firstNode = null; //这个链表为空
else
target.setNext(null);
size--;
return true;
}
return false;
}
//链表中间
private boolean unLinked(int index){
if (firstNode!=null && lastNode != null){ //表示链表不为空
Node target = node(index); //得到删除节点target
target.getPrev().setNext(target.getNext()); //设置删除节点的上一个节点的下一个节点指针指向的是删除节点的下一个节点
target.getNext().setPrev(target.getPrev()); //设置删除节点的下一个节点的上一个节点指针指向的是删除节点的上一个节点
target.setPrev(null); //删除节点的上一个节点的指针为null
target.setData(null); //删除节点的数值为null
target.setNext(null); //删除节点的下一个节点的指针为null 目的:gc回收
size--; //个数减一
return true;
}
return false;
}
//将删除的方法暴露给其它类使用
public boolean remove(int index){
checkPositionIndex(index);
if (index == 0)
return firstUnLinked();
else if(index == size-1)
return lastUnLinked();
else
return unLinked(index);
}
//修改目的节点的值
public boolean set(int index,T data){
checkPositionIndex(index);
if (firstNode !=null && lastNode !=null){
Node target = node(index);
target.setData(data);
return true;
}
return false;
}
//查询节点
public T get(int index){
checkPositionIndex(index);
Node targetNode =node(index);
return (T) targetNode.getData();
}
//判断下标index是否符合条件
private void checkPositionIndex(int index) {
if(index < 0 || index >= size) throw new IndexOutOfBoundsException("传入的下标不符合条件");
}
//根据下标获取节点
private Node node(int index){
Node targetNode;
// >> 表示右移 >>1表示除以2的1次方
if (index < (size >> 1)){
targetNode = firstNode;
for (int i = 0; i < index; i++) {
targetNode = targetNode.getNext();
}
return targetNode;
}else{
targetNode = lastNode;
for (int i = size-1; i > index; i--) {
targetNode = targetNode.getPrev();
}
return targetNode;
}
}
public static void main(String[] args) {
bothWayList list = new bothWayList();
for (int i = 0; i <5 ; i++) {
list.add("data"+i);
}
//list.add(2,1860);
//list.remove(0);
//list.set(4,520);
for (int i = 0; i < list.size; i++) {
System.out.println(list.get(i));
}
}
}
在上面的代码中的linkBefore(int index,T data)有2行代码:targetNode.setPrev(newNode);
和prev.setNext(newNode);
,那么它们的的位置可以交换吗?为什么?(循环)
linkBefore(int index,T data)的实现原理如下图
正常情况下添加节点的顺序为:
1、s.next = p.next;
2、p.next.prev = s;
3、s.prev = p;
4、p.next = s;
targetNode.setPrev(newNode);相当于上图的p.next.prev = s;
prev.setNext(newNode);相当于上图的p.next = s;
如果这两句交换了位置,那么它的存储结构就变成了下图形式
(执行p.next.prev = s时由图中的2变成了5):
在图中,a1、a2、a3形成了一个循环(4、1和a3到a1的箭头)
(四)顺序表、单向链表、双向链表的优缺点
优点 | 缺点 | |
---|---|---|
顺序表 | 存储空间连续、允许随机访问、尾插、尾删方便 | 插入效率低、删除效率低、长度固定 |
单向链表 | 随意进行增删改、插入效率高、删除效率高、长度随意修改 | 内存不连续、不能随机查找 |
双向链表 | 随意进行增删改、插入效率高、删除效率高、长度可以随意修改、查找效率比单链表快一倍 | 内存不连续、不能随机查找,但是效率比单链表快一倍 |
顺序表的介绍点击计入
ArrayList和LinkedList的区别是什么?
查看ArrayList源码(ArrayList的具体分析)我们知道它是利用顺序表进行存储的,而LinkedList使用双向链表进入存储的,而顺序表和和双向链表的优缺点如上图所示,我们使用时要看具体情况分析来选择,如果插、删使用比较少的话我们应选择ArrayList,否则选择LinkedList
注意:手机查看上面的代码可能显示不全,可以转到https://editor.csdn.net/md/?articleId=107609702
如果对你们有帮助的话点个赞哦(*^_^*)