数据结构与算法-链表(含经典面试题)

     一 面试经典:

        1. 如何设计一个LRU缓存淘汰算法?基础

        思想:新加的点来了, 首先去链表里面遍历,如果找到了。删掉 然后插入到头部。头部就是最新的吧如果不在原来的链表里:如果有空间就插入头部。LRU有内存限制的,如果没有空间了怎么办? 删除最后一个,完成了这个算法!

( 最近使用,只需要维护一个有序的单链表就可以了。有序的指的就是加入的时间排序 )

        2. 约瑟夫问题  (对手娟,丢到谁谁淘汰,只留最后一个人)

       二 什么是链表?

        1.链表的定义

        链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。

        2.特点
                (1)不需要连续的内存空间。
                (2)有指针引用
                (3)三种最常见的链表结构:单链表、双向链表和循环链表

        单向链表说明:

        从单链表图中,可以发现,有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们一般把第一个结点叫作头结点,把最后一个结点叫作尾结点。
其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。while(p.next != null){} head 自己记录的

          循环链表说明:

        循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。

      三 代码实现单链表和双向链表

package list;

/**
 * @author:Kevin
 * @create: 2023-08-14 10:45
 * @Description:        单向列表
 */

public class MyLinkedList {

    private ListNode head;
    private int size = 0;

    public void insertHead(int data){    //插入链表头部    O(1)
        ListNode newNode = new ListNode(data);

        newNode.next = head;
        head = newNode;

    }

    public void inertNth(int data,int postion){        //插入链表中间位置   O(n)
        if (postion == 0){
            insertHead(data);
        }else {
            ListNode cur = head;
            for (int i = 1;i<postion;i++){
                cur = cur.next;
            }
            ListNode listNode = new ListNode(data);

            listNode.next = cur.next;
            cur.next = listNode;
        }

    }

    public void deleteHead(){   //O(1)

        head = head.next;

    }

    public void deleteNth(int position){    //O(n)

        if (position == 0){
            deleteHead();
        }else {
            ListNode cur = head;
            for (int i =1;i<position;i++){
                cur = cur.next;
            }
            cur.next=cur.next.next;
        }

    }

    public void print(){

        ListNode cur = head;
        while (cur!=null){
            System.out.println(cur.value);
            cur = cur.next;
        }

    }



}

class ListNode{

    int value;  //值
    ListNode next;  //指针

    ListNode(int value) {
        this.value = value;
        this.next = null;
    }
}

                双向链表

package list;

/**
 * 	双向列表
 */
public class DoubleLinkList {		// 双向链表

	private DNode head;		//头 
	private DNode tail;		// 尾
	
	DoubleLinkList(){
		head = null;
		tail = null;
	}
	
	public void inserHead(int data){
		DNode newNode = new DNode(data);
		if(head == null){
			tail = newNode;
		}else{
			head.pre = newNode;
			newNode.next = head;
		}
		head = newNode;
	}
	public void deleteHead(){
		if(head == null) return ;		//没有数据
		if(head.next == null){		//就一个点
			tail = null;
		}else{
			head.next.pre = null;	
		}
		head = head.next;
	}

	public void deleteKey(int data){
		DNode current = head;
		while (current.value != data) {
			if (current.next == null) {
				System.out.println("没找到节点");
				return ;
			}
			current = current.next;
		}
		if (current == head) {// 指向下个就表示删除第一个
			deleteHead();
		} else {
			current.pre.next = current.next;
			if(current == tail){		//删除的是尾部
				tail = current.pre;
				current.pre = null;
			}else{
				current.next.pre = current.pre;
			}
		}
	}
}

class DNode{
	
	int value;		//值
	DNode next;		//下一个的指针
	DNode pre;		//指向的是前一个指针

	DNode(int value){
		this.value = value;
		this.next = null;
		this.pre = null;
	}
}

     四 数组vs链表

重要区别:
        1.数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。
        2.链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。
        3.数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,
导致“内存不足(out ofmemory)”。如果声明的数组过小,则可能出现不够用的情况。
        4.动态扩容:数组需再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容。

猜你喜欢

转载自blog.csdn.net/qq_67801847/article/details/132273482