数据的存储一般分线性存储结构和链式存储结构两种。前者是一种顺序的存储方式,在内存中用一块连续的内存空间存储数据,即逻辑上相连的物理位置相邻,比较常见的就是数组;后者是一种链式存储方式,不保证顺序性,逻辑上相邻的元素之间用指针所指定,它不是用一块连续的内存存储,逻辑上相连的物理位置不一定相邻。本篇主要介绍链式存储结构基于链表的实现,使用的语言为Java。

链表是一种递归的数据结构,它要么为空(null),要么指向是指向一个结点(node)的引用,该节点含有一个泛型元素(该泛型元素可以是任意数据类型),和一个指向另一个链表的引用。链表有很多种,下面主要介绍单向链表、双端链表、有序链表、双向链表。

单向链表:单向链表是最简单、最基础的链表,它的一个结点(node)分两部分,第一部分存储结点的数据信息(data),第二部分存储指向下一结点的地址(next)信息。最后一个结点(链尾)指向一个空地址(null)。单向链表一般只在链表表头(链头)结点的位置插入元素,这样每次新加入的元素都会在链头位置,而最先加入的元素会在链尾位置。删除操作时,如果在链头位置删除,只需要把头结点指向其下一个结点即可;如果是在中间位置删除,只需要将其前一个结点指向其下一个结点即可。单向链表示意图如下图所示:

单向链表的Java代码实现:

import java.util.Stack;
public class LinkedListOnePoint {
private Node head; //头结点
private int size; //链表长度,即链表中结点数量 public LinkedListOnePoint(){
head = null;
size = 0;
} //私有内部类,代表链表每个结点
private class Node{
private Object data; //链表结点的值
private Node next; //指向的下一个结点
public Node(Object data){
this.data = data;
}
} //判断链表是否为空
public boolean isEmpty(){
return size==0?true:false;
} //返回链表长度
public int size(){
return size;
} //查看链表头结点,不移除
public Object headNode(){
if(size == 0) return null;
return head.data;
} //在链表表头插入一个结点(入栈)
public void insertInHead(Object obj){
Node newNode = new Node(obj);
if(size == 0){
head = newNode;
}else{
newNode.next = head;
head = newNode;
}
size++;
} //删除链表表头结点(出栈)
public Object deleteHeadNode(){
if(size == 0) return null;
Object obj = head.data;
if(head.next == null){
head = null; //只有一个结点
}else{
head = head.next;
}
size--;
return obj;
} //链表查找:判断链表中是否包含某个元素
public boolean containObject(Object obj){
if(size == 0) return false;
Node n = head;
while(n != null){
if(n.data == obj) return true;
else n = n.next;
}
return false;
} //删除链表中的指定结点(如果包含多个指定结点,只会删除第一个)
public boolean deleteNode(Object obj){
if(size == 0){
System.out.println("链表为空!");
return false;
}
//先在链表中查询是否包含该结点,找到之后获取该结点和其前一个结点
Node previous = null; //前一个结点
Node current = head; //当前结点
while(current.data != obj){
if(current.next == null){
System.out.println("没有找到该结点!");
return false;
}
previous = current;
current = current.next;
}
if(current == head){
this.deleteHeadNode();
}else{
previous.next = current.next;
size--;
}
return true;
} //正向打印链表
public void display(){
if(size == 0) System.out.println("链表为空!");
Node n = head;
while(n != null){
System.out.print("<-"+n.data);
n = n.next;
}
System.out.println();
} //反向打印链表(用栈)
public void printListFromTailToHead(Node node){
if(node == null) System.out.println("链表为空!");
Stack<Integer> sta = new Stack<Integer>();
while(node != null){
sta.push((Integer) node.data); //先将链表压入栈中
node = node.next;
}
while(!sta.empty()){
System.out.print(sta.pop()+"<-"); //出栈
}
System.out.println();
} //反向打印链表(递归)
public void printListFromTailToHeadDiGui(Node node){
if(node == null){
System.out.println("链表为空!");
}else{
if(node.next != null){
printListFromTailToHeadDiGui(node.next);
}
System.out.print(node.data+"<-");
}
} public static void main(String[] args) {
LinkedListOnePoint list = new LinkedListOnePoint();
System.out.println(list.isEmpty()); //true
System.out.println(list.size()); //
list.display(); //链表为空!
list.printListFromTailToHead(list.head); //链表为空! list.insertInHead(0);
list.insertInHead(1);
list.insertInHead(2);
list.insertInHead(3);
list.display(); //<-3<-2<-1<-0
list.printListFromTailToHead(list.head); //0<-1<-2<-3<-
list.printListFromTailToHeadDiGui(list.head); //0<-1<-2<-3<-
System.out.println(list.isEmpty()); //false
System.out.println(list.size()); //
System.out.println(list.containObject(1)); //true
}
}

