数据结构基础2

1.单链表的数据结构+案例
2.双链表的数据结构+案例
3.栈的数据结构(双向链表+数组实现) + 案例
4.队列的数据结构(双向链表+数组实现)
5.写一个NodeUtils工具类
6.全部的代码+测试用例gitee仓库自取


1.单链表的数据结构和案例

单向链表

/**
 * 构造一个单向链表
 */
public class  Node   {
    // 该节点的值 value
    public T value;
    // 该节点指向的下一节点
    public Node nextNode;
    // 构造函数
    public Node(T value){
        this.value = value;
    }
}

案例1:给定一个链表,如何反转链表?

/**
 * @author Tara
 * @implNote 给定一个单向链表,如何反转链表?
 */
public class Pra1 {

    public static void main(String[] args) {
        Object[] arr = {2, 4, 7, 9, 23, 21};
        // 构造一个链表
        Node node = NodeUtils.constructNode(arr);
        // 打印链表轨迹
        NodeUtils.printSingleNode(node);
        // 反转链表
        Node node1 = reverseLinkedList(node);
        //打印链表结构
        NodeUtils.printSingleNode(node1);
    }

    //反转链表
    public static Node reverseLinkedList(Node head){
        Node preNode = null;
        Node nextNode = null;
        while (null!=head){
            nextNode = head.nextNode;
            head.nextNode = preNode;
            preNode = head;
            head = nextNode;
        }
        while (null!=head){
            System.out.print(head.value+" -> ");
        }
        return preNode;
    }
}

案例2:把给定的node节点中有指定值的节点全部移除

/**
 * @author Tara
 * @implNote 给定一个链表,删除所有节点值为x的节点,并返回头节点
 */
public class Pra2 {

    public static void main(String[] args) {
        Object[] arr = {3, 2, 4, 7, 9, 3, 3, 2, 3, 23, 21};
        // 构造一个链表
        Node node = NodeUtils.constructNode(arr);
        // 打印链表轨迹
        NodeUtils.printSingleNode(node);
        // 移除所有值为3的节点
        Node node1 = removeValues(node,2);
        //打印链表结构
        NodeUtils.printSingleNode(node1);
    }

    //移除单向链表的值为xx的节点
    private static Node removeValues(Node head, Object value) {
        // 1.考虑head第一位是否存在要移除的节点
        while (null != head) {
            // 先排除head位置存在要移除的节点
            if (head.value != value) {
                // 首位没有符合条件的就停止。
                break;
            }
            head = head.nextNode;
        }
        // 2.考虑 中间是否存在要移除的节点,存在就跳过去,不存在指针指向下一个
        // 游标指向当前节点
        Node cur = head;
        Node pre = null;
        while (null != cur) {
            if (cur.value == value) {
                pre.nextNode = cur.nextNode;
            } else {
                pre = cur;
            }
            cur = cur.nextNode;
        }
        return head;
    }
}

2.双链表的数据结构和案例

双向链表

/**
 * 构造一个双向链表
 * @param 
 */
public class DoubleNode  {
    //该节点的值
    public T value;
    // 该节点指向的上一个节点
    public DoubleNode lastNode;
    // 该节点指向的下一节点
    public DoubleNode nextNode;
    // 双向链表的构造方法
    public DoubleNode(T value){
        this.value = value;
    }
}

案例1:给定一个双向链表,如何反转双向链表?

/**
 * @author Tara
 * @implNote 给定一个双向链表,如何反转双向链表?
 */
public class Pra3 {

    public static void main(String[] args) {
        Object[] arr = {2, 4, 7, 9, 23, 21, 8 , 43};
        // 构造一个双向链表
        DoubleNode node = NodeUtils.constructDoubleNode(arr);
        // 打印链表轨迹
        NodeUtils.printDoubleNode(node);
        //打印链表结构
        NodeUtils.printDoubleNode2(node);

        System.out.println("=====================反转双向链表======================");
        DoubleNode node1 = reverseDoubleNodeList(node);
        NodeUtils.printDoubleNode(node1);
        NodeUtils.printDoubleNode2(node1);
    }

