数据结构与算法 - 栈 队列

目录

 

栈stack 

栈的接口设计

动态数组实现栈

练习题

有效的括号

队列

队列的接口设计

简单实现一个queue

 练习 - 用栈实现队列

双端队列

双端队列接口设计

双端队列源码 

 循环队列 

入队列

出队列


栈stack 

  • 栈是一种特殊的线性表,只能在一端进行操作
  • 往栈中添加元素的操作,一般叫做push,入栈
  • 从栈中移除元素的操作,一般叫做pop,出栈
  • 后进先出的原则

                                       

栈的接口设计

int size(); // 元素的数量
boolean isEmpty(); // 是否为空
void push(E element); // 入栈
E pop(); // 出栈
E top(); // 获取栈顶元素
void clear(); // 清空

栈的内部实现是否可以直接利用以前学过的数据结构 ?

  • 动态数组、链表 

动态数组实现栈

public class Stack <E> {

	private List<E> list = new ArrayList<>();
	
	public void clear(){
		list.clear();
	}
	
	public boolean isEmpty(){
		return list.isEmpty();
	}
	
	public void push(E element){
		list.add(element);
	}
	
	public E pop(){
		return list.remove(list.size() - 1); 
	}
	
	public E top(){
		return list.get(list.size() - 1);
	}
	
}

练习题

有效的括号

  • 解法1:
public boolean isValid(String s) {
	while(s.contains("{}") || s.contains("[]") || s.contains("()")){
		s.replace("{}", "");
		s.replace("[]", "");
		s.replace("()", "");
	}
	return s.isEmpty();
}
  • 解法2: 
public boolean isValid2(String s){
	Stack<Character> stack = new Stack<>();
	int len = s.length();
	for(int i = 0; i < len; i++){
		char c = s.charAt(i);
		if(c == '(' || c == '[' || c == '{'){ // 左括号
			stack.push(s.charAt(i)); // 左字符入栈
		}else{ // 右括号
			if(stack.isEmpty()) return false; // 没有左括号, 却匹配到了右括号,false
			
			char left = stack.pop();
			if(left == '(' && c != ')') return false; // 左右括号不匹配
			if(left == '[' && c != ']') return false; // 左右括号不匹配
			if(left == '{' && c != '}') return false; // 左右括号不匹配
		}
	} // 扫描完毕
	return stack.isEmpty();
}
  • 解法3: 
static HashMap<Character, Character> map = new HashMap<>();
static {
	map.put('(', ')');
	map.put('[', ']');
	map.put('{', '}');
}
public boolean isValid(String s){
	Stack<Character> stack = new Stack<>();
	int len = s.length();
	for(int i = 0; i < len; i++){
		char c = s.charAt(i); 
		if(map.containsKey(c)){ // 左括号,哈希表中有该键则入栈
			stack.push(c);
		}else{ // 右括号
			if(stack.isEmpty()) return false;
			char left = stack.pop();
			if(c != map.get(left)) return false;
		}
	}
	return stack.isEmpty();
}

 

队列

队列是一种特殊的线性表,只能在头尾两端进行操作;

  • 队尾:只能从队尾添加元素,入队
  • 对头:只能从对头移除元素,出队
  • 先进先出的原则,First In First Out,FIFO

队列的接口设计

int size(); // 元素的数量
boolean isEmpty(); // 是否为空
void clear(); // 清空
void enQueue(E element); // 入队
E deQueue(); // 出队
E front(); // 获取队列的头元素

队列的内部实现是否可以直接利用以前学过的数据结构?

  • 动态数组、链表
  • 优先使用双向链表,因为队列主要是往头尾操作元素 

简单实现一个queue

import java.util.LinkedList;
import java.util.List;

public class Queue<E> {

    private List<E> linkedList= new LinkedList<>();

    public int size() {
        return linkedList.size();
    }

    public boolean isEmpty() {
        return linkedList.isEmpty();
    }

    // 入队列
    public void enQueue(E element){
        linkedList.add(element);
    }

    // 出队列
    public E deQueue(){
        return linkedList.remove(0);
    }

    // 获取队列的头元素
    public E front(){
        return linkedList.get(0);
    }


    public static void main(String[] args) {
        Queue<Integer> queue = new Queue();
        queue.enQueue(1);
        queue.enQueue(2);
        queue.enQueue(3);
        queue.enQueue(4);
        queue.enQueue(5);

        while (!queue.isEmpty()){
            System.out.println("Queue.main:"+queue.deQueue());
        }
    }
}

 练习 - 用栈实现队列

  • 准备2个栈:instack、outstack
  • 入队时,push到instack
  • 出队时: 如果outstack 为空 ,将instack所有元素逐一弹出,push到outstack,outstack弹出栈顶元素

                      如果outstack不为空,outstack弹出栈顶元素

        

 



/**
 * 使用栈实现队列
 */
public class QueueStack {

    private java.util.Stack<Integer> integerStack = new java.util.Stack<>();

    private java.util.Stack<Integer> outStack = new java.util.Stack<>();

    /**
     * 入队
     *
     * @param x
     */
    public void push(int x) {
        integerStack.push(x);
    }

    /**
     * 出队
     *
     * @return
     */
    public int pop() {
        if (outStack.empty()) {
            while (!integerStack.empty()) {
                outStack.push(integerStack.pop());
            }
        }
        return outStack.pop();
    }