我们知道,栈是一种“后进先出”的数据结构,对栈的插入和删除操作都是在栈头位置进行的,这与在单向链表的表头插入和删除元素的原理类似,因此可以用单向链表实现栈。

单向链表实现栈的Java代码:

/*
* 单链表实现栈
*/
public class LinkedListToStack {
private LinkedListOnePoint linkedlist; public LinkedListToStack(){
linkedlist = new LinkedListOnePoint();
} //栈大小
public int size(){
return linkedlist.size();
} //是否为空栈
public boolean isEmpty(){
return linkedlist.isEmpty();
} //入栈
public void push(Object obj){
linkedlist.insertInHead(obj);
} //出栈
public Object pop(){
if(this.isEmpty()) return null;
return linkedlist.deleteHeadNode();
} //查看栈顶元素
public Object peek(){
if(this.isEmpty()) return null;
return linkedlist.headNode();
} //打印栈中元素
public void display(){
linkedlist.display();
} public static void main(String[] args) {
LinkedListToStack stack = new LinkedListToStack();
stack.push(0);
stack.push(1);
stack.push(2);
stack.push(3);
stack.display(); //<-3<-2<-1<-0
System.out.println(stack.peek()); //
System.out.println(stack.pop()); //
System.out.println(stack.pop()); //
System.out.println(stack.pop()); //
System.out.println(stack.pop()); //
System.out.println(stack.pop()); //null
}
}

双端链表:双端链表和单向链表大体上是一样的,不同的是,单向链表在表尾部分插入元素时,需要从头结点一直遍历到尾结点才能进行插入操作,这样难免有些繁琐。因此如果加入一个对尾结点的引用,这样就可以很方便地在尾结点进行插入操作,这就是双端链表。除了有一个头结点(head),还有一个尾结点(tail)。注意它和后面双向链表的区别!

双端链表的Java代码实现:

import java.util.Stack;

/*
* 双端链表,比单链表多了个尾结点
*/
public class LinkedListTwoPoint {
private Node head; //头结点
private Node tail; //尾结点
private int size; //链表长度,即链表中结点数量 public LinkedListTwoPoint(){
head = null; //头结点
tail = null; //尾结点
size = 0; //链表长度,即链表中结点数量
} //私有内部类,代表链表每个结点
private class Node{
private Object data; //链表结点的值
private Node next; //指向的下一个结点
public Node(Object data){
this.data = data;
}
} //判断链表是否为空
public boolean isEmpty(){
return size==0?true:false;
} //返回链表长度
public int size(){
return size;
} //查看链表头结点,不移除
public Object headNode(){
if(size == 0) return null;
return head.data;
} //查看链表尾结点,不移除
public Object tailNode(){
if(size == 0) return null;
return tail.data;
} //在链表表头插入一个结点
public void insertInHead(Object obj){
Node newNode = new Node(obj);
if(size == 0){
head = newNode;
tail = newNode;
}else{
newNode.next = head;
head = newNode;
}
size++;
}
//在链表表尾插入一个结点
public void insertInTail(Object obj){
Node newNode = new Node(obj);
if(size == 0){
head = newNode;
tail = newNode;
}else{
tail.next = newNode;
tail = newNode;
}
size++;
} //删除链表表头结点
public Object deleteHeadNode(){
if(size == 0) return null;
Object obj = head.data;
if(head.next == null){ //只有一个结点
head = null;
tail = null;
}else{
head = head.next;
}
size--;
return obj;
} //链表查找:判断链表中是否包含某个元素
public boolean containObject(Object obj){
if(size == 0) return false;
Node n = head;
while(n != null){
if(n.data == obj) return true;
else n = n.next;
}
return false;
} //删除链表中的指定结点(如果包含多个指定结点,只会删除第一个)
public boolean deleteNode(Object obj){
if(size == 0){
System.out.println("链表为空!");
return false;
}
//先在链表中查询是否包含该结点,找到之后获取该结点和其前一个结点
Node previous = null; //前一个结点
Node current = head; //当前结点
while(current.data != obj){
if(current.next == null){
System.out.println("没有找到该结点!");
return false;
}
previous = current;
current = current.next;
}
if(current == head){
this.deleteHeadNode();
}else{
previous.next = current.next;
size--;
}
return true;
} //正向打印链表
public void display(){
if(size == 0) System.out.println("链表为空!");
Node n = head;
while(n != null){
System.out.print(n.data + "<-");
n = n.next;
}
System.out.println();
} //反向打印链表(用栈)
public void printListFromTailToHead(Node node){
if(node == null) System.out.println("链表为空!");
Stack<Integer> sta = new Stack<Integer>();
while(node != null){
sta.push((Integer) node.data); //先将链表压入栈中
node = node.next;
}
while(!sta.empty()){
System.out.print(sta.pop()+"<-"); //出栈
}
System.out.println();
} //反向打印链表(递归)
public void printListFromTailToHeadDiGui(Node node){
if(node == null){
System.out.println("链表为空!");
}else{
if(node.next != null){
printListFromTailToHeadDiGui(node.next);
}
System.out.print(node.data+"<-");
}
} public static void main(String[] args) {
LinkedListTwoPoint list = new LinkedListTwoPoint();
System.out.println(list.isEmpty()); //true
System.out.println(list.size()); //
list.display(); //链表为空!
list.printListFromTailToHead(list.head); //链表为空! list.insertInHead(0);
list.insertInHead(1);
list.insertInHead(2);
list.insertInHead(3);
list.display(); //3<-2<-1<-0<-
list.printListFromTailToHead(list.head); //0<-1<-2<-3<-
list.printListFromTailToHeadDiGui(list.head); //0<-1<-2<-3<-
System.out.println(list.isEmpty()); //false
System.out.println(list.size()); //
System.out.println(list.containObject(1)); //true
}
}