    //反转双向链表
    public static DoubleNode reverseDoubleNodeList(DoubleNode head){
        DoubleNode preNode = null;
        DoubleNode nextNode = null;
        while (null!=head){
            nextNode = head.nextNode;
            head.nextNode = preNode;
            head.lastNode = nextNode;
            preNode = head;
            head = nextNode;
        }
        return preNode;
    }
}

案例2:给定一个双向链表,删除所有节点值为x的节点,并返回头节点head

/**
* @author Tara
* @implNote 给定一个双向链表,删除所有节点值为x的节点,并返回头节点head
*/
public class Pra4 {

   public static void main(String[] args) {
       Object[] arr = {3, 2, 4, 7, 9, 3, 3, 2, 3, 23, 21};
       // 构造一个链表
       DoubleNode node = NodeUtils.constructDoubleNode(arr);
       // 打印链表轨迹
       NodeUtils.printDoubleNode(node);
       NodeUtils.printDoubleNode2(node);
       // 移除所有值为3的节点
       DoubleNode removedNode = removeValues(node, 2);
       //打印链表结构
       NodeUtils.printDoubleNode(removedNode);
       NodeUtils.printDoubleNode2(removedNode);
   }

   /**
    * 移除单向链表的值为xx的节点
    * @param head 双链表的一个头部
    * @param value 要移除节点的值value;
    * @return 新头部节点newHead
    */
   private static DoubleNode removeValues(DoubleNode head, Object value) {
       // 1.考虑head第一位是否存在要移除的节点
       while (null != head) {
           // 先排除head位置存在要移除的节点
           if (head.value != value) {
               // 首位没有符合条件的就停止。
               break;
           }
           head = head.nextNode;
           //指针后移后,上一个指针置空;
           head.lastNode = null;
       }
       // 2.考虑 中间是否存在要移除的节点,存在就跳过去,不存在指针指向下一个
       // 游标指向当前节点
       DoubleNode cur = head;
       DoubleNode pre = null;
       DoubleNode next = null;
       while (null != cur) {
           // 记录指针的下一节点
           next = cur.nextNode;
           if (cur.value == value) {
               // 直接将当前节点指向下一节点,跳过cur;
               pre.nextNode = next;
               // 释放当前节点的指向
               cur.nextNode = null;
               cur.lastNode = null;
               // 让下一个节点往回指;
               next.lastNode = pre;
           } else {
               // 当前节点变为下一次的pre节点
               pre = cur;
           }
           //游标后移
           cur = next;
       }
       return head;
   }
}

3.栈的数据结构(双向链表+数组实现) + 案例

双向链表实现

/**
 * 使用双向队列实现栈
 * @param 
 */
public class Stack {
    private DoubleEndsQueue queue;
    // 构造方法
    public Stack(){
        queue = new DoubleEndsQueue<>();
    }

    /**
     * 压栈操作
     * @param value
     */
    public void push(T value){
        queue.addFromHead(value);
    }
    // 取值
    public T pop(){
        return queue.popFromHead();
    }

    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty(){
        return queue.isEmpty();
    }
}

数组实现

/**
 * 使用数组实现固定长度的栈
 */
public class Stack {
    // 存值的数组;
    private Object[] arr;
    // 栈顶部的索引;
    private int index;
    //初始化长度;
    private final int limit;

    // 构造方法初始化
    public Stack(int limit) {
        this.arr = new Object[limit];
        this.index = 0;
        this.limit = limit;
    }

    // 压栈操作
    public void push(Object value) {
        if (index == limit) {
            throw new RuntimeException("栈已经满了,不能再装了");
        }
        this.arr[index] = value;
        index++;
    }

    // 取栈顶元素;
    public Object pop() {
        if (index == 0) {
            throw new RuntimeException("栈已经空了,不能再取了");
        }
        index--;
        Object temp = this.arr[index];
        this.arr[index] = null;
        return temp;
    }

