算法与数据结构全阶班-左程云版(二)基础阶段之2.链表、栈、队列、递归行为、哈希表和有序表(上)

简介: 本文主要介绍了一些常用的数据结构,包括链表、栈、队列、递归、哈希表和有序表。

引言

本文主要介绍了一些常用的数据结构,包括链表、栈、队列、递归、哈希表和有序表。

1.链表结构

单链表节点结构:

class Node {
      public int value;
      public Node next;
      public Node(int data) {
          value = data;
      }
  }

双向链表节点结构:

class DoubleNode {
    public int value;
    public DoubleNode last;
    public DoubleNode next;
    public DoubleNode(int data) {
        value = data;
    }
}

简单练习:

1)单链表和双链表如何反转

2)把给定值都删除


实现如下:

// 反转单链表
public static Node reverseLinkedList(Node head) {
    Node pre = null;
    Node next = null;
    while (null != head) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}
// 反转双向链表
public static DoubleNode reverseDoubleList(DoubleNode head) {
    DoubleNode pre = null;
    DoubleNode next = null;
    while (null != head) {
        next = head.next;
        head.next = pre;
        head.last = next;
        pre = head;
        head = next;
    }
    return pre;
}
// 删除单链表元素
public static Node removeValue(Node head, int num) {
    // 跳过链表头部值为num的部分
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    // 头节点为第一个值不为num的节点
    Node pre = head;
    Node cur = head;
    while (cur != null) {
        // pre始终保持其值不等于num
        if (num == cur.value) {
            pre.next = cur.next;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}
// 删除双向链表元素
public static DoubleNode removeValue(DoubleNode head, int num) {
    // 跳过头节点
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    if (head != null) {
        head.last = null;
    }
    DoubleNode cur = head, pre = head;
    while (cur != null) {
        if (cur.value == num) {
            pre.next = cur.next;
            cur.last = null;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}

Java和C++在垃圾回收方面存在区别:


当一块内存空间所对应的变量或引用不存在时,就会自动释放这块内存,Java存在内存泄漏的原因是因为变量的生命周期不同,例如一个生命周期较短的方法中对一个生命周期更长的数据结构进行了操作,但是调用结束时并没有恢复,简单来说,内存空间找不到对应变量或引用就会被释放,否则就不会被释放;


C++ 内存泄漏是因为声明的变量忘记释放,必须手动调用函数释放。

2.栈和队列

栈:数据先进后出,犹如弹匣;

队列:数据先进先出,好似排队。

栈和队列的实现:

(1)基于双向链表

package structure02;
/**
 * @author Corley
 * @date 2021/10/7 14:02
 * @description LeetCodeAlgorithmZuo-structure02
 */
public class LinkedListQueueStack {
    /*
    自定义节点
     */
    static class Node<T> {
        public Node<T> last;
        public Node<T> next;
        public T value;
        public Node(T value) {
            this.value = value;
        }
    }
    /*
    自定义双向链表
     */
    static class DoubleLinkedList<T> {
        public Node<T> head;
        public Node<T> tail;
        public void addFromHead(T value) {
            Node<T> cur = new Node<>(value);
            if (null == head) {
                head = cur;
                tail = cur;
            } else {
                cur.next = head;
                head.last = cur;
                head = cur;
            }
        }
        public void addFrombottom(T value) {
            Node<T> cur = new Node<>(value);
            if (null == head) {
                head = cur;
                tail = null;
            } else {
                cur.last = tail;
                tail.next = cur;
                tail = cur;
            }
        }
        public T popFromHead() {
            if (null == head) {
                return null;
            }
            T res = head.value;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                head = head.next;
                head.last = null;
            }
            return res;
        }
        public T popFromBottom() {
            if (null == head) {
                return null;
            }
            T res = tail.value;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                tail = tail.last;
                tail.next = null;
            }
            return res;
        }
        public boolean isEmpty() {
            return null == head;
        }
    }
    /*
    自定义栈
     */
    static class Stack<T> {
        private final DoubleLinkedList<T> stack;
        public Stack() {
            stack = new DoubleLinkedList<>();
        }
        public void push(T value) {
            stack.addFromHead(value);
        }
        public T pop() {
            return stack.popFromHead();
        }
        public boolean isEmpty() {
            return stack.isEmpty();
        }
    }
    /*
    自定义队列
     */
    static class Queue<T> {
        private final DoubleLinkedList<T> queue;
        public Queue() {
            queue = new DoubleLinkedList<>();
        }
        public void push(T value) {
            queue.addFromHead(value);
        }
        public T pop() {
            return queue.popFromBottom();
        }
        public boolean isEmpty() {
            return queue.isEmpty();
        }
    }
}

(2)基于数组

使用数组时,需要考虑数组的大小问题,这里选择使用固定长度的数组来实现。

其中,数组实现较麻烦,如下:

2345_image_file_copy_104.jpg

实现如下:

package structure02;
/**
 * @author Corley
 * @date 2021/10/7 14:50
 * @description LeetCodeAlgorithmZuo-structure02
 * 使用环形数组RingBuffer的思想实现队列
 */
public class ArrayQueueStack {
    static class Queue {
        private final int[] arr;
        private int pushi;          // 加元素的下标
        private int pulli;          // 取元素的下标
        private int size;
        private final int limit;    // 队列大小
        public Queue(int limit) {
            arr = new int[limit];
            pushi = 0;
            pulli = 0;
            size = 0;
            this.limit = limit;
        }
        public void push(int num) {
            if (size == limit) {
                throw new RuntimeException("队列已满,不能再添加元素!");
            }
            size++;
            arr[pushi] = num;
            pushi = nextIndex(pushi);
        }
        public int pull() {
            if (isEmpty()) {
                throw new RuntimeException("队列已空,不能再取元素!");
            }
            size--;
            int res = arr[pulli];
            pulli = nextIndex(pulli);
            return res;
        }
        public boolean isEmpty() {
            return 0 == size;
        }
        private int nextIndex(int index) {
            return index < (limit - 1) ? (index + 1) : 0;
        }
    }
    class Stack {
        int[] arr;
        int size;
        int limit;
        public Stack(int limit) {
            arr = new int[limit];
            this.limit = limit;
            size = 0;
        }
        public void push(int num) {
            if (size == limit) {
                throw new RuntimeException("栈已满,不能再添加元素!");
            }
            arr[size++] = num;
        }
        public int pop() {
            if (0 == size) {
                throw new RuntimeException("栈已空,不能再取元素!");
            }
            return arr[--size];
        }
    }
}

既然语言都提供了这些结构和API,为什么还需要手写代码:


1)算法问题无关语言;

2)语言提供的API是有限的,当有新的功能是API不提供的就需要改写;