我们知道,队列是一种“先进先出”的数据结构,队列的插入操作是在队尾进行的,而删除操作是在队头进行的,这与在双端链表的表尾插入和在表头删除操作是类似的,因此可以用双端链表实现队列。

双端链表实现队列的Java代码:

/*
* 双端链表实现队列
*/
public class LinkedListToQueue {
private LinkedListTwoPoint linkedlist; public LinkedListToQueue(){
linkedlist = new LinkedListTwoPoint();
} //队列大小
public int size(){
return linkedlist.size();
} //是否为空队列
public boolean isEmpty(){
return linkedlist.isEmpty();
} //入列,在链表表尾插入节点
public void add(Object obj){
linkedlist.insertInTail(obj);
} //出列,在链表表头删除结点
public Object poll(){
if(this.isEmpty()) return null;
return linkedlist.deleteHeadNode();
} //查看队列头元素
public Object peekHead(){
if(this.isEmpty()) return null;
return linkedlist.headNode();
} //查看队列尾元素
public Object peekTail(){
if(this.isEmpty()) return null;
return linkedlist.tailNode();
} //打印队列元素
public void display(){
linkedlist.display();
} public static void main(String[] args) {
LinkedListToQueue stack = new LinkedListToQueue();
stack.add(0);
stack.add(1);
stack.add(2);
stack.add(3);
stack.display(); //0<-1<-2<-3<-
System.out.println(stack.peekHead()); //
System.out.println(stack.peekTail()); //
System.out.println(stack.poll()); //
System.out.println(stack.poll()); //
System.out.println(stack.poll()); //
System.out.println(stack.poll()); //
System.out.println(stack.poll()); //null
}
}

有序链表:链表本身是一种无序的数据结构,元素的插入和删除不能保证顺序性,但是有没有有序的链表呢?答案是肯定的,我们在单向链表中插入元素时,只需要将插入的元素与头结点及其后面的结点比较,从而找到合适的位置插入即可。一般在大多数需要使用有序数组的场合也可以使用有序链表,有序链表在插入时因为不需要移动元素,因此插入速度比数组快很多,另外链表可以扩展到全部有效的使用内存,而数组只能局限于一个固定的大小中。

有序链表的Java代码实现:

/*
* 有序链表
*/
public class LinkedListInOrder {
private Node head; //头结点
private int size; //链表长度,即链表中结点数量 public LinkedListInOrder(){
head = null;
size = 0;
} //私有内部类,代表链表每个结点
private class Node{
private Integer data; //链表结点的值
private Node next; //指向的下一个结点
public Node(Integer data){
this.data = data;
}
} //判断链表是否为空
public boolean isEmpty(){
return size==0?true:false;
} //返回链表长度
public int size(){
return size;
} //在链表中插入一个结点,保持链表有序性(头结点最小,尾结点最大)
public void insertNode(Integer obj){
Node newNode = new Node(obj);
if(size == 0){ //空链表直接放入头结点
head = newNode;
}else{
Node previous = null; //插入位置前一个结点
Node current = head; //插入位置后一个结点(当前结点)
while(current.data<obj && current.next != null){ //找到插入位置
previous = current;
current = current.next;
}
if(current.next == null){ //如果插入的结点大于链表中所有结点,则放到链尾
current.next = newNode;
}else{
previous.next = newNode; //在合适位置插入
newNode.next = current;
}
}
size++;
} //删除链表表头结点
public Object deleteHeadNode(){
if(size == 0) return null;
Object obj = head.data;
if(head.next == null){
head = null; //只有一个结点
}else{
head = head.next;
}
size--;
return obj;
} //正向打印链表
public void display(){
if(size == 0) System.out.println("链表为空!");
Node n = head;
while(n != null){
System.out.print("<-"+n.data);
n = n.next;
}
System.out.println();
} public static void main(String[] args) {
LinkedListInOrder list = new LinkedListInOrder();
System.out.println(list.isEmpty()); //true
System.out.println(list.size()); //
list.display(); //链表为空! list.insertNode(0);
list.insertNode(1);
list.insertNode(2);
list.insertNode(3);
list.insertNode(2);
list.insertNode(4);
list.display(); //<-0<-1<-2<-2<-3<-4
System.out.println(list.isEmpty()); //false
System.out.println(list.size()); //
System.out.println("*****************************"); }
}

双向链表:前面的几种链表只能从头结点遍历到尾结点这一个方向,每个结点都只能指向其下一个结点。而双向链表的每个结点既能指向下一个结点,又能指向前一个结点,双向链表既能从头结点向尾结点遍历,又能从尾结点向头结点遍历,既有一个头结点,又有一个尾结点。

双向链表的Java代码实现:

/*
* 双向链表
* 单向链表只可向一个方向遍历,一般查找一个结点的时候需要从第一个结点开始每次访问下一个结点,一直访问到需要的位置。
* 双向链表的每个结点都有指向的前一个结点和后一个节点,既有链表表头又有表尾,即可从链头向链尾遍历,又可从链尾向链头遍历。
* LinkedList中的私有静态内部类Node实际上就是一个双向链表,<E>代表泛型,指明结点的数据类型
* private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
*/
public class LinkedListTwoDirections {
private Node head; //头结点
private Node tail; //尾结点
private int size; //链表长度,即链表中结点数量 public LinkedListTwoDirections(){
head = null;
tail = null;
size = 0;
} //私有内部类,代表链表每个结点
private class Node{
private Object data; //链表结点的值
private Node previous; //当前结点指向的前一个结点
private Node next; //当前结点指向的下一个结点
public Node(Object data){
this.data = data;
}
} //判断链表是否为空
public boolean isEmpty(){
return size==0?true:false;
} //返回链表长度
public int size(){
return size;
} //查看链表头结点,不移除
public Object headNode(){
if(size == 0) return null;
return head.data;
} //查看链表尾结点,不移除
public Object tailNode(){
if(size == 0) return null;
return tail.data;
} //在链表表头插入一个结点
public void insertInHead(Object obj){
Node newNode = new Node(obj);
if(size == 0){
head = newNode;
tail = newNode;
}else{
newNode.next = head;
head.previous = newNode;
head = newNode;
}
size++;
} //在链表表尾插入一个结点
public void insertInTail(Object obj){
Node newNode = new Node(obj);
if(size == 0){
head = newNode;
tail = newNode;
}else{
newNode.previous = tail;
tail.next = newNode;
tail = newNode;
}
size++;
} //删除链表表头结点
public Object deleteHeadNode(){
if(size == 0) return null;
Object obj = head.data;
if(head.next == null){ //只有一个结点
head = null;
tail = null;
}else{
head = head.next;
head.previous = null;
}
size--;
return obj;
} //删除链表表尾结点
public Object deleteTailNode(){
if(size == 0) return null;
Object obj = tail.data;
if(tail.previous == null){ //只有一个结点
head = null;
tail = null;
}else{
tail = tail.previous;
tail.next = null;
}
size--;
return obj;
} //正向打印链表
public void display(){
if(size == 0) System.out.println("链表为空!");
Node n = head;
while(n != null){
System.out.print("<-"+n.data);
n = n.next;
}
System.out.println();
} public static void main(String[] args) {
LinkedListTwoDirections list = new LinkedListTwoDirections();
System.out.println(list.isEmpty()); //true
System.out.println(list.size()); //
list.display(); //链表为空! list.insertInHead(0);
list.insertInHead(1);
list.insertInHead(2);
list.insertInHead(3);
list.display(); //<-3<-2<-1<-0
System.out.println(list.deleteHeadNode()); // list.insertInTail(1);
list.insertInTail(2);
list.display(); //<-2<-1<-0<-1<-2 }
}

