内容概括:
- 3-1.什么是链表
- 3-2.在链表中添加元素
- 3-3.使用链表的虚拟头结点
- 3-4.链表的遍历,查询和修改
- 3-5.从链表中删除元素
- 3-6.使用链表实现栈
- 3-7.带有尾指针的链表:使用链表实现队列
3-1.什么是链表
- 链表:真正的动态数据结构
- 最简单的动态数据结构
- 更深入的理解引用(或者指针)
- 更深入的理解递归
- 辅助组成其他数据结构
链表Linked List
链表像火车一样,每个节点相当于一节车厢,每一节车厢存放真正的数据,每一节车厢之间要有next链接
- 数据存储在“节点”(Node)中
- 如果一个节点的next为null,则已经到了链表末尾了
- 优点:真正的动态,不需要处理固定容量的问题
- 缺点:与数组比较丧失了随机访问的能力
数组和链表相比
组成链表的内部节点和next类:
- 数组最好用于索引有语意的情况。scores[2]
- 最大的优点:支持快速查询
- 链表不适合用于索引有语意的情况
- 最大优点:动态
public class LinkedList<E> {
//私有内部类
private class Node {//只有在链表内才能访问
public E e;//节点数据
public Node next;//节点next
public Node(E e, Node next) {
this.e = e;//用户传进来的e赋值给当前节点的e
this.next = next;//用户传来的next当前节点的next }
public Node(E e) {
this(e, null); }
public Node() {
this(null, null) }
@Override
public String toString(){
return e.toString();}
}
private Node head;//链表头
private int size;//链表长度
public LinkedList(){
head=null;
size=0; }
//获取链表中元素的个数
public int getSize(){
return size; }
//返回链表是否为空
public boolean isEmpty(){
return size==0; }
}
3-2.在链表中添加元素
1.在链表头添加元素
//在链表头添加新的元素
public void addFirst(E e){
// Node node=new Node();
//// node.next=head;
//// head=node;
head=new Node(e,head);//当前节点为用户传进来的节点,当前节点的next为用户上一个头结点
size++;
}
2.在链表中间添加元素
如果顺序调换。
- 关键:找到要添加的节点的前一个节点
//在链表的index(0-based)位置添加新的元素e
//在链表中不是一个常用操作,练习用:
public void add(int index,E e){
if(index<0||index>size){//判断index的合法性
throw new IllegalArgumentException("Add is Fail,Illega Index"); }
if(index==0)
addFirst(e);
else{
Node prev=head;
for (int i=0;i<index-1;i++){//让prev找到index-1位置
prev=prev.next;//让prev挪向待插入位置的前一个节点
// Node node =new Node(e);//1
// node.next=prev.next;//2
// prev.next=node;//3
//new Node(e,prev.next);完成前两句话的任务
//prev.next=..完成第三句话的任务
prev.next=new Node(e,prev.next);
size++;
}}}
//在链表的末尾添加新的元素e
public void addLast(E e){
add(size,e);
}
3-3.链表设置虚拟头结点
元素从0开始,dummyHead是0位置的前一个节点
- 在没有虚拟头结点的链表中链表头添加元素和链表任意位置上添加元素的操作不同
- 因为链表头没有前一个节点,为了实现两个操作统一,设置一个虚拟头结点。
public LinkedList(){
dummyHead=new Node(null,null);//创建一个虚拟头结点
size=0; }
//获取链表中元素的个数
public int getSize(){
return size; }
//返回链表是否为空
public boolean isEmpty(){
return size==0; }
//在链表的index(0-based)位置添加新的元素e
//在链表中不是一个常用操作,练习用:
public void add(int index,E e){
if(index<0||index>size){//判断index的合法性
throw new IllegalArgumentException("Add is Fail,Illega Index");
}
Node prev=dummyHead;//虚拟头结点,0位置的元素的前一个节点
for (int i=0;i<index;i++)//让prev找到index位置的前一个位置
prev=prev.next;//让prev挪向待插入位置的前一个节点
prev.next=new Node(e,prev.next);
size++;
}
//在链表头添加新的元素
public void addFirst(E e){
add(0,e);
}
//在链表的末尾添加新的元素e
public void addLast(E e){
add(size,e);
}
3-4.链表的遍历、查询和修改
获得链表的第index个元素
//获得链表的第index(0-based)个位置的元素
public E get(int index){
//判断index的合法性
if(index<0||index>size){//判断index的合法性
throw new IllegalArgumentException("Get is Fail,Illega Index");
}
Node cur=dummyHead.next;//从dummyHead的下一个节点开始遍历
for(int i=0;i<index;i++)
cur=cur.next;
return cur.e;//返回的就是index位置的元素
}
//获得链表的第一个元素
public E getFirst(){
return get(0);
}
//获得链表的最后一个元素
public E getLast(){
return get(size-1);//从0开始算的 0个位置有一个元素 size=1
}
修改index位置的元素
//修改链表的第index(0-based)个位置的元素
public void set(int index,E e){
//判断index的合法性
if(index<0||index>size){//判断index的合法性
throw new IllegalArgumentException("Set is Fail,Illega Index");
}
Node cur=dummyHead.next;
for (int i=0;i<index;i++)
cur=cur.next;
cur.e=e;//让第index位置的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;//找不到
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
Node cur=dummyHead.next;
// for(Node cur=dummyHead.next;cur!=null;cur=cur.next)等价
while(cur!=null){
res.append(cur+"->");
cur=cur.next;
}
res.append("NULL");//到达节尾
return res.toString();
}
Main_Activity.java中实现
public class Main {
public static void main(String[] args) {
LinkedList<Integer> linkedList=new LinkedList<>();
for(int i=0;i<5;i++){
linkedList.addFirst(i);
System.out.println(linkedList);
}
linkedList.add(2,666);//在索引为2的位置添加元素666
System.out.println(linkedList);
}
}
结果
0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL
3-5.从链表中删除元素
链表删除操作的实现:
//从链表中删除index元素的草(0-based)
public E remove(int index){
//判断index的合法性
if(index<0||index>size){//判断index的合法性
throw new IllegalArgumentException("Set is Fail,Illega Index");
}
Node prev=dummyHead;
for (int i=0;i<index;i++){
prev=prev.next;//存待删除之前的节点
}
Node retNode=prev.next;//要删除的节点retNode
prev.next=retNode.next;//跳过retNode
retNode=null;
size--;
return retNode.e;//返回删除的的元素
}
//删除第一个元素
public E removeFirst(){
return remove(0);
}
//删除最后一个元素
public E removeLast(){
return remove(size-1);
}
Main实现
public class Main {
public static void main(String[] args) {
LinkedList<Integer> linkedList=new LinkedList<>();
for(int i=0;i<5;i++){
linkedList.addFirst(i);
System.out.println(linkedList); }
linkedList.add(2,666);//在索引为2的位置添加元素666
System.out.println(linkedList);
//删除2的元素
linkedList.remove(2);
System.out.println(linkedList);
//删除第一个元素
linkedList.removeFirst();
System.out.println(linkedList);
//删除最后的元素
linkedList.removeLast();
System.out.println(linkedList);
}
}
结果
0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL
4->3->2->1->0->NULL
3->2->1->0->NULL
3->2->1->NULL
链表的时间复杂度分析:
添加操作:O(n)
- addLast(e) O(n)
- addFirst(e) O(1)
- add(index,e) O(n/2)=O(n)
删除操作:O(n)
- removeLast(e) O(n)
- removeFirst(e) O(1)
- remove(index,e) O(n/2)=O(n)
修改操作:O(n)
- set(index,e) O(n)
查找操作: O(n)
- get(e) O(n)
- contains(e) O(n)
3-6.使用链表实现栈
要想实现上一节只对链表头进行操作,可以将链表实现栈,将链表头看做栈顶
public interface Stack<E> {
int getSize();//返回栈的元素个数
boolean isEmpty();//返回栈是否为空
void push(E e);//入栈
E pop();//出栈
E peek();//查看栈末尾的元素
}
public class LinkedListStack<E> implements Stack<E>{
private LinkedList<E> list;
public LinkedListStack(){
list=new LinkedList<>();
}
@Override
public int getSize(){
return list.getSize();
}
@Override
public boolean isEmpty(){
return list.isEmpty();
}
//给栈顶添加一个元素
@Override
public void push(E e){
list.addFirst(e);//链表头是栈顶
}
//从栈顶取出元素
@Override
public E pop(){
return list.removeFirst();
}
//看栈顶的元素
@Override
public E peek(){
return list.getFirst();
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("Stack:top ");
res.append(list);
return res.toString();
}
//测试用例
public static void main(String[] args) {
LinkedListStack<Integer> stack=new LinkedListStack<>();
for(int i=0;i<5;i++){
stack.push(i);//入栈
System.out.println(stack);
}
stack.pop();//出栈
System.out.println(stack);
}
}
结果:
Stack:top 0->NULL
Stack:top 1->0->NULL
Stack:top 2->1->0->NULL
Stack:top 3->2->1->0->NULL
Stack:top 4->3->2->1->0->NULL
Stack:top 3->2->1->0->NULL
数组栈和链表的栈的比较
public class MainTest {//Queue的多态性
//测试使用q运行opCount个enqueue和dequeue操作两种队列需要花费的时间,单位秒
private static double testStack(Stack<Integer> stack,int opCount){
long startTime=System.nanoTime();//计算当时时间,单位纳秒
//。。。。。你要进行的操作
//生成随机数
Random random=new Random();
for(int i=0;i<opCount;i++){
//入队插入随机数
stack.push(random.nextInt(Integer.MAX_VALUE));//0-Integer最大值
}
for(int i=0;i<opCount;i++){
//出队操作
stack.pop();
}
long endTime=System.nanoTime();//计算结束时间,单位纳秒
return (endTime-startTime)/1000000000.0;//转换单位成秒
}
public static void main(String[] args) {
int opCount=100000;
//数组栈的操作
ArrayStack<Integer> arrayStack=new ArrayStack<>();
double time1=testStack(arrayStack,opCount);
System.out.println("ArrayStack Time="+time1+"s");
//链表实现的栈的操作
LinkedListStack<Integer> linkedListStack=new LinkedListStack<>();
double time2=testStack(linkedListStack,opCount);
System.out.println("LinkedListStack Time="+time2+"s");
}
}
结果:并不是所有情况LinkedListStack都比ArrayStack时间快
ArrayStack Time=0.037542493s
LinkedListStack Time=0.021257096s
3-7.带有尾指针的链表:使用链表实现队列
在队尾中添加一个tail属性,来控制元素的入队操作,时间复杂度为O(1)
head属性来控制出队操作,时间复杂度也为O(1)
基本属性
public class LinkedListQueue<E> implements Queue<E> {
//私有内部类
private class Node {//只有在链表内才能访问
public E e;//节点数据
public Node next;//节点next
public Node(E e, Node next) {
this.e = e;//用户传进来的e赋值给当前节点的e
this.next = next;//用户传来的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() {
head = null;
tail = null;
size = 0; }
@Override
public int getSize() {
return size; }
@Override
public boolean isEmpty() {
return size == 0;
}}
进队操作
//进队操作、在队尾中插入元素
@Override
public void enqueue(E e){
if (tail == null) {//链表尾如为空,则队首head也为空
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
出队操作
//出队操作,从队首
@Override
public E dequeue(){
if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an Empty queue");
Node reNode=head;
head=head.next;
reNode.next=null;
if(head==null)
tail=null;
size--;
return reNode.e;
}
//获取队首元素
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("Queue is empty");
return head.e;
}
重写toString方法
@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[] arg){
LinkedListQueue<Integer> queue=new LinkedListQueue<>();
for(int i=0;i<10;i++){
queue.enqueue(i);
System.out.println(queue);
//每插入队列3个元素,取出一个元素
if(i%3==2){//0、1、2 2对3取余为2
queue.dequeue();
System.out.println(queue);
} } }
结果:
Queue:front 0->NULL tail
Queue:front 0->1->NULL tail
Queue:front 0->1->2->NULL tail
Queue:front 1->2->NULL tail
Queue:front 1->2->3->NULL tail
Queue:front 1->2->3->4->NULL tail
Queue:front 1->2->3->4->5->NULL tail
Queue:front 2->3->4->5->NULL tail
Queue:front 2->3->4->5->6->NULL tail
Queue:front 2->3->4->5->6->7->NULL tail
Queue:front 2->3->4->5->6->7->8->NULL tail
Queue:front 3->4->5->6->7->8->NULL tail
Queue:front 3->4->5->6->7->8->9->NULL tail
(转自发条鱼)