本专栏文章主要用于帮助Java使用者快速上手数据结构,刷算法题!
自古以来数据结构界就分为九重天,据说冲破这九重天之后就可以去进攻算法界最终修炼最后成佬,受万人敬仰。
但是这谈何容易,因为每一重天都有神兽把守,想要冲破每一重天都必须收服守护的神兽才行。
守护九重天的神兽分别是:数组、字符串、栈、队列、链表、树、散列表、堆、图。可见他们的战斗力也是逐层增强的。想只凭靠自身的能力拿下他们谈何容易。
不过大家不必惊慌,我这里有一本上古秘籍《Java小子怒闯数据结构九重天》,里面有每一重天神兽的攻略。只要修炼者仔细钻研里面的每一篇,对九重天了如指掌之后,冲破这九重天也是易如反掌的。
简单来说链表是实现线性表的链式存储结构的基础。
链表是一种通过指针串联在一起的线性结构,每一个节点是由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
节点情况如下:
数据域 | 指针域 |
---|---|
date | next |
上面的介绍都是单链表相关的,除了单链表我们还有双向链表,循环链表等。
双链表
双链表顾名思义就是比单链表高级了一点,一个双链表的结点在单链表的基础上又增加了一个指针,这个指针指向它的前一个结点
节点情况如下:
指针域 | 数据域 | 指针域 |
---|---|---|
prior | date | next |
循环链表
之前的链表都是条状的,循环链表是头尾相连了,就是将链表的尾部与头部连接起来。有因为条状链表有单链表与双链表之分,所以循环链表也有两种分别是:循环单链表与循环双链表
对于循环单链表我们一般设置尾指针这样操作效率会更高。
对于循环双链表我们头结点的prior结点指向最后一个节点,尾结点的next指针指向头结点,以形成循环双链表。
想要实例化链表就得先创建一个链表的结构类,这个类代表的是每一个节点结构。
之前提到过每一个节点是由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
所以链表节点结构定义为:
class ListNode {
int val; // 节点值
ListNode next; // 后继节点引用
ListNode(int x) {
val = x;
}
}
使用定义的这个链表结构我们可以自己构造一个链表如下:
// 实例化节点
ListNode n1 = new ListNode(4); // 节点 head
ListNode n2 = new ListNode(5);
ListNode n3 = new ListNode(1);
// 连接节点使之成为链表
n1.next = n2;
n2.next = n3;
n3.next = null;
其中实现了Iterable
接口,提供了遍历方式。
import java.util.Iterator;
/**
* 链表的head是不可以动的
* @param
*/
public class LinkList<T> implements Iterable<T>{
private Node head;//头结点,链表的head是不可以动的
private int N;//结点个数
//构造方法
public LinkList() {
this.head = new Node(null,null);
N = 0;
}
//结点内部类
private class Node{
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next) {
this.item = item;
this.next = next;
}
}
//清空链表
public void clear(){
head.item=null;
head.next=null;// 头节点next为null就是空链表
this.N=0;
}
//判断链表是否为空
public boolean isEmpty(){
return this.N==0;
}
//获取链表的长度
public int length(){
return this.N;
}
//读取链表第i位置的元素值并返回
public T get(int i){
//非法性检查
if(i<0 || i > this.N){
throw new RuntimeException("位置不合法");
}
// n也是指向头结点
Node n = head;
for(int index=0; index<i; index++){
n = n.next;
}
return n.item;
}
//往链表中插入数据t
public void insert(T t){
// head不可以移动,不然就无法在找到链表
// 定义一个临时的Node也指向head的指针就可以通过移动该指针就可以
Node n = head;
// 获取尾节点
while(true){
// 当刚好就一个节点时(头节点)
if(n.next == null){
break;
}
n = n.next;
}
//当为空表时,就可以插入
Node node = new Node(t, null);
n.next =node;
this.N ++;
}
//在第i位置上插入数据t
public void insert(T t,int i){
// 非法性检查
if(i < 0 || i > this.N){
throw new RuntimeException("插入位置非法");
}
Node pre = head;
for(int index=0;index <= i-1; index++){
pre = pre.next;
}
Node current = pre.next;
//先链接后面结点
Node newNode = new Node(t,null);
pre.next = newNode;
newNode.next = current;
this.N++;
}
//移除并返回第i位置的元素值
public T remove(int i){
// 非法性检查
if(i < 0 || i >this.N){
throw new RuntimeException("删除位置有误");
}
Node n =head;
for(int index=0;index <= i-1;index ++){
n = n.next;
}
//要删除的节点
Node curr = n.next;
n.next = curr.next;
this.N--;//结点个数减一
return curr.item;
}
//查找元素t在链表中第一次出现的位置
public int indexof(T t){
Node n = head;
for(int i = 0; n.next != null;i++){
n =n.next;
if(n.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator iterator() {
return new Iterator() {
Node n =head;
@Override
public boolean hasNext() {
return n.next !=null;
}
@Override
public Object next() {
//下移一个指针
n = n.next;
return n.item;
}
};
}
}
补充一点:链表的赋值给新的链表后,两个链表是会相会影响的,说白了就是把地址赋值给它了,他们操作是同一块内存的同一个对象。
Node n = head;
把head赋值给n,现在对n操作也是会影响head的。
我们可以申请两个指针,第一个指针叫 pre,最初是指向 null 的。
第二个指针 cur 指向 head,然后不断遍历 cur。
每次迭代到 cur,都将 cur 的 next 指向 pre,然后 pre 和 cur 前进一位。
都迭代完了(cur 变成 null 了),pre 就是最后一个节点了。
代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
//申请节点,pre和 cur,pre指向null
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while(cur!=null) {
//记录当前节点的下一个节点
tmp = cur.next;
//然后将当前节点指向pre
cur.next = pre;
//pre和cur节点都前进一位
pre = cur;
cur = tmp;
}
return pre;
}
}
恭喜你修炼到这里,你已经基本有了收服神兽链表的能力。神兽链表是我们到进攻算法界最重要的能力之一。大家不可懈怠。
感兴趣的修炼者可以关注下面公众号,会持续推送最新更新的!
持续更新中…