转载请注明出处 http://www.cnblogs.com/Y-oung/p/8886142.html

工作、学习、交流或有任何疑问,请联系邮箱:yy1340128046@163.com  微信:yy1340128046

数据结构之链表及其Java实现的更多相关文章

  1. java之数据结构之链表及包装类、包

    链表是java中的一种常见的基础数据结构,是一种线性表,但是不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针.与线性对应的一种算法是递归算法:递归算法是一种直接或间接的调用自身算法的过 ...

  2. JAVA数据结构之链表

    JAVA数据结构之链表 什么是链表呢? 链表作为最基本的数据结构之一,定义如下: 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的. 简单来说呢,链 ...

  3. java实现 数据结构:链表、 栈、 队列、优先级队列、哈希表

    java实现 数据结构:链表. 栈. 队列.优先级队列.哈希表   数据结构javavector工作importlist 最近在准备找工作的事情,就复习了一下java.翻了一下书和网上的教材,发现虽然 ...

  4. 数据结构——单链表java简易实现

    巩固数据结构 单链表java实现 单链表除了表尾 每个几点都有一个后继 结点有数据和后继指针组成  通过构建表头和表尾(尾部追加需要)两个特殊几点 实现单链表的一些操作,代码如下 package co ...

  5. 算法是什么(二)手写个链表(java)

    算法是什么(二)手写个链表(java)   liuyuhang原创,未经允许禁止转载 目录 算法是什么(〇) 很多语言的API中都提供了链表实现,或者扩展库中实现了链表. 但是更多的情况下,Map(或 ...

  6. 深入理解Redis 数据结构—双链表

    在 Redis 数据类型中的列表list,对数据的添加和删除常用的命令有 lpush,rpush,lpop,rpop,其中 l 表示在左侧,r 表示在右侧,可以在左右两侧做添加和删除操作,说明这是一个 ...

  7. 数据结构与算法【Java】02---链表

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  8. 数据结构与算法【Java】08---树结构的实际应用

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  9. 学习javascript数据结构(二)——链表

    前言 人生总是直向前行走,从不留下什么. 原文地址:学习javascript数据结构(二)--链表 博主博客地址:Damonare的个人博客 正文 链表简介 上一篇博客-学习javascript数据结 ...

随机推荐

  1. 一款可以安利的MarkDown编辑器

    Typeora一款可以安利的MarkDown编辑器 Typeora是什么? ​ 一款使用MarkDown的本地编辑器集结了MarkDown的所有特点并展现了简洁.高效的特点,关键是免费. 特点: 支持 ...

  2. 删除datatable的行后,出现“不能通过已删除的行访问该行的信息”的错误,即DeletedRowInaccessibleException

    删除datatable的行后,出现“不能通过已删除的行访问该行的信息”的错误 =========================================================== 采 ...

  3. Oracle 查看占用undo大的sql语句

    select s.sid,s.serial#,s.sql_id,v.usn,segment_name,r.status, v.rssize/1024/1024 mb     from dba_roll ...

  4. C/C++ 合法整数与字符

    一.C语言中的合法整型 首先C语言中的整型有三种表示方式:十进制.八进制和十六进制.(C语言中没有表示二进制的整型) 十进制: 如 int a = 63; //一个正常的整型 八进制: 如果想用8进制 ...

  5. Graph 133. Clone Graph in three ways(bfs, dfs, bfs(recursive))

    Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. OJ's ...

  6. NET Core Web API下事件驱动型架构CQRS架构中聚合与聚合根的实现

    NET Core Web API下事件驱动型架构在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件 ...

  7. [译] 怎样(以及为什么要)保持你的 Git 提交记录的整洁

    最近在掘金翻译了一篇文章,主要讲的是 Git 提交记录的维护,确实很有用,感兴趣的同学可以去看一下.链接如下: [译] 怎样(以及为什么要)保持你的 Git 提交记录的整洁 截图:

  8. CALayer & bitmap Content

    Working with High-Resolution Images Layers do not have any inherent knowledge of the resolution of t ...

  9. 3springboot:springboot配置文件(配置文件占位符、Profile、配置文件的加载位置)

    1.配置文件占位符 RaandomValuePropertySourcr:配置文件可以使用随机数     ${random.value}    ${random.int}  ${random.long ...

  10. springmvc小结(下)

    1.@ModelAttribute 1.给共享的数据设置model数据设置,贴在形参上,也可以贴在方法上,设置一个model的key值 2.当controller方法返回一个对象的时候,,缺省值会把当 ...