    // 栈为空判断
    public boolean isEmpty() {
        return index == 0;
    }

    @Override
    public String toString() {
        String str = "";
        for (int i = 0; i < arr.length; i++) {
            str += arr[i] + ",";
        }
        return str;
    }
}

案例1:设计一个栈StackMin来实现,向栈中加入若干个值,以最高的效率取出栈中的最小值,设计出O(l)的方案

/**
 * @Author:      Tara.Li(李春侣)
 * @Contact Me WeChat: flyme9988
 * @ReWroteBy:
 * @Date:  2021-12-19 20:23:08
 * @Description:  设计一个栈,无论怎么压栈,弹出,都能以最小的时间复杂度,获取栈中的最小元素。
 */
public class StackMin {
    //定义一个正常存储的栈
    private Stack stackData;
    // 定义一个存储最小元素的栈,
    private Stack stackMin;

    //构造函数

    public StackMin() {
        stackData = new Stack<>();
        stackMin = new Stack<>();
    }
    /**
     * 压栈
     * @param newNum 新压入栈的值
     */
    public void push(int newNum){
        //如果记录最小值的栈为空,,即为第一个元素,直接两个栈都进。
        if (this.stackMin.isEmpty()){
            stackMin.push(newNum);

            // 如果新压入的元素小于stackMin元素的栈顶元素,就压入栈顶。
        }else if (newNum < this.stackMin.peek()) {
            stackMin.push(newNum);

            // 如果新压入的元素大于等于stackMin元素,就再压一次栈顶元素。
        }else if (newNum >= this.stackMin.peek()){
            Integer temp = this.stackMin.peek();
            this.stackMin.push(temp);
        }

        // 无论stackMin怎么变动,stackData都是正常push值的
        stackData.push(newNum);
    }

    /**
     * 弹出按照正常情况弹出stackData栈的数据
     * @return
     */
    public Object pop(){
        //判断栈是不是为空
        if (stackData.isEmpty()){
            throw new RuntimeException("当前栈元素已经空了");
        }
        // 两个同步弹出,保证栈内元素一致。
        stackMin.pop();
        return  stackData.pop();
    }

    /**
     * 获取最小元素
     * @return
     */
    public Object getMin(){
        //判断栈是不是为空
        if (stackData.isEmpty()){
            throw new RuntimeException("当前栈元素已经空了");
        }
        //当前栈顶永远存储的是最小元素。
        return this.stackMin.peek();
    }
}

测试案例1

/**
 * @author Tara
 * @implNote  取任意一个栈,栈中的最小元素,设计出O(l)的方案
 */
public class Pra9 {
    public static void main(String[] args) {

        StackMin stack = new StackMin();

        // 想栈中加入最小元素
        stack.push(8);
        stack.push(2);
        stack.push(3);
        stack.push(2);
        stack.push(3);
        stack.push(5);
        stack.push(7);
        stack.push(3);
        stack.push(99);
        stack.push(0);
        stack.push(12);
        stack.push(17);
        stack.push(19);
        stack.push(13);

        System.out.println("最小元素为:"+stack.getMin());
        // 当前栈中的元素为:
        for (int i = 0; i < 15; i++) {
            System.out.print(stack.pop()+",");
        }
    }
}

4.队列的数据结构(双向链表+数组实现)

双向链表实现

/**
 * 使用双向链表实现队列
 * @param 
 */
public class Queue {
    // 初始化一个双向链表
    private DoubleEndsQueue queue;
    public Queue(){
        queue = new DoubleEndsQueue<>();
    }

    /**
     * 放进队列
     * @param value
     */
    public void push(T value){
        queue.addFromHead(value);
    }

    /**
     * 从队列中取出
     * @return
     */
    public T poll(){
        return queue.popFromBottom();
    }

    /**
     * 判断是否为空
     * @return
     */
    public boolean isEmpty(){
        return queue.isEmpty();
    }
}

数组实现

