• 《算法》笔记 1


    下压栈(简称栈)是一种基于后进后出(LIFO)策略的集合类型。这里学习分别用数组和链表这两种基础数据结构来实现栈。
    栈支持的基本操作有push,pop。

    可变长数组实现

    要用数组实现栈,可以声明一个int型的标记,这个标记指向的位置即为栈顶,push操作时,将值放在数据中标记的位置,同时将标记+1,pop时,返回数组在标记位置的值,同时标记-1。
    但java中的数组在声明的时候其长度就已经固定了,所以栈使用的空间只能是这个数组最大容量的一部分。为了能容纳更多的数据而声明一个特别大的数组会非常浪费空间,那如何解决这个问题,达到既不会浪费数组空间也不会超出数组范围呢?
    可以采用动态调整数组大小的方式,在push操作导致栈已满时,重新创建一个数组,其容量为原数组的两倍。同理在pop操作使数组的闲置空间达到一定程度时,重新创建一个容量更小的数组。但闲置的判断标准不能为一半1/2,否则会造成在1/2的临界点处push和pop操作时发生“抖动”,即频繁地进行数组扩容、缩容操作,这会极大地降低栈的性能。所以通常的做法是在减少到数组容量的1/4时缩容为1/2。

    实现可变长数组的代码如下:

    public class StackResizeArray<Item>  {
    	private Item[] a; // array of items
    	private int n; // number of elements on stack
    
    	public StackResizeArray() {
    		a = (Item[]) new Object[2];
    		n = 0;
    	}
    
    	public boolean isEmpty() {
    		return n == 0;
    	}
    
    	public int size() {
    		return n;
    	}
    
    	private void resize(int capacity) {
    		assert capacity >= n;
    
    		Item[] temp = (Item[]) new Object[capacity];
    		for (int i = 0; i < n; i++) {
    			temp[i] = a[i];
    		}
    		a = temp;
    	}
    
    	public void push(Item item) {
    		if (n == a.length)
    			resize(2 * a.length); // double size of array if necessary
    		a[n++] = item; // add item
    	}
    
    	public Item pop() {
    		if (isEmpty())
    			throw new NoSuchElementException("Stack underflow");
    		Item item = a[n - 1];
    		a[n - 1] = null; // to avoid loitering
    		n--;
    		// shrink size of array if necessary
    		if (n > 0 && n == a.length / 4)
    			resize(a.length / 2);
    		return item;
    	}
    }
    

    pop方法中,a[n - 1] = null将数组中已经出栈的位置设为null,是为了避免对象游离,否则这个位置的对象虽然已经不在栈中,但还被数组引用着,导致GC无法对其回收。

    链表实现

    栈的另外一种实现方式是采用链表,链表是一种递归的数据结构,它或者为空,或者指向一个结点的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。
    构成链表的基本结点可以为:

    private static class Node<Item> {
        public Item item;
        public Node next;
    }
    

    其中泛型的item变量存放数据,同样是Node类型的next变量用来指向下一个结点。将链表的头部作为栈顶,push相当于在表头插入元素,pop是从表头删除元素。
    代码如下:

    public class StackLinkedList<Item> {
    	private static class Node<Item> {
    		public Item item;
    		public Node next;
    	}
    
    	private Node<Item> first;
    	private int N;
    
    	private boolean isEmpty() {
    		return first == null;
    	}
    
    	public int size() {
    		return N;
    	}
    
    	public void push(Item item) {
    		Node<Item> oldFirst = first;
    		first = new Node<Item>();
    		first.item = item;
    		first.next = oldFirst;
    		N++;
    	}
    
    	public Item pop() {
    		if (isEmpty())
    			throw new NoSuchElementException("Stack underflow");
    		Item item = first.item;
    		first = first.next;
    		N--;
    		return item;
    	}
    }
    
    数组与链表的对比

    数组与链表的区别决定了两种栈实现的区别:

    • 存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取; 
    • 存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定; 
    • 存储空间上,链表由于带有指针域,存储密度不如数组大; 
    • 按序号查找时,数组可以随机访问,需要的时间是常数,而链表不支持随机访问,需要的时间与数据规模成线性关系。
    • 按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
    • 插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可; 
    • 空间分配方面: 虽然可变长数组可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败; 链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;

    队列

    链表实现

    先进先出队列(简称队列)是一种基于先进先出(FIFO)策略的集合类型。这里只关注队列的链表实现。与链表实现的栈类似,用结点包含泛型的item变量和next变量,前者存放数据,后者用来指向下一个结点。不同之处在于链表的头部、尾部都会被操作,从链表的一端入队(enqueue),从另一端出队(dequeue)
    代码:

    public class Queue<Item> {
        private static class Node<Item> {
            public Item item;
            public Node next;
        }
    
        private Node<Item> first;
        private Node<Item> last;
        private int N;
    
        public void enqueue(Item item) {
            Node<Item> oldLast = last;
            last = new Node<Item>();
            last.item = item;
            if (isEmpty()) {
                first = last;
            } else {
                oldLast.next = last;
            }
            N++;
        }
    
        public Item dequeue() {
            if (isEmpty()) {
                return null;
            }
            Item result = first.item;
            first = first.next;
            if (isEmpty()) {
                last = null;
            }
            N--;
            return result;
        }
    
        public boolean isEmpty() {
            return N == 0;
        }
    
        public int size() {
            return N;
        }
    }
    
  • 相关阅读:
    Ubutun 安装oh-my-zsh
    归并算法
    3n+1问题
    插入排序
    二分查找
    rehl7配置本地yum仓库
    解决Anaconda3报错:AttributeError: '_NamespacePath' object has no attribute 'sort'
    深度学习情感分析(随机梯度下降代码实现)
    MySQL中创建数据库/表(外键约束),向表中插入数据
    python---利用unittestreport模块输出测试报告
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/11324858.html
Copyright © 2020-2023  润新知