动态数据结构基础—链表

链表是最基础也是最重要的数据结构之一,它是真正的动态数据结构。如果有了对链表理解的基础将会更容易地学习更复杂地数据结构,比如二叉搜索树、AVL树、红黑树、B 树等等,这些都是上层建筑,而数组和链表才是结构基础

一、数据结构链表的存储方式

数据结构的存储方式只有两种:数组(顺序存储)和链表(链式存储)

而链表是不连续的,它是靠指针指向下一个元素的位置,所以不存在数组扩容的问题;如果知道某一个元素的前驱和后驱,操作指针就可删除该元素或则插入新的元素,时间复杂度为 O(1) 。

但是因为链表的存储空间不连续,就无法根据索引算出对应元素的地址,所以就不能随机访问;而且由于每个元素必须指向前后元素位置的指针,会消耗更多的内存空间。

在链表中,涉及到计算机领域重要地概念就是所谓地引用(在C++中叫做指针),其次链表本身就是一个线性数据结构,有清晰的递归结构,所以我们可以用循环的方式对链表进行操作。

二、什么是链表 Linked List ?

就是把数据存储在“节点”(Node)中。

//指向Node节点的下一个节点next
Class Node {
	E e;
	Node next;
}

对于链表,就像火车一样,每一个节点就是一节车厢,在车厢中存储正真的数据,而车厢和车厢直接进行连接以使得数据是整合在一起的,用户方便再所有的数据上进行查询等其它操作。而数据和数据之间的连接就是由 next 来完成的。
节点
因为链表存储的数据是有限的,当节点存储的是一个 NULL ,表示这个节点是最后一个节点。所以链表是一个真正动态的数据结构,不需要处理固定容量的问题。 缺点就是不能随机访问。不能像数组那样,给一个索引,直接从数组拿出所需要的元素。

从底层机制上,数组所开辟的空间在内存里是连续分布的,所以能直接找到索引对应的偏移,直接计算出相应的数据所存储的内存地址,直接用 O(1) 的复杂读直接拿出来。但链表是靠 next 一层一层连接的,所以在底层每一个节点所在的内存的位置是不同的,我们必须靠 next 来找到所需的元素,比较麻烦。

下面我们用代码实现链表的存储节点:

public class LinkedList<E> {
    private class Node{
        public E e;
        public Node next;
   		//1.传来e和next
        public Node(E e,Node next){
            this.e = e;
            this.next = next;
        }
        //只传来e
        public Node(E e){
            this(e,null);
        }
        //什么都不传 
        public Node(){
            this(null,null);
        }

        @Override
        public String toString() {
        	//return e.toString();
            return "Node{" +
                    "e=" + e +
                    ", next=" + next +
                    '}';
        }
    }
}

如果我们要访问链表的所有节点,就要存储链表的头节点 head。开始的时候,链表里没有元素,此时:

//初试时候为空
head = null;
size = 0;
获取链表中元素的个数:
public int getSize(){
	return size;
}
返回当链表元素为空的时候:
public boolean isEmpty(){
	return size == 0;
}

三、在链表中添加元素

在链表头添加元素:

public void addFirst(E e){
	Node node = new Node(e);
	node.next = head;
	head = node;
	//head = new Node(e,head);
	size++;
}
在链表中间添加新的元素:

我们要找到插入元素的上一个节点位置,叫做 prevprev 初始位置是和 head 头节点指向同一个位置。

要找到插入元素的前一个节点,直接将它前一个节点的 next 指向要插入的元素,然后再将这个元素的 next 指向它上一个节点的后一个节点就完成添加操作。
在这里插入图片描述
关键点:找到要添加节点的前一个节点

注意点(易错点):

//错误的方法!!!
prev.next = node;
node.next = prev.next;

代码实现:

public void  add(int index, E e){
	if (index < 0 && index > size)
		throw new IllegalArgumentException("Add failed. Illegal index");
	if (index == 0)
		addFirst(e);
	else {
		Node prev = head;
		for (int i = 0; i < index - 1; i++){
			//移动prev
			prev = prev.next;
		}
		Node node = new Node(e);
		node.next = prev.next;
		prev.next = node;
		//prev.next = new Node(e,prev.next);
		size++;
	}
}
在链表末尾添加元素:
public void addLast(E e){
	add(size,e);
}
改进方法:为链表设立虚拟头节点

因为在链表头添加元素比较特殊,没有头节点,所以我们在头节点前面设一个虚拟的节点 null (不存任何元素),叫做 dummyHead。
在这里插入图片描述
那么现在的头节点表示为 head = dummyHead.next

获得链表的第index个位置的元素:
public E get(int index) {
	if (index < 0 && index > size)
		throw new IllegalArgumentException("Get failed. Illegal index");
	Node curr = dummyHead.next;
	for (int i = 0; i < index; i++) {
		curr = curr.next;
	}
	return curr.e;
}
获得链表的第一个元素:
public E getFirst() {
	return get(0);
}
获得链表的最后一个元素:
public E getLast() {
	return get(size - 1);
}
修改链表的第index个位置的元素e:
public void set(int index, E e) {
	if (index < 0 && index > size)
		throw new IllegalArgumentException("Set failed. Illegal index");
	Node curr = dummyHead.next;
	for (int i = 0; i < index; i++) {
		curr = curr.next;
	}
	curr.e = e;
}
查找链表中是否存在元素e:
public boolean contains(E e) {
	Node curr = dummyHead.next;
	while (curr != null) {
		if (curr.e.equals(e)) {
			return true;
		}
		curr = curr.next;
	}
	return false;
}

四、在链表中删除元素

寻找待删除元素的前一个节点,加入我们要删除索引为 2 位置的节点:
在这里插入图片描述
在这里插入图片描述
核心代码:

pre.next = delNode.next
delNode.next = null;

即进行了下面这个操作:
在这里插入图片描述

从链表中删除index位置的元素,并返回删除的元素
public E remove(int index){
	if (index < 0 || index >= size){
		throw new IllegalArgumentException("Remove failed. Index is illegal.");
	}
	Node prev = dummyHead;
	for (int i = 0;i < index;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);
}

五、链表的时间复杂度分析

添加操作 :O(n)

添加操作时间复杂度
链表头添加O(1)
链表尾添加O(n)
链表中添加O(n/2) = O(n)

删除操作:O(n)

删除操作时间复杂度
链表头删除O(n)
链表尾删除O(1)
链表中删除O(n/2) = O(n)

修改操作:O(n)

查找操作:O(n)

六、使用链表实现栈

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(){
        StringBuffer res = new StringBuffer();
        res.append("Stack:top ");
        res.append(list);
        return res.toString();
    }
}

public interface Stack<E> {
    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();
}

七、使用链表实现队列

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(){
        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){
            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 retNode = head;
        head = head.next;
        retNode.next = null;
        if(head == null)
            tail = null;
        size --;
        return retNode.e;
    }

    @Override
    public E getFront(){
        if(isEmpty())
            throw new IllegalArgumentException("Queue is empty.");
        return head.e;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");

        Node cur = head;
        //可以改写成for形式
        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);

            if(i % 3 == 2){
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

八、链表与递归

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值