    /**
     * 获取队头元素
     *
     * @return
     */
    public int peek() {
        if (outStack.empty()) {
            while (!integerStack.empty()) {
                outStack.push(integerStack.pop());
            }
        }
        return outStack.peek();
    }

    /**
     * 是否为空
     *
     * @return
     */
    public boolean empty() {
        return integerStack.isEmpty() && outStack.isEmpty();
    }

    public static void main(String[] args) {

    }
}

双端队列

双端队列是能在头尾两端添加、删除的队列;

  • 英文 deque 是 double ended queue 的简称;

双端队列接口设计

int size(); // 元素的数量
boolean isEmpty(); // 是否为空
void clear(); // 清空
void enQueueRear(E element); // 从队尾入队
E deQueueFront(); // 从队头出队
void enQueueFront(E element); // 从队头入队
E deQueueRear(); // 从队尾出队
E front(); // 获取队列的头元素
E rear(); // 获取队列的尾元素

双端队列源码 

import java.util.LinkedList;
import java.util.List;

/**
 * 双端队列
 */
public class Deque<E> {

    private List<E> list = new LinkedList<E>();

    // 入队列 队尾
    public void enQueueRear(E element) {
        list.add(element);
    }

    // 入队列 队头
    public void enQueueFront(E element) {
        list.add(0, element);
    }

    // 出队列 队头
    public E deQueueFront() {
        return list.remove(0);
    }

    // 出队列 队尾
    public E deQueueRear() {
        return list.remove(list.size() - 1);
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    // 获取队列的头元素
    public E front() {
        return list.get(0);
    }

    // 获取队列的头元素
    public E rear() {
        return list.get(list.size() - 1);
    }

    public static void main(String[] args) {
        Deque<Integer> deque = new Deque<>();
        deque.enQueueFront(1);
        deque.enQueueFront(2);
        deque.enQueueRear(3);
        deque.enQueueRear(4);
        while (!deque.isEmpty()){
            System.out.println("Queue.main:"+deque.deQueueRear());
        }
    }
}

 

 循环队列 

 其实队列底层也可以使用动态数组实现,并且各项接口也可以优化到O(1)的时间复杂度。

  • 循环队列底层用数组实现

           

public class CircleQueue<E> {

    private int front;
    private int size;
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;

    public CircleQueue() {
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }

    public int size() {
        return size;
    }

    // 入队列
    public void enQueue(E element) {
        // 扩容
		ensureCapacity(size + 1);
        elements[(front + size) % elements.length] = element;
        size++;
    }


    // 出队列
    public E deQueue() {
        E frontElement = elements[front];
        elements[front] = null;
        front = (front + 1) & elements.length;
        size--;
        return frontElement;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    // 获取队列的头元素
    public E front() {
        return elements[front];
    }

    // 扩容
	private void ensureCapacity(int capacity) {
		int oldCapacity = elements.length;
		if (oldCapacity >= capacity)
			return;
		// 新容量为旧容量的 1.5 倍
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		E[] newElements = (E[]) new Object[newCapacity];
		for (int i = 0; i < size; i++) { // 旧数组中元素移到新数组
			newElements[i] = elements[(i + front) % elements.length];
			// newElements[i] = elements[index(i)];
		}
		System.out.println("从" + oldCapacity + "扩容到" + newCapacity);
		elements = newElements;
		front = 0; // 重置front
	}


    public static void main(String[] args) {

    }
}

入队列

  •    当我们往队列里添加元素时,通常往size位置添加元素; elements[size] = element;

                         

  •    但是我们发现循环队列 出队列时会把元素清除掉,队头位置后移,那么此时elements[size] = element; 就会覆盖44这个元素的值

                                  

  •   那么我们需要往队尾添加元素,其实就是  elements[front + size] = element; 但是如果此时 队尾添加元素已经到达数组最大长度,继续往队尾添加元素,那么将会数组越界。

                                    

  •   于是我们  elements[(front + size) % elements.length] = element; 对数组长度取余

                                   

出队列

  • 出队时,需要取出队头数据,并将front 前移,size减一 ;E frontElement = elements[front];front++;size--; 但是如果下面情况 front++ 为7

              

  • 那么我们将 front = (front + 1) % elements.length;

                             

循环双端队列

  • 可以进行两端添加、删除操作的循环队列

 

                                         

 

1.算法是程序的灵魂,优秀的程序在对海量数据处理时,依然保持高速计算,就需要高效的数据结构算法支撑。2.网上数据结构算法的课程不少,但存在两个问题:1)授课方式单一,大多是照着代码念一遍,数据结构算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上就是听天书了2)说是讲数据结构算法,但大多是挂羊头卖狗肉,算法讲的很少。 本课程针对上述问题,有针对性的进行了升级 3)授课方式采用图解+算法游戏的方式,让课程生动有趣好理解 4)系统全面的讲解了数据结构算法, 除常用数据结构算法外,还包括程序员常用10大算法:二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法、马踏棋盘算法。可以解决面试遇到的最短路径、最小生成树、最小连通图、动态规划等问题及衍生出的面试题,让你秒杀其他面试小伙伴3.如果你不想永远都是代码工人,就需要花时间来研究下数据结构算法。教程内容:本教程是使用Java来讲解数据结构算法,考虑到数据结构算法较难,授课采用图解加算法游戏的方式。内容包括: 稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。学习目标:通过学习,学员能掌握主流数据结构算法的实现机制,开阔编程思路,提高优化程序的能力。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页