/**
 * 使用数组实现固定 长度的队列
 */
public class Queue {
    // 数组实现
    private Object[] arr;
    // 添加元素位置标记
    private int pushi;
    // 弹出元素位置标记
    private int polli;
    // 用于记录当前占用了几个空间,最大空间是limit
    private int size;
    // 队列大小
    private final int limit;

    //构造方法
    public Queue(int limit) {
        this.arr = new Object[limit];
        pushi = 0;
        polli = 0;
        size = 0;
        this.limit = limit;
    }

    //队列中添加值value
    public void push(Object value) {
        if (size == limit) {
            throw new RuntimeException("队列满了,不能再push了");
        }
        // 占用空间+1;
        size++;
        // 当前位置指针存储值
        arr[pushi] = value;
        // 前置指针往下一个位置指;
        pushi = nextIndex(pushi);
    }
    
    //取出一个队列中的值
    public Object pop() {
        if (size == 0) {
            throw new RuntimeException("队列空了,不能再poll了");
        }
        //占用空间--
        size--;
        Object temp = arr[polli];
        arr[polli] = 0; // 回收,值置空
        polli = nextIndex(polli);
        return temp;
    }

    /**
     * 获取下一个位置索引
     *
     * @param i 遇到最大位置,直接跳到0,实现循环复用
     * @return
     */
    private int nextIndex(int i) {
        // limit-1是最后一个元素 ,如果到了最后一个元素,下一个就返回到0,否则,继续往下索引;
        return i < limit - 1 ? i + 1 : 0;
    }

    // 队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        String str = "";
        for (int i = 0; i < arr.length; i++) {
            str += arr[i].toString() + ",";
        }
        return str;
    }
}


结尾:部分工具类代码

双向链表写的队列方法DoubleEndsQueue

/**
 * 定义一个双头链表队列,可以从头部插入,头部移除,尾部插入,尾部移除
 *
 * @param 
 */
public class DoubleEndsQueue {
    //用双向链表 定义 头部,和尾部指针
    public DoubleNode head;
    public DoubleNode tail;

    /**
     * 从头部向队列中添加值
     *
     * @param value
     */
    public void addFromHead(T value) {
        // 创建一个节点存储传进来的value
        DoubleNode cur = new DoubleNode(value);
        // 如果当前队列为空(没有节点),则头,尾都指向这个新节点cur
        if (null == head) {
            head = cur;
            tail = cur;
        } else {//节点不为空,往头部添加,head前移,尾部不动
            cur.nextNode = head;
            head.lastNode = cur;
            head = cur;
        }
    }

    /**
     * 从尾部向队列中添加值
     *
     * @param value
     */
    public void addFromBottom(T value) {
        DoubleNode cur = new DoubleNode(value);
        if (null == head) {
            head = cur;
            tail = cur;
        } else {
            cur.lastNode = tail;
            tail.nextNode = cur;
            tail = cur;
        }
    }

    /**
     * 从头部取值
     *
     * @return
     */
    public T popFromHead() {
        if (null == head) {
            return null;
        }
        DoubleNode cur = head;
        // 头部和尾部指向同一个节点==>只有一个节点
        if (head == tail) {
            head = null;
            tail = null;
        } else { // 有两个及以上节点
            head = head.nextNode;
            head.lastNode = null;
            cur.nextNode = null;
        }
        return cur.value;
    }

    /**
     * 从尾部取值
     *
     * @return
     */
    public T popFromBottom() {
        if (null == head) {
            return null;
        }
        DoubleNode cur = tail;
        // 只有一个元素
        if (head == tail) {
            head = null;
            tail = null;
        } else { // 有两个及以上元素
            tail = tail.lastNode;
            tail.nextNode = null;
            cur.lastNode = null;
        }
        // 返回底部元素;
        return cur.value;
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return head == null;
    }
}

6.全部的代码+测试用例gitee仓库自取

https://gitee.com/flyme9988/leetcode

你可能感兴趣的:(数据结构基础2)