3)任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的。


实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能

1)pop: push、getMin操作的时间复杂度都是O(1);

2)设计的栈类型可以使用现成的栈结构。


实现思路1如下:


维护两个栈,一个栈保存数据,另一个栈保存到当前高度的最小值,如下:


2345_image_file_copy_106.jpg

实现如下:

static class MinStack1 {
    private final Stack<Integer> stackData;
    private final Stack<Integer> stackMin;
    public MinStack1() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }
    public void push(int num) {
        if (this.stackMin.isEmpty()) {
            this.stackMin.push(num);
        } else if (num < this.stackMin.peek()) {
            this.stackMin.push(num);
        } else {
            this.stackMin.push(this.stackMin.peek());
        }
        this.stackData.push(num);
    }
    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        stackMin.pop();
        return stackData.pop();
    }
    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        return  stackMin.peek();
    }
}

实现思路2如下:


维护两个栈,一个栈保存数据,一个栈保存到当前高度的最小值,但是只有当当前要入栈的数≤之前(栈下面)的最小值时才入最小栈,会节省一些空间,但是会增加时间,因为增加了逻辑判断,如下:

2345_image_file_copy_107.jpg

实现如下:

static class MinStack2 {
    private final Stack<Integer> stackData;
    private final Stack<Integer> stackMin;
    public MinStack2() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }
    public void push(int num) {
        if (this.stackMin.isEmpty()) {
            this.stackMin.push(num);
        } else if (num <= this.stackMin.peek()) {
            this.stackMin.push(num);
        }
        this.stackData.push(num);
    }
    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        int res = stackData.pop();
        if (res == getMin()) {
            stackMin.pop();
        }
        return res;
    }
    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        return  stackMin.peek();
    }
}

栈和队列的常见面试题:

1)如何用栈结构实现队列结构;

2)如何用队列结构实现栈结构。


用队列实现栈:

用两个队列来实现,包括原始队列和辅助队列,如下:


2345_image_file_copy_108.jpg

两个队列角色互相切换。


实现如下:

static class TwoQueueStack<T> {
    private Queue<T> queue;
    private Queue<T> help;
    public TwoQueueStack() {
        queue = new LinkedList<>();
        help = new LinkedList<>();
    }
    public void push(T value) {
        queue.offer(value);
    }
    public T pop() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T res = queue.poll();
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return res;
    }
    public T peek() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T res = queue.poll();
        help.offer(res);
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return res;
    }
    public boolean isEmpty() {
        return queue.isEmpty();
    }
}


相关文章
|
11天前
|
JSON 监控 算法
员工上网行为监控:利用Scala编写数据处理和分析算法
企业在数字化时代利用Scala进行员工上网行为监控,以确保合规和网络安全。通过Scala的数据处理和分析能力,读取CSV日志数据转换为DataFrame,分析员工行为,如统计最常访问网站。此外,还展示了将监控数据以JSON格式提交至公司网站的函数,实现实时信息更新与安全防护。
41 5
|
7天前
|
机器学习/深度学习 数据采集 算法
Python用逻辑回归、决策树、SVM、XGBoost 算法机器学习预测用户信贷行为数据分析报告
Python用逻辑回归、决策树、SVM、XGBoost 算法机器学习预测用户信贷行为数据分析报告
19 1
|
5天前
|
存储
栈与队列练习题
栈与队列练习题
|
5天前
数据结构第四课 -----线性表之队列
数据结构第四课 -----线性表之队列
|
6天前
|
存储 Java
数据结构奇妙旅程之栈和队列
数据结构奇妙旅程之栈和队列
|
6天前
|
机器学习/深度学习 算法 搜索推荐
R语言LASSO特征选择、决策树CART算法和CHAID算法电商网站购物行为预测分析
R语言LASSO特征选择、决策树CART算法和CHAID算法电商网站购物行为预测分析
12 0
|
9天前
|
算法 索引
数据结构与算法-三种队列基础入门
数据结构与算法-三种队列基础入门
8 0
|
10天前
带你彻底理解栈和队列
带你彻底理解栈和队列
17 1
|
12天前
|
数据采集 机器学习/深度学习 监控
使用R编写公司电脑监控软件的异常行为检测算法
本文阐述了在数字化时代,企业使用R语言开发高效异常行为检测算法的重要性,以保障网络安全和数据隐私。文章通过示例展示了如何加载和预处理数据,绘制数据传输趋势图,并运用3倍标准差法识别异常点。此外,还介绍了一种利用R的httr库将异常数据自动提交到网站的方法,以增强安全防护。
36 3
|
13天前
|
存储
数据结构:7、队列
数据结构:7、队列
20 0