链表
底层存储结构
数组需要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个 100MB 大小的数组,当内存中没有连续
的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。
单链表
我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要
记录链上的下一个结点的地址。如图所示,我们把这个记录下个结点地址的指针叫作后继指针 next。
特点
我们习惯性地把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是
链表上最后一个结点。
插入、删除
循环链表
双向链表,顾名思义,它支持两个方向,每个结点不止有一个后继指针 next 指向后面的结点,还有一
个前驱指针 prev 指向前面的结点。
双向链表
写好链表得技巧
-
理解指针或引用的含义
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针 中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。 -
警惕指针丢失和内存泄漏
-
利用哨兵简化实现难度
如果我们引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个 哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫 作不带头链表。
哨兵结点是不存储数据的。因为哨兵结点一直存在, 所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为 相同的代码实现逻辑了。
- 重点留意边界条件处理
- 举例画图,辅助思考
单链表得demo
package com.wanda.list;
/**
* 单链表的实现
* @author tomcat
*
* @param <E>
*/
public class LinkedList<E>{
private class Node{
public E e;//数据域
public Node next;//指向下一个节点的指针
public Node(E e,Node next) {
this.e=e;
this.next=next;
}
public Node(E e) {
this(e,null);
}
public Node() {
this(null,null);
}
@Override
public String toString() {
return e.toString();
}
}
//虚拟头节点,不存储任何数据
private Node dummyHead;
int size;
public LinkedList() {
dummyHead=new Node(null,null);
size=0;
}
public int getSize(){
return size;
}
public boolean isEmpty() {
return size==0;
}
public void addFirst(E value) {
/*Node node=new Node(value);
node.next=head;
head=node;*/
add(0,value);
size++;
}
//添加元素
public void add(int index,E value) {
if(index<0||index>size) {
throw new IllegalArgumentException("add failed");
}
Node prev=dummyHead;
for(int i=0;i<index;i++) {
prev=prev.next;
}
Node node=new Node(value);
node.next=prev.next;
prev.next=node;
//prev.next=new Node(value,prev.next);
size++;
}
public void addLast(E value) {
add(size,value);
}
public E get(int index) {
if(index<0||index>size)
throw new IllegalArgumentException("get failed");
Node cur=dummyHead.next;
for(int i=0;i<index;i++) {
cur=cur.next;
}
return cur.e;
}
public E getFirst() {
return get(0);
}
public E getLast() {
return get(size-1);
}
public void set(int index,E e) {
if(index<0||index>=size) {
throw new IllegalArgumentException("set failed");
}
Node cur=dummyHead.next;
for(int i=0;i<index;i++) {
cur=cur.next;
}
cur.e=e;
}
public boolean contains(E e) {
Node cur=dummyHead.next;
while(cur!=null) {
if(cur.e.equals(e)) {
return true;
}
cur=cur.next;
}
return false;
}
public E remove(int index) {
if(index<0||index>=size) {
throw new IllegalArgumentException("remove failed");
}
Node prev=dummyHead.next;
for(int i=0;i<index-1;i++) {
prev=prev.next;
}
Node retNode=prev.next;
prev.next=retNode.next;
retNode.next=null;
size--;
return retNode.e;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size-1);
}
public void removeElement(E e) {
Node prev=dummyHead;
while(prev.next!=null) {
if(prev.next.e.equals(e))
break;
prev=prev.next;
}
if(prev.next!=null) {
Node delNode=prev.next;
prev.next=delNode.next;
delNode.next=null;
}
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
Node cur=dummyHead.next;
while(cur!=null) {
res.append(cur+"->");
cur=cur.next;
}
res.append("Null");
return res.toString();
}
}
基于链表得队列
package com.wanda.list;
import com.wanda.interfaces.Queue;
public class LinkedListQueue<E> implements Queue<E> {
private class Node{
public E e;
public Node next;
public Node(E e,Node next) {
this.e=e;
this.next=next;
}
public Node(E e) {
this(e,null);
}
public Node() {
this(null,null);
}
@Override
public String toString() {
return e.toString();
}
}
private Node head,tail;
private int size;
public LinkedListQueue() {
// TODO Auto-generated constructor stub
head=null;
tail=null;
size=0;
}
@Override
public void enqueue(E value) {
// TODO Auto-generated method stub
if(tail==null) {
tail=new Node(value);
head=tail;
}else {
tail.next=new Node(value);
tail=tail.next;
}
size++;
}
@Override
public E dequeue() {
// TODO Auto-generated method stub
if(isEmpty()) {
throw new IllegalArgumentException("can not dequeue from an empty queue");
}
Node retNode=head;
head=head.next;
retNode.next=null;
if(head==null) {
tail=null;
}
size--;
return retNode.e;
}
@Override
public E getFront() {
// TODO Auto-generated method stub
if(isEmpty()) {
throw new IllegalArgumentException("can not dequeue from an empty queue");
}
return head.e;
}
@Override
public int getSize() {
// TODO Auto-generated method stub
return size;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return size==0;
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
res.append("Queue: front");
Node cur=head;
while(cur!=null) {
res.append(cur+"->");
cur=cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> queue=new LinkedListQueue<>();
for(int i=0;i<10;i++) {
queue.enqueue(i);
System.out.println(queue);
}
}
}
链表的应用测试是否是回文数
package com.wanda.list;
import java.util.Scanner;
class Node{ //定义节点类
char data;
Node next;
public Node(char t){
this.data=t;
}
}
public class IsHuiWen {
public static boolean IsHuiwen(Node head){
Node n1=head;
Node n2=head;
// System.out.println(n2.next.next.data);
while(n2.next!=null&&n2.next.next!=null){
//这里有个很容易出错的地方是 如果while循环的循环条件没有加上n2.next!=null则会抱空指针异常,以为当n2已经是最后一个了 则不存在n2.next.next
//快慢指针 n2前进2步 n1前进1步
n1=n1.next;
n2=n2.next.next;
}//循环结束时n2指向最末尾节点,n1刚好指向中间节点
n2=n1.next;
Node pre=n1;
Node next=null;
while(n2!=null){
//后半部分节点逆序指向
next=n2.next;
n2.next=pre;
pre=n2;
n2=next;
}
n1.next=null;
n2=pre;
n1=head;
while(n1.next!=null&&n2.next!=null){
//原单链表的前后节点 进行逐一比较
if(n1.data!=n2.data)
return false;
n1=n1.next;
n2=n2.next;
}
return true;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in=new Scanner(System.in);
String tmp=in.nextLine();
char[] s=tmp.toCharArray();
Node[] node=new Node[s.length];
for(int i=0;i<s.length;i++){
node[i]=new Node(s[i]);
}
for(int i=0;i<s.length;i++){
if(i==s.length-1){ //最后一个节点,指针指向空
node[i].next=null;
}
else node[i].next=node[i+1];
}
boolean flag=IsHuiwen(node[0]);
System.out.println("链表是回文吗? "+flag);
}
}