单向链表只有一个指针域,在整个节点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的节点。
单向链表中,每个节点的数据域都是通过一个 Object 类的对象引用来指向数据元素的,与数组类似,单向链表中的节点也具有一个线性次序,即如果节点 a1 的 next 引用指向节点 a2,则 a1 就是 a2 的直接前驱,a2 是 a1 的直接后续。只能通过前驱节点找到后续节点,而无法从后续节点找到前驱节点。
特点:
数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素。每个结点是由数据域和指针域组成。 元素之间的逻辑关系通过存储节点之间的链接关系反映出来。
逻辑上相邻的节点物理上不必相邻。
缺点:
1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。
2、查找结点时链式存储要比顺序存储慢(每个节点地址不连续、无规律,导致按照索引查询效率低下)。
优点:
1、插入、删除灵活 (不必移动节点,只要改变节点中的指针,但是需要先定位到元素上)。
2、有元素才会分配结点空间,不会有闲置的结点。
package day14.linkedlist_;
public class MyLink {
/**
* 创建节点
*/
class Node{
/**
* 真正的数据
*/
private Object data;
/**
* 下一个节点
*/
private Node next;
//构造器
public Node(){
this.data = 0;
this.next = null;
}
public Node(Object data){
this.data = data;
this.next = null;
}
//为了显示方法,我们重新创建toString
@Override
public String toString() {
return "Node{ data=" + data+" }";
}
}
/**
* 头结点
*/
private Node head;
/**
* 链表中节点的个数
*/
private int size;
/**
* 获取链表中元素的个数
* @return
*/
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node curNode = head;
while(curNode != null){
sb.append(curNode + "-->");
curNode = curNode.next;
}
return sb.toString();
}
public MyLink(){
this.head = null;
this.size = 0;
}
/**
* 判断链表是否为空
* @return
*/
public boolean isEmpty(){
return head.next == null;
}
/**
* 无虚拟头结点
* index下标上增加元素
* @param o
* @param index
*/
/*
public void add(Object o,int index){
if(index < 0 || index > this.size){
throw new ArrayIndexOutOfBoundsException("index 下标越界");
}
Node node = new Node(o);
//链表里没有值
if(head == null){
head = node;
}
//index == 0,从头结点前插入
if(index == 0){
node.next = head;
head = node;
this.size++;
return;
}
Node prev = head;
for(int i = 0;i < index - 1;i++){
prev = prev.next;
}
node.next = prev.next;
prev.next = node;
this.size++;
}
*/
/**
* 从头部增加元素
* @param o
*/
/*
public void addFirst(Object o){
add(o,0);
}
*/
/**
* 从尾部增加元素
* @param o
*/
/*
public void addLast(Object o){
add(o,size);
}
public Object get(int index){
return get(index);
}
*/
/**
* 使用虚拟头结点
* @param o
* @param index
*/
public void add(Object o,int index){
if(index < 0 || index > this.size){
throw new ArrayIndexOutOfBoundsException("index超出范围");
}
Node dummyHead = new Node(null);
Node node = new Node(o);
dummyHead.next = head;
Node prev = dummyHead;
for(int i = 0;i < index;i++){
prev = prev.next;
}
node.next = prev.next;
prev.next = node;
this.size++;
head = dummyHead.next;
}
/**
* 返回index处的内容
* @param index
* @return
*/
public Node get(int index){
if(index < 0 || index > this.size){
throw new ArrayIndexOutOfBoundsException("index 超出范围");
}
Node curNode = head;
for(int i = 0;i < index;i++){
curNode = curNode.next;
}
return curNode;
}
/**
* 获得头节点
*/
public void getFirst(){
get(0);
}
/**
* 获得尾结点
*/
public void getLast(){
get(size - 1);
}
/**
* 链表中是否存在o
* @param o
* @return
*/
public boolean isContains(Object o){
Node node = head;
for(int i = 0;i < size;i++){
if(this.get(i).data.equals(o)){
return true;
}
}
return false;
}
/**
* 移除头节点
*/
public void removeFirst(){
remove(0);
}
/**
* 移除尾节点
*/
public void removeLast(){
remove(size-1);
}
public void remove(int index) {
if(index < 0 || index > this.size){
throw new IllegalArgumentException("参数index不合法");
}
Node dummyNode = new Node(null);
dummyNode.next = head;
Node prev = dummyNode;
for(int i = 0;i < index;i++){
prev = prev.next;
}
/* 要删除的节点 */
Node delNode = prev.next;
/* 删除操作 */
prev.next = delNode.next;
delNode.next = null;
/* 更新size */
this.size--;
/* 更新head */
dummyNode.next = head;
}
}
要在单向链表中找到某个节点的前驱节点,必须从链表的头节点出发依次向后寻找,但是需要Ο(n)时间。为此我们可以扩展单向链表的节点结构,使得通过一个节点的引用,不但能够访问其后续节点,也可以方便的访问其前驱节点。扩展单向链表节点结构的方法是,在单链表节点结构中新增加一个域,该域用于指向节点的直接前驱节点。该链表称为双向链表。单向链表只能从一个方向遍历,双向链表可以从两个方向遍历。
在使用双向链表实现链接表时,为使编程更加简洁,我们使用带两个哑元节点的双向链表来实现链接表。其中一个是头节点,另一个是尾节点,它们都不存放数据元素,头节点的pre 为空,而尾节点的 Next 为空。
在具有头尾节点的双向链表中插入和删除节点,无论插入和删除的节点位置在何处,因为首尾节点的存在,插入、删除操作都可以被归结为某个中间节点的插入和删除;并且因为首尾节点的存在,整个链表永远不会为空,因此在插入和删除节点之后,也不用考虑链表由空变为非空或由非空变为空的情况下 head 和 tail 的指向问题;从而简化了程序。
在使用双向链表实现链接表时,为使编程更加简洁,我们使用带两个哑元节点的双向链表来实现链接表。其中一个是头节点,另一个是尾节点,它们都不存放数据元素,头节点的pre 为空,而尾节点的 Next 为空。
在具有头尾节点的双向链表中插入和删除节点,无论插入和删除的节点位置在何处,因为首尾节点的存在,插入、删除操作都可以被归结为某个中间节点的插入和删除;并且因为首尾节点的存在,整个链表永远不会为空,因此在插入和删除节点之后,也不用考虑链表由空变为非空或由非空变为空的情况下 head 和 tail 的指向问题;从而简化了程序。
package day16.doublelinkedlist;
public class DoubleLinkedList {
/*组装节点的数据结构*/
class Node{
/*真正的数据*/
Object data;
/*前驱结点*/
Node prev;
/*后继结点*/
Node next;
public Node(Object data){
this.data = data;
this.prev = null;
this.next = null;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Node : { data = " + data);
if(prev != null){
stringBuilder.append(" , prev = " + prev.data);
}
if(next != null){
stringBuilder.append(" , prev = " + next.data);
}
stringBuilder.append(" }");
return stringBuilder.toString();
}
}
/**
* 头结点
*/
private Node first;
/**
* 尾结点
*/
private Node last;
/**
* 链表中结点的个数
*/
private int size;
/**
* 头结点
* @return
*/
public Node getFirst() {
if(isEmpty()){
return null;
}
return first;
}
public void setFirst(Node first) {
this.first = first;
}
/**
* 尾结点
* @return
*/
public Node getLast() {
if(isEmpty()){
return null;
}
return last;
}
public void setLast(Node last) {
this.last = last;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
/**
* 无参构造
*/
public DoubleLinkedList(){
this.size = 0;
this.first = this.last = null;
}
public boolean isEmpty(){
return this.size == 0;
//return this.first = null;
}
/**
* 头部添加
* @param val
*/
public void addFirst(int val){
Node node = new Node(val);
if(isEmpty()){
last = node;
}else{
node.next = first;
first.prev = node;
}
first = node;
this.size++;
}
/**
* 尾部添加
* @param val
*/
public void addLast(int val){
Node node = new Node(val);
if(isEmpty()){
first = node;
}else{
node.prev = last;
last.next = node;
}
last = node;
this.size++;
}
/**
* 将val插入到内容为key的结点之后
* @param key
* @param val
*/
public void add(int key,int val){
/* 1.找到内容为key的结点 */
Node node = find(key);
Node newLink = new Node(val);
/* 2.跟后继结点的连接 */
if(node == last){
newLink.next = null;
last = newLink;
}else{
newLink.next = node.next;
node.next.prev = newLink;
}
node.next = newLink;
newLink.prev = node;
this.size++;
}
/**
* 返回val的索引
* @param val
* @return
*/
public int indexOf(int val){
int index = 0;
for(Node node = first;node != null;node = node.next){
if(node.data.equals(val)){
return index;
}
index++;
}
return -1;
}
/**
* 判断某一个内容是否存在
* @param val
* @return
*/
public boolean isContains(int val){
return indexOf(val) != -1;
}
/**
* 找到内容为val的结点
* @param val
* @return
*/
public Node find(Object val){
for(Node node = first; node != null;node = node.next){
if(node.data.equals(val)){
return node;
}
}
return null;
}
/**
* 从前往后输出
* @return
*/
public String stringForward(){
StringBuilder sb = new StringBuilder();
for(Node node = first;node != null;node = node.next){
sb.append(node);
}
return sb.toString();
}
public Node removeFirst(){
Node temp = first;
if(isEmpty()){
return null;
}
if(first.next == null){
last = null;
}else {
first.next.prev = null;
}
first = first.next;
this.size--;
return temp;
}
public Node removeLast(){
Node temp = last;
if(isEmpty()){
return null;
}
if(last.prev == null){
first = null;
}else{
last.prev.next = null;
}
last = last.prev;
this.size--;
return temp;
}
public Node remove(Object val){
//1.找到要删除的结点
Node node = find(val);
//2.如果这个节点是first
if(node == first){
first = first.next;
}else{
//2.1 否则 node的前一个的next指向 node的下一个结点
node.prev.next = node.next;
}
//3.如果这个节点是last
if(node == last){
last = last.prev;
}else{
//3.1 否则 node的下一个的prev指向 node的上一个结点
node.next.prev = node.prev;
}
return node;
}
/**
* 根据索引查找结点
* @param index
* @return
*/
public Node getByIndex(int index){
/* 二分查找 */
Node node = null;
if(index < size >> 1){
node = first;
for(int i = 0;i < index;i++){
node = node.next;
}
}else{
node = last;
for(int i = size - 1;i > index;i --){
node = node.prev;
}
}
return node;
}
/**
* 从后往前输出
* @return
*/
public String stringBack(){
StringBuilder sb = new StringBuilder();
for(Node node = last;node != null;node = node.prev){
sb.append(node);
}
return sb.toString